Using RAML-For-JAX-RS for Brewing (Part II): Building a better model

JP Belanger
RAML by Example
5 min readJul 31, 2018

--

Welcome back!

We left off our discussion with a rather simple model that was somewhat too simple to be useful. It had only simple types and relatively little to help the developer handle changes. Let’s see if we can make this better.

Before we start, we’ll remove the plugin defined in the pom.xml file. Although it’s often simpler to activate the Jackson plugin in the pom.xml file, I’m of the opinion that having to run around the multiple files used in a build to figure out what to do is a bad practice. We’ll be controlling everything RAML from the RAML files. So we will be removing the generateWithTypes tag from our maven file <configuration/>.

<build>
<plugins>
<plugin>
<groupId>org.raml.jaxrs</groupId>
<artifactId>raml-to-jaxrs-maven-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<ramlFile>
${project.build.resources[0].directory}/api.raml
</ramlFile>
<resourcePackage>
org.raml.jaxrs.beertrader.resources
</resourcePackage>
<supportPackage>
org.raml.jaxrs.beertrader.support
</supportPackage>
<modelPackage>
org.raml.jaxrs.beertrader.model
</modelPackage>
</configuration>
</plugin>
</plugins>
</build>

We are still generating resource classes and objects, but right now we aren’t generating Jackson annotations. We’ll address this a bit later.

Back to the model. Firstly, we would like our key and reference types to be UUIDs. That’s what we are using in our database system, and it seems to be a good idea to expose this. The problem here is that UUIDs are not part of the RAML specification. All we have are strings. Hmmm.

We’ll start by declaring a type called UUID in our RAML specification. UUIDs are, after all, strings. So we’ll start here.

UUID:
type: string
pattern: "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"

We could then use this type like so, in a RAML spec:

Beer:
properties:
id: UUID

This would be a valid declaration, and the parser handles it correctly. But raml-to-jaxrs still sees it as a string type, and will, by default generate a java.lang.String. If only we could tell it otherwise…

Well, quite obviously, we can. I wouldn’t be typing all of this without a reason.

raml-to-jaxrs allows us to change some of these types (among other things) through a plugin system. The plugin system is activated through RAML annotations. We need to import a RAML Library fragment that defines those annotations in order for raml-to-jaxrs to change its behaviour. In the raml-java-tools project, we’ll find a file called ramltopojo.raml. We can add it to our project and bring it into our spec with:

#%RAML 1.0
title: The BeerTrader api
description: This is the API for the world famous Beer Trader API
version: v1
baseUri:
value: http://www.beertrader.com/services
protocols: [ HTTP, HTTPS ]
mediaType: [ application/json ]
uses:
ramltopojo: ramltopojo.raml

This file pulls in plugin support for raml-to-pojo, the library that handles RAML types. We will then activate the core.changeType plugin to tell the code generator to use the java.util.UUID type instead of java.lang.String.

UUID:
(ramltopojo.types):
plugins:
- name: core.changeType
arguments: [ java.util.UUID ]
type: string
pattern: "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"

The good thing about this is that Jackson will handle UUIDs properly without any more adjustments, so it’s a pretty simple way of using proper types. Had Jackson not supported the type, we would have had to write a plugin to use annotations or some such. But we don’t, so we won’t this week.

Ok, we’ve moved one step closer to a better model. Huzzah.

Here’s another thing I like to do: have a top-level class for my objects. All my objects will have an ID (think of it like a URN). So let’s build that top class:

Identified:
(ramltopojo.types):
plugins:
- core.makeAbstract
additionalProperties: false
properties:
id: UUID

This object will never travel through the wire: it will only be used as a component of other objects. Therefor we don’t want to build an actual implementation of this object, so we will use another plugin: makeAbstract. This makes it so that only the “interface” half of the object will be generated, without an implementation.

We are getting closer to our target. Well my target at least.

Another thing that annoys me is the manual copying from transfer objects to database objects. The thing is that not all fields should be copied: very often the “plain” properties (strings, number, dates and such) are easy to copy through several libraries. For example, Spring has many methods that allow this.

So we will extract the plain properties of our objects and pull them out to a superinterface. This allows me to reuse this interface in my database objects. I can also use this interface to implement some automatic copying mechanism instead of copying them by hand. That way I can:

  1. Make sure that properties are copied from my database objects to my transfer objects and vice-versa.
  2. Exclude the more complex members of my objects by not having them in the topmost interface

Let’s just look at the resulting RAML:

InventoryEntryProperties:
(ramltopojo.types):
plugins:
- core.makeAbstract
additionalProperties: false
type: Identified
properties:
count: integer
availableCount: integer
InventoryEntry:
(ramltopojo.types):
plugins:
- core.jackson
type: InventoryEntryProperties
additionalProperties: true
properties:
beerReference: UUID

In this example, InventoryEntryProperties is the common interface between database objects and the RAML objects. Notice that it is extending type Identified. This will make both our database objects and RAML objects Identified through its “id” property. It has two more simple properties: “count” — which counts the total number of a particular kind of beer — and “availableCount — which counts the bottles that aren’t committed to trades yet. It is also an abstract object.

InventoryEntry inherits from InventoryEntryProperties. First, this is the actual over-the-wire implementation RAML object. For this reason, we need to make sure that the Jackson annotations are added to this generated class. This is added to all the RAML objects that will go over the wire (including the enumerations). Notice that the beerReference property is only accessible through the transfer object: this is because our JPA objects will not be implementing a beerReference property. On the network side, we will use UUIDs, but on the database side we will be using a regular hibernate relationship.

All the objects are built using this model. Our database objects will look like this:

@Entity
public class InventoryObject extends PersistentObject implements InventoryEntryProperties {
@Basic
private int count;
private int availableCount;
@OneToOne
private BeerObject beer;
}

With this model we can build fairly simple resource implementations.

@Override
public GetUsersInventoryByUserIdResponse getUsersInventoryByUserId(String userId) {
// Load from DB.
List<InventoryObject> inventoryObjects = context
.createQuery( "from InventoryObject",
InventoryObject.class)
.getResultList();
// Copy DB objects to RAML
List<InventoryEntry> beers =
inventoryObjects.stream()
.map(this::inventoryObjectToInventory)
.collect(Collectors.toList());

return GetUsersInventoryByUserIdResponse
.respond200WithApplicationJson(beers);
}
InventoryEntry inventoryObjectToInventory(InventoryObject db) {
InventoryEntry inventoryEntry = this.dbToTransfer(db);
inventoryEntry.setBeerReference(db.getId());
return inventoryEntry;
}

The “dbToTransfer” method exists and copies the properties. Trust me. Or even better download this phase of the project from https://github.com/jpbelang/BeerTrader/tree/better-model/model.

So we’ve managed to write a RAML model that represents both our business and implementation logic. Sure, it could have been built differently. We could have written a plugin to add JPA annotations to the generated code. Or even simpler, we could have done something using MongoDB and just stored the objects. Or SpringData. Sure. But this is what we did. I like it :-)

Next time, we’ll look into some testing strategies that could leverage the RAML spec.

--

--