DATAREST-451 - Add projection and excerpt to reference documentation.
Original pull request: #163.
This commit is contained in:
committed by
Oliver Gierke
parent
d6595970e3
commit
7d13c447fe
@@ -15,6 +15,7 @@ include::intro.adoc[]
|
||||
include::getting-started.adoc[]
|
||||
include::repository-resources.adoc[]
|
||||
include::representations.adoc[]
|
||||
include::projections-excerpts.adoc[]
|
||||
include::validation.adoc[]
|
||||
include::events.adoc[]
|
||||
include::metadata.adoc[]
|
||||
|
||||
321
src/main/asciidoc/projections-excerpts.adoc
Normal file
321
src/main/asciidoc/projections-excerpts.adoc
Normal file
@@ -0,0 +1,321 @@
|
||||
[[projections-excerpts]]
|
||||
= Projections and Excerpts
|
||||
|
||||
Spring Data REST presents a default view of the domain model you are exporting. But sometimes, you may need to
|
||||
alter the view of that model for various reasons. In this section, you will learn how to define *projections* and
|
||||
*excerpts* to serve up simplified and reduced views of resources.
|
||||
|
||||
== Projections
|
||||
|
||||
Look at the following domain model:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Entity
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private long id;
|
||||
|
||||
private String firstName;
|
||||
|
||||
private String lastName;
|
||||
|
||||
@OneToOne
|
||||
private Address address;
|
||||
...
|
||||
}
|
||||
----
|
||||
|
||||
This `Person` has several attributes:
|
||||
|
||||
* *id* is the primary key
|
||||
* *firstName* and *lastName* are data attributes
|
||||
* *address* is a link to another domain object
|
||||
|
||||
Now assume we create a corresponding repository as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {}
|
||||
----
|
||||
|
||||
By default, Spring Data REST will export this domain object including all of its attributes. *firstName* and *lastName* will be exported
|
||||
as the plain data objects that they are. There are two options regarding the *address* attribute.
|
||||
|
||||
One option is to also define a repository for `Address` objects like this:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public interface AddressRepository extends CrudRepository<Address, Long> {}
|
||||
----
|
||||
|
||||
In this situation, a `Person` resource will render the *address* attribute as a URI to it's corresponding `Address` resource. If we were
|
||||
to look up "Frodo" in the system, we could expect to see a HAL document like this:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
{
|
||||
"firstName" : "Frodo",
|
||||
"lastName" : "Baggins",
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "http://localhost:8080/persons/1"
|
||||
},
|
||||
"address" : {
|
||||
"href" : "http://localhost:8080/persons/1/address"
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
There is another route. If the `Address` domain object does not have it's own repository definition, Spring Data REST will inline the data
|
||||
fields right inside the `Person` resource.
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
{
|
||||
"firstName" : "Frodo",
|
||||
"lastName" : "Baggins",
|
||||
"address" : {
|
||||
"street": "Bag End",
|
||||
"state": "The Shire",
|
||||
"country": "Middle Earth"
|
||||
},
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "http://localhost:8080/persons/1"
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
But what if you don't want *address* details at all? Again, by default, Spring Data REST will export all its attributes (except the *id*).
|
||||
You can offer the consumer of your REST service an alternative by defining one or more *projections*.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Projection(name = "noAddresses", types = { Person.class }) <1>
|
||||
public interface NoAddresses { <2>
|
||||
|
||||
public String getFirstName(); <3>
|
||||
|
||||
public String getLastName(); <4>
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
This projection has the following details:
|
||||
|
||||
<1> The `@Projection` annotation flags this as a projection. The *name* atrributes provides
|
||||
the name of the projection, which you'll see how to use shortly. The *types* attributes targets this projection to only apply to
|
||||
`Person` objects.
|
||||
<2> It's a Java interface making it declarative.
|
||||
<3> It exports the *firstName*.
|
||||
<4> It exports the *lastName*.
|
||||
|
||||
The `NoAddresses` projection only has getters for *firstName* and *lastName* meaning that it won't serve up any address information.
|
||||
|
||||
Assuming you have a separate repository for `Address` resources, the default view from Spring Data REST is slightly different as
|
||||
shown below:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
{
|
||||
"firstName" : "Frodo",
|
||||
"lastName" : "Baggins",
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "http://localhost:8080/persons/1{?projection}", <1>
|
||||
"templated" : true <2>
|
||||
},
|
||||
"address" : {
|
||||
"href" : "http://localhost:8080/persons/1/address"
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
<1> There is a new option listed for this resource, *{?projection}*.
|
||||
<2> The *self* URI is a URI Template.
|
||||
|
||||
To view apply the projection to the resource, look up `http://localhost:8080/persons/1/projection=noAddresses`.
|
||||
|
||||
NOTE: The value supplied to the `projection` query parameter is the same as specified in `@Projection(name = "noAddress")`. It has
|
||||
nothing to do with the name of the projection's interface.
|
||||
|
||||
It's possible to have multiple projections.
|
||||
|
||||
=== Finding existing projections
|
||||
|
||||
Spring Data REST provides https://spring.io/blog/2014/07/14/spring-data-rest-now-comes-with-alps-metadata[ALPS], a micro metadata format.
|
||||
To view the ALPS metadata, visit `http://localhost:8080/alps`. If you navigate down to the ALPS document for `Person` resources (which
|
||||
would be `/alps/persons`), you can find many details about `Person` resources.
|
||||
|
||||
Projections will be listed along with the details about the *GET* REST transitions, something like this:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
...
|
||||
"id" : "get-person", <1>
|
||||
"name" : "person",
|
||||
"type" : "SAFE",
|
||||
"rt" : "#person-representation",
|
||||
"descriptors" : [ {
|
||||
"name" : "projection", <2>
|
||||
"doc" : {
|
||||
"value" : "The projection that shall be applied when rendering the response. Acceptable values available in nested descriptors.",
|
||||
"format" : "TEXT"
|
||||
},
|
||||
"type" : "SEMANTIC",
|
||||
"descriptors" : [ {
|
||||
"name" : "noAddresses", <3>
|
||||
"type" : "SEMANTIC",
|
||||
"descriptors" : [ {
|
||||
"name" : "firstName", <4>
|
||||
"type" : "SEMANTIC"
|
||||
}, {
|
||||
"name" : "lastName", <4>
|
||||
"type" : "SEMANTIC"
|
||||
} ]
|
||||
} ]
|
||||
} ]
|
||||
},
|
||||
...
|
||||
----
|
||||
|
||||
<1> This part of the ALPS document shows details about *GET* and `Person` resources.
|
||||
<2> Further down are the *projection* options.
|
||||
<3> Further down you can see projection *noAddresses* listed.
|
||||
<4> The actual attributes served up by this projection include *firstName* and *lastName*.
|
||||
|
||||
=== Bringing in hidden data
|
||||
|
||||
So far, you have seen how projections can be used to reduce the information that is presented to the user. Projections can also bring
|
||||
in normally unseen data. For example, Spring Data REST will ignore fields or getters that are marked up with `@JsonIgnore` annotations.
|
||||
Look at the following domain object:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Entity
|
||||
public class User {
|
||||
|
||||
@Id @GeneratedValue
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
|
||||
@JsonIgnore <1>
|
||||
private String password;
|
||||
|
||||
private String[] roles;
|
||||
...
|
||||
----
|
||||
|
||||
<1> Jackson's `@JsonIgnore` is used to prevent the *password* field from getting serialized into JSON.
|
||||
|
||||
This `User` class can be used to store user information as well as integration with Spring Security. If you create a `UserRepository`,
|
||||
the *password* field would normally have been exported. Not good! In this example, we prevent that from happening by applying Jackson's
|
||||
`@JsonIgnore` on the *password* field.
|
||||
|
||||
NOTE: Jackson will also not serialize the field into JSON if `@JsonIgnore` is on the field's corresponding getter function.
|
||||
|
||||
However, projections introduce the ability to still serve this field. It's possible to create a projection like this:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Projection(name = "passwords", types = { User.class }) <2>
|
||||
public interface PasswordProjection {
|
||||
|
||||
public String getPassword();
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
If such a projection is created and used, it will side step the `@JsonIgnore` directive placed on `User.password`.
|
||||
|
||||
IMPORTANT: This example may seem a bit contrived, but it's possible with a richer domain model and many projections, to accidentally
|
||||
leak such details. Since Spring Data REST cannot discern the sensitivity of such data, it is up to the developers to avoid such situations.
|
||||
|
||||
== Excerpts
|
||||
|
||||
An excerpt is a projection that is applied to a repository automatically. For an example, you can alter the `PersonRepository` as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RepositoryRestResource(excerptProjection = NoAddresses.class)
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {}
|
||||
----
|
||||
|
||||
This directs Spring Data REST to use the `NoAddresses` projection when embedding `Person` resources into collections or related resources.
|
||||
|
||||
NOTE: Excerpt projections do NOT apply when rendering a single resource.
|
||||
|
||||
In addition to altering the default rendering, excerpts have additional rednering options as shown below.
|
||||
|
||||
== Excerpting commonly accessed data
|
||||
|
||||
A common situation with REST services arises when you compose domain objects. For example, a `Person` is stored in one table and their
|
||||
related `Address` is stored in another. By default, Spring Data REST will serve up the person's *address* as a URI the client must
|
||||
navigate. But if it's common for consumers to always fetch this extra piece of data, an excerpt projection can go ahead and inline
|
||||
this extra piece of data, saving you an extra *GET*.
|
||||
|
||||
To do so, let's define another excerpt projection:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Projection(name = "inlineAddress", types = { Person.class }) <1>
|
||||
public interface InlineAddress {
|
||||
|
||||
public String getFirstName();
|
||||
|
||||
public String getLastName();
|
||||
|
||||
public Address getAddress(); <2>
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
<1> This projection has been named *inlineAddress*.
|
||||
<2> This projection adds in `getAddress` which returns the `Address` field. When used inside a projection, it causes the information
|
||||
to be inlined.
|
||||
|
||||
We can plug it into the `PersonRepository` definition as follows:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RepositoryRestResource(excerptProjection = InlineAddress.class)
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {}
|
||||
----
|
||||
|
||||
This will cause the HAL document to appear as follows:
|
||||
|
||||
[source,javascript]
|
||||
----
|
||||
{
|
||||
"firstName" : "Frodo",
|
||||
"lastName" : "Baggins",
|
||||
"address" : { <1>
|
||||
"street": "Bag End",
|
||||
"state": "The Shire",
|
||||
"country": "Middle Earth"
|
||||
},
|
||||
"_links" : {
|
||||
"self" : {
|
||||
"href" : "http://localhost:8080/persons/1"
|
||||
},
|
||||
"address" : { <2>
|
||||
"href" : "http://localhost:8080/persons/1/address"
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This should appear as a mix of what you've seen so far.
|
||||
|
||||
<1> The *address* data is inlined directly, so you don't have to navigate to get it.
|
||||
<2> The link to the `Address` resource is still provided, making it still possible to navigate to its own resource.
|
||||
|
||||
WARNING: Configuring `@RepositoryRestResource(excerptProjection=...)` for a repository alters the default behavior. This can potentially
|
||||
case breaking change to consumers of your service if you have already made a release. Use with caution.
|
||||
Reference in New Issue
Block a user