Choosing a strategy to expose JPA Entities

The content object(s) exposed in resources are JPA Entities. The interesting point about wrapping a JPA Entity in a resource comes with the low-level nature of an Entity itself, which supposedly represents a restricted identifiable domain area. This definition should ideally be entirely translated to the exposed REST resources.

So, how do we represent an Entity in REST HATEOAS? How do we safely and uniformly represent the JPA associations?

This recipe presents a simple and conservative method to answer these questions.

How to do it…

  1. We have presented one entity used as a resource (Index.java). Here is another entity that is used: Exchange.java. This entity presents a similar strategy to expose its JPA associations:
    import edu.zc.csm.core.converters.IdentifiableSerializer;
    import edu.zc.csm.core.converters.IdentifiableToIdConverter;
    
    @Entity
    public class Exchange extends ProvidedId<String> {
      private String name;
    
      @ManyToOne(fetch = FetchType.EAGER)
      @JoinColumn(name = "market_id", nullable=true)
      @JsonSerialize(using=IdentifiableSerializer.class)
      @JsonProperty("marketId")
      @XStreamConverter(value=IdentifiableToIdConverter.class, strings={"id"})
      @XStreamAlias("marketId")
      private Market market;
      
      @OneToMany(mappedBy = "exchange", cascade = CascadeType.ALL, fetch=FetchType.LAZY)
      @JsonIgnore
      @XStreamOmitField
      private Set<Index> indices = new LinkedHashSet<>();
      
      @OneToMany(mappedBy = "exchange", cascade = CascadeType.ALL, fetch=FetchType.LAZY)
      @JsonIgnore
      @XStreamOmitField
      private Set<StockProduct> stocks = new LinkedHashSet<>();
    
      public Exchange(){}
      public Exchange(String exchange) {
        setId(exchange);
      }
    
      //getters & setters
    
      @Override
          public String toString() {
            return "Exchange [name=" + name + ", market=" + market + ", id=" + id+ "]";
          }
    } 
  2. The Exchange.java Entity references two custom utility classes that are used to transform the way external Entities are fetched as part of the main entity rendering (JSON or XML). Those utility classes are the following IdentifiableSerializer and the IdentifiableToIdConverter:
    • The IdentifiableSerializer class is used for JSON marshalling:
      import org.springframework.hateoas.Identifiable;
      import com.fasterxml.jackson.core.JsonGenerator;
      import com.fasterxml.jackson.core.JsonProcessingException;
      import com.fasterxml.jackson.databind.JsonSerializer;
      import com.fasterxml.jackson.databind.SerializerProvider;
      public class IdentifiableSerializer extends JsonSerializer<Identifiable<?>> {
         @Override
         public void serialize(Identifiable<?> value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
          provider.defaultSerializeValue(value.getId(), jgen);
         }
      }
    • The IdentifiableToIdConverter class is used for XML marshlling and is built with XStream dependencies:
      import com.thoughtworks.xstream.converters.Converter;
      public class IdentifiableToIdConverter implements Converter {
          private final Class <Identifiable<?>> type;
          public IdentifiableToIdConverter(final Class <Identifiable<?>> type, final Mapper mapper, final ReflectionProvider reflectionProvider, final ConverterLookup lookup, final String valueFieldName) {
              this(type, mapper, reflectionProvider, lookup, valueFieldName, null);
          }
        public IdentifiableToIdConverter(final Class<Identifiable<?>> type, final Mapper mapper, final ReflectionProvider reflectionProvider, final ConverterLookup lookup, final String valueFieldName, Class valueDefinedIn) {
              this.type = type;
              Field field = null;
              try {
        field = (valueDefinedIn != null? valueDefinedIn : type.getSuperclass()).getDeclaredField("id");
        if (!field.isAccessible()) {
          field.setAccessible(true);
          }
            } catch (NoSuchFieldException e) {
              throw new IllegalArgumentException( 	e.getMessage()+": "+valueFieldName);
                }
            }
            public boolean canConvert(final Class type) {
              return type.isAssignableFrom(this.type);
          }
          public void marshal(final Object source, final HierarchicalStreamWriter writer,final 	MarshallingContext context) {
                if(source instanceof Identifiable){
                  writer.setValue( ((Identifiable<?>)source).getId() .toString()
                );
              }
            }
          public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
            return null;
          }
      }

How it works...

Let's understand how this strategy works.

The REST CRUD principle

One REST architectural constraint is to present a uniform interface. A uniform interface is achieved by exposing resources from endpoints that can all be targeted from different HTTP methods (if applicable).

Resources can also be exposed under several representations (json, xml, and so on), and information or error messages must be self-descriptive. The implementation of HATEOAS provides a great bonus for the self-explanatory character of an API.

In REST, the more intuitive and inferable things are, the better. From this perspective, as a web/UI developer, I should be able to assume the following:

  • The structure of the object I receive from the GET call on an endpoint will be the expected structure that I have to send back with a PUT call (the edition of the object)
  • Similarly, the same structure should be used for the creation of a new object (the POST method)

This consistency of payload structures among different HTTP methods is a SOLID and conservative argument that is used when it is time to defend the API interests. It's pretty much always the time to defend the API interests.

Exposing the minimum

Exposing the minimum amount of information has been the core idea during the refactoring for this chapter. It's usually a great way to ensure that one endpoint won't be used to expose information data that would be external to the initial controller.

A JPA Entity can have associations to other Entities (@OneToOne, @OneToMany, @ManyToOne, or @ManyToMany).

Some of these associations have been annotated with @JsonIgnore (and @XStreamOmitField), and some other associations have been annotated with @JsonSerialize and @JsonProperty (and @XStreamConverter and @XStreamAlias).

If the Entity doesn't own the relationship

In this situation, the database table of the Entity doesn't have a foreign key to the table of the targeted second Entity.

The strategy here is to completely ignore the relationship in REST to reflect the database state.

The ignore instructions depend on the supported representations and the chosen implementations.

For json, we are using Jackson, the solution has been: @JsonIgnore.

For xml, we are using XStream, the solution has been: @XstreamOmitField.

If the Entity owns the relationship

Here, the database table of the Entity has a foreign key the table of the targeted second Entity.

If we plan to update an entity of this table, which depends on an entity of the other table, we will have to provide this foreign key for the entity.

The idea then is to expose this foreign key as a dedicated field just as all the other columns of the database table. Again, the solution to implement this depends on the supported representations and the configured marshallers.

For json and Jackson, we have done it with the following code snippet:

@JsonSerialize(using=IdentifiableSerializer.class)
@JsonProperty("marketId")

As you can see, we rename the attribute to suggest that we are presenting (and expecting) an ID. We have created the IdentifiableSerializer class that extracts the ID from the entity (from the Identifiable interface) and places only this ID into the value of the attribute.

For xml and XStream, it has been:

@XStreamConverter(value=IdentifiableToIdConverter.class, strings={"id"})
@XStreamAlias("marketId")

In the same way, we rename the attribute to suggest that we are presenting an ID, and we target the custom converter IdentifiableToIdConverter that also chooses only the ID of the Entity as a value for the attribute.

Here is an example of the xml representation example of the ^AMBAPT index:

If the Entity owns the relationship

Separation of resources

This strategy promotes a clear separation between resources. The displayed fields for each resource match the database schema entirely. This is a standard practice in web developments to keep the HTTP request payload unchanged for the different HTTP methods.

When HATEOAS is adopted, we should then fully encourage the use of links to access related entities instead of nested views.

The previous recipe Building links for a Hypermedia-Driven API features examples to access (using links) the Entities that are associated with @...ToOne and @...ToMany. Below is an example of these links in an exposed Entity as it is achieved in the previous recipe:

Separation of resources

There's more…

We detail here official sources of information for the implemented marshallers.

Jackson custom serializers

You can find the official wiki page guide for these serializers at:

http://wiki.fasterxml.com/JacksonHowToCustomSerializers

XStream converters

XStream has been migrated from codehaus.org to Github. To follow an official tutorial about XStream converters, go to:

http://x-stream.github.io/converter-tutorial.html

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset