Generating REST APIs

The development of REST APIs is at the same time difficult and boring. It is difficult because the correct implementation of the principles of the REST architecture paradigm, especially the Hypermedia principle, requires a lot of experience and good planning. It is tedious because, despite all differences in the concrete REST APIs, the technical implementation is very similar and not really challenging with the help of frameworks and libraries over presentation, logic and persistence layers, if you have done it a few times.

The idea is close to saving the effort for the development and reverting to an idea that has existed for some time in software engineering: model-driven software development w3jothc. The idea is to describe the software to be developed in a model that is at a higher abstraction level than, for example, a Java source code. From this abstract model, specific source code is generated by means of a software generator, for example, in the programming language Java.

The principle is similar to a compiler that translates a high-level language (at a high level of abstraction) into assembler (at a lower level). However, in the case of model-driven software development, we may leave the so-called Turing completeness in programming languages, ie the possibility of describing all possible algorithms. And that is also useful, because we just want to describe in a model not everything, but only a certain application domain. Because of this restriction, such special modeling languages are also called Domain Specific Languages (DSL).

The domain we should look at now are applications that can be described with the REST architectural style. The aim of our work is the provision of a DSL for the compact description of such applications. What are the basic building blocks of a REST application? First, let’s talk about resources that contain attributes and can be retrieved via URIs. Then we talk about the behavior at the URI endpoints, which we call states. We must define which HTTP verbs can be used at the various endpoints and whether, for example, an authentication is necessary. Finally, we talk about the hyperlinks with which the server guides the clients of the API through the states of the applications.

For the representation of a DSL there are again different approaches. You can define a new formal language using a grammar and then translate this new language into a concrete programming language like Java using tools such as ANTLR or xText. Or you define a graphical modeling possibility, comparable to a UML editor, from which source code is generated. Or you can use XML, JSON, or YAML as a structured description. We have chosen an approach in which we define a DSL as the Fluent API in Java.

For a better understanding, here is a section for defining an API for managing important appointments. First we need to define some meta-information:

1
2
3
4
5
6
RestApi.create( )
  .producer( "mycompany" )
  .projectName( "importantdates" )
  .contextPath( "dates" )
  .generateInPackageWithPrefix( "com.example.importantdates" )
  .usingDatabase( "importantdates" ).atHost( "localhost" ).usingUsername( "login" ).andPassword( "password" ).done()

The context path is the first path to the URIs. If at the end the API is deployed on a server www.example.org, then the API is accessible under the URI https://www.example.org/dates. The last line defines the dates in a MySQL database. Next we define the resource Date:

1
2
3
4
5
6
7
.defineResourceWithName( "Date" )
  .withSimpleAttribute( "title" ).asString( )
  .withSimpleAttribute( "description" ).asString( )
  .withSimpleAttribute( "duedate" ).asDate( )
  .allAttributesDefined()
  .andCachingByValidatingEtags()
  .allResourcesDefined()

This resource has three attributes, and the validation method with etags is to be used as the caching method for this resource. Alternatively, at this point, we could also specify that a validation date should be used, or a Expires header should be set. Next, we define the endpoints of the API that we call states in our model:

1
2
3
4
.states()
  .dispatcherState()
    .named( "StartState" )
    .grantToEverybody().done()

At the beginning, we define an entry point that the clients can query to obtain additional hyperlinks. The URI is determined by the hostname and the context path. For internal use, each state requires a name. With grantToEverybody , we specify that you can call this state without authentication. For the entry point, of course, the HTTP verb GET is predefined.

Next, we define a state that allows querying a collection resource:

1
2
3
4
5
6
7
8
9
10
11
12
13
.primaryGetCollection()
  .named( "GetAllDates" )
  .withResource( "Date" )
  .grantToEverybody()
  .withQuery()
    .name( "SelectByTitle" )
    .queryTemplate( "{model.title} like {query.t.concat('%')}" )
    .queryParameters()
      .named( "t" ).withDefaultValue( "" ).asString()
    .doneWithQuery()
    .offsetSize()
      .offsetParameterIsNamed( "offset" ).withDefaultValue( 0 )
      .sizeParameterIsNamed( "size" ).withDefaultValue( 10 )

This collection of Date resources provides a filter that can handle a URL parameter named t. Using the queryTemplate method, we define the query for the database, that is, the title attribute of the resource in the database t Of the URL (query), where we still append the percentage as a placeholder to this parameter. For the page-driven querying of results, we define a paging mechanism with the parameters offset and size with corresponding default values. The generator then ensures that, during queries, corresponding hyperlinks to the previous and subsequent page are also included in the HTTP response message, if available.

1
2
3
4
.primaryGetSingle()
  .named("GetOneDate")
  .withResource( "Date" )
  .done()

Here we define a simple state for accessing a single resource by specifying an id. Finally, an example of a state for generating a new resource using HTTP verb POST. Here we define a necessary authentication with grantToGroups.

1
2
3
4
5
.primaryPost()
  .named( "CreateDate" )
  .withResource( "Date" )
  .grantToGroups( "admin" )
  .done()

Finally, we must define the possible transitions between the states. For example, at this point, we only show the transition from the entry point to the state for querying all appointments.

1
2
3
4
5
.transitions()
  .fromState( "StartState" )
    .asHeaderLink()
    .toState( "GetAllDates" )
    .usingRelationType( "getAllDates" )

Thus, when the client queries the entry point with a GET message, he receives a hyperlink to the URI to query the collection resource, specifying all possible URL parameters, in the header of the response message. This allows the client to easily create and submit the new request by simply exchanging the parameters in the URI. Similarly, we have to provide corresponding data for all further transitions.

From this model, a generator generates Java source code for the presentation layer that represents the actual REST API, the business logic for processing the individual queries, and the database layer with the CRUD operations for MySQL. It also generates database scripts and POM files for Maven, which can immediately deploy the project to a Tomcat container without further manual changes. According to the experience of the last few months, there is an enormous time-saving in the use of such a model-driven approach. Accurate examinations are difficult, nor have we made a comparison between a completely manual development of such an API and our approach, but such an API as above (of course, complete with all states and transitions) includes less than 100 lines of source code, Write down 30 minutes. That would probably not even experienced developer even only approximately in this time create.