Using RAML-For-JAX-RS for Brewing (Part I)

JP Belanger
RAML by Example
7 min readJul 1, 2018

--

This is a tutorial on how to use raml-for-jaxrs to develop a real-life application. We will try to cover every aspect of application development and build on our application base. I’ll break this down into sections. They’ll be clear, don’t worry.

It seems to me that everyone and their brother-in-law is brewing beer these days. All sorts of beers (mostly IPAs, honestly) being made off different recipes and some rather original ideas. It seems that beer has brought out the artist in a lot of people.

And artists like to show off their stuff. And show off they do. They’ll bring their home-brewed stuff to parties and other types of social gatherings and share. Which is all good, until the sheer volume of their production creates a backlog of bottles that will soon overrun your house. Or at least your basement.

Well, there’s BeerTrader to the rescue. BeerTrader will allow you to both keep an inventory of your brews and allow you to setup trades with other users of the site, allowing you to rotate your overstock into different types of beer, just so you don’t go nuts drinking that apricot based recipe that you thought would turn out great.

So the first iteration of the server will be just about useless: it will just be a server that takes messages, translates it to DB objects and persists it in the database. That’s it: no great model, no real security, no real testing. We will then try to organize our data better, add validation, add security features and such. At the end, we should have a good simple implementation of our server.

Stuff you need to know

Well, the first thing you need to know is RAML 1.0. No deep knowledge required. I don’t have a deep knowledge myself and I probably refer to the spec more often than I should. You also probably need to know about JAX-RS. Again, nothing complicated. There will be some hibernate stuff, but we won’t get into the nitty gritty. And the project will be bootstrapped with spring-boot, because it’s magical.

I’m guessing that you’d like to know where the server code is right now: well it’s here. The master branch contains the finished code for our tutorial. I’ll be referring to it all through this post.

The Setup

We are going to be using maven to setup our project. There will be two submodules: the model submodule, where the model objects and JAX-RS stubs will be generated. This is the part we should NOT be editing. In fact, the only files we’ll be editing are the api.raml and the maven POM files.

The second module is the server module, where everything else lives: Spring, hibernate and its objects and our implementations of the JAX-RS interface. The database schema is automatically built and stored in memory (using HSQLDB).

BeerTrader
model
src
main
resources
api.raml <-- Our RAML model
pom.xml <-- The pom.xml using raml-for-jaxrs
server
src
main
java
org
raml
jaxrs
beertrader
data <-- Database model
resources <-- implementations of JAX-RS
spring <-- String configuration for resources
resources
META-INF
persistence.xml <-- Database configuration
pom.xml

Our model objects

For now, we’ll create a very simple REST model. Objects will only use simple properties and use no form of inheritance. We will be improving the model as we go.

All our objects will have an ID.

The first object in the model is, as in most systems, the User. It’s the classic name and email kind. Nothing particularly innovative.

User:
properties:
id: string
name: string

We have two enumerations on how we define the kind of beer being used. Two different YAML list styles giving the same results. By the way, we’ll be storing these enumerations straight into the database. They are in fact shared between both the messages and database objects.

BeerType:
enum: [ALE, STOUT, LAGER, PORTER, MALTS, OTHER]
BeerStyle:
enum:
- AMBER
- BLOND
- BROWN
- CREAM
- DARK
- FRUIT
- GOLDEN
- HONEY
- IPA
- LIGHT
- LIME
- PALE
- PILSNER
- STRONG
- RED
- PILSNER
- WHEAT
- OTHER

We then have the Beer object. This object uses our two enumerations by referring to BeerType and BeerStyle.

Beer:
properties:
id: string
name: string
description: string
type: BeerType
style: BeerStyle

The next object is the InventoryEntry object. This object essentially contains a count for a given beer. The beerReference property contains the ID value of the counted beer.

InventoryEntry:
properties:
id: string
beerReference: string
count: integer
availableCount: integer

Finally, a Trade object. A very flat data structure containing “from” and “to” components. We have two references for the beers being traded matched to the users trading them away. We are still using the string IDs as references. Not much to see here either. We’ll make everything better in the next instalment.

Trade:
properties:
id: string
fromUserReference: string
fromBeerReference: string
fromCount: integer
toUserReference: string
toBeerReference: string
toCount: integer

Our resources

The resources used in our application will start straightforward. The only thing I will do here is separate them so that I have different Java classes. Should I model everything in one tree, it would create only one large Java interface to implement. That would be rather unwieldy structure to use.

So we will break our application down along the lines of operations on our model objects. They will be built in the expected collection/item fashion: the URI “/somethings” will list a collection of objects where we create objects, and “/somethings/{id}” will allow us to play with a specific object.

My first draft started as something like this:

/users:
get:
responses:
200:
body:
application/json: User[]
post:
body:
application/json: User
responses:
201:
body:
application/json: User
/{userId}:
get:
responses:
200:
body:
application/json: User
404:
delete:
responses:
200:
404:
put:
body:
application/json: User
responses:
200:
404:
/users/{userId}/inventory:
get:
responses:
200:
body:
application/json: InventoryEntry[]
post:
body:
application/json: InventoryEntry
responses:
201:
body:
application/json: InventoryEntry
/{entryId}:
get:
responses:
200:
body:
application/json: InventoryEntry
404:
delete:
responses:
200:
404:
put:
body:
application/json: InventoryEntry
responses:
200:
404:
/users/{userId}/beers:
get:
responses:
200:
body:
application/json: Beer[]
post:
body:
application/json: Beer
responses:
201:
body:
application/json: Beer
/{entryId}:
get:
responses:
200:
body:
application/json: Beer
404:
delete:
responses:
200:
404:
put:
body:
application/json: Beer
responses:
200:
404:

However, this seems repetitive. So we’ll reach into the shallow part of our RAML bag of tricks and use a resourceType to save repetition. ResourceTypes can be used like templates. They save typing.

resourceTypes:
collection:
get:
responses:
200:
body:
application/json: <<targetType>>[]
post:
body:
application/json: <<targetType>>
responses:
201:
headers:
Location: string
body:
application/json: <<targetType>> item:
get:
responses:
200:
body:
application/json: <<itemType>>
404:
delete:
responses:
200:
404:
put:
body:
application/json: <<itemType>>
responses:
200:
404:

Our resources now becomes:

/users:
type: { collection: { targetType: User } }
/{userId}:
type: { item: { itemType: User } }
/users/{userId}/inventory:
type: { collection: { targetType: InventoryEntry } }
/{entryId}:
type: { item: { itemType: InventoryEntry } }
/users/{userId}/beers:
type: { collection: { targetType: Beer } }
/{entryId}:
type: { item: { itemType: Beer } }
/users/{userId}/trades:
type: { collection: { targetType: Trade } }
/{tradeId}:
type: { item: { itemType: Trade } }

So we now have a RAML specification. From this, we’ll generate our objects and JAX-RS stubs. We’ll be using maven. To do this, all we need to set up is the appropriate maven plugin.

<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>
<generateTypesWith>
<value>jackson2</value>
</generateTypesWith>
</configuration>
</plugin>
</plugins>
</build>

We separate the generated code into three packages: the model package will contain the message objects, the resources package will contain our JAX-RS resource stubs, and finally the support package which is for somewhat internal raml-for-jaxrs usage.

The other important setup is the generateTypesWith section: here we set up plugins to the object and resource generation processes. It’s the simplest of three ways of setting up plugins. It’s also the least flexible: it applies to everything in the RAML spec. The only one we have in there for now is jackson2, which will generate Jackson 2 annotations on our objects for marshalling and unmarshalling.

Once this is done, we can go into our project directory and create the model.

Biggly:BeerTrader jpbelang$ cd model
Biggly:model jpbelang$ mvn clean install
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------------------------------------------------
[INFO] Building model 1.0-SNAPSHOT
[INFO] -------------------------------------------------------------

This will generate the code in the target/generated-sources/raml-to-jaxrs directory. Go ahead, check it out.

The implementation

We will now implement our interfaces and bridge the calls to the database. Before we get into that though, I’d like to state that I’m not going to explain much of Spring, and I’m asking you to just believe in magic when it comes to injection. It works and it makes our life simpler.

Ok, so let’s look at UsersImpl. First thing we’ll have to do is implement the stub interface. Forces us to implement something that supports the resources in our RAML specification.

import org.raml.jaxrs.beertrader.data.UserObject;
import org.raml.jaxrs.beertrader.model.User;
import org.raml.jaxrs.beertrader.model.UserImpl;
import org.raml.jaxrs.beertrader.resources.Users;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.transaction.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Component
@Transactional
public class UsersImpl implements Users {
final private EntityManager context;@Inject
public UsersImpl(EntityManager context) {
this.context = context;
}
@Override
public GetUsersResponse getUsers() {
List<UserObject> dbUsers = context
.createQuery("from UserObject ", UserObject.class)
.getResultList();
List<User> users = dbUsers
.stream()
.map(UsersImpl::userObjectToUser)
.collect(Collectors.toList());
return GetUsersResponse
.respond200WithApplicationJson(users);
}
// ...

We don’t return simple JAX-RS Responses. Returned responses must conform to what is in the RAML specification. The interface makes it simple to return the correct return codes and headers. So you should use it respectfully: don’t fool around with headers and such to break your own RAML spec.

So in this method, we fetch users from the database, transform them into transfer objects and return them with a RAML safe response.

A quick simple run

We can run the server in one of two ways: running from the IDE or from the command line. Either way, we will need to run the main class (in server/src/main/java/org/raml/jaxrs/beertrader/Main.java). To run from maven, from the server directory:

java -jar target/server-1.0-SNAPSHOT.jar

You should also be able to run it from your IDE.

POST /services/users HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cache-Control: no-cache
{
"name": "JP Belanger",
"email": "jp@fun.com"
}

returning the response:

HTTP/1.1 201 Created
Content-Length: 43
Content-Type: application/json
Date: Sun, 27 May 2018 18:00:13 GMT
Location: http://localhost:8080/services/users/084586f3-3f22-4248-901a-522da0252fda
{
"name": "JP Belanger",
"email": "jp@fun.com"
}

So that’s it: this is a simple version of a currently simple service. We will be adding to it shortly.

--

--