Building a RESTful API with Java and Spring Framework — Part 1

Image for post
Image for post

Nowadays, it is more popular to have online applications in all environments. These applications aim to consume information through interfaces with a series of routines and standards. We named these types of applications of API.

The API acronym comes from Application Programming Interface. It is a set of documented standards and rules so that an application X can use another application Y features without knowing the application X implementation details.

Is it still confusing? To a better understanding, let’s imagine a day in a company that has an e-commerce website. The developers are working on the solution based on the store’s dynamics, such as creating, updating, deleting products internally, and showing the products to the customers online. These resources can be developed in a server-side application as if it were an API. Then, the e-commerce website and the company’s other applications can use this information.

Now that we already know what an API is and where it can be used, we need to understand how everything works. The next section presents the protocols used for communication between applications and how the data can be represented.

HTTP, REST, and data representations in APIs

The primary communication protocol on the Web is HTTP. It works like a request-response protocol in a model we call a client-server. In our e-commerce example, the browser used to access the website would be the client — the computer or virtual machine in some cloud service where the server host the API. The client sends an HTTP request to the server, and the server, with its resources and content, returns a response message to the client.

Image for post
Image for post
Example of how the HTTP protocol works

The HTTP protocol has been used since 1990, and your current version is described in this draft is the HTTP/3. Eight methods are defined that determine actions be performed when requesting a resource from the server. Of these eight, the four most used are:

  • GET: the method used to read and retrieve data. Request a representation of the specified resource and return that representation.
  • POST: the method used to create a new resource and sends data to the server. The Content-Type in the header indicates the type of the request body.
  • PUT: creates a new resource or replaces a representation of the target resource with the latest data. PUT, and POST, the difference is that it is idempotent. When calling it one or more times repeatedly, the effect is the same while getting POST can have additional consequences. For example, if we create a product with POST, if the URL defined in the API is called 20 times, we will create 20 items, and each of these items will have a different ID. In the PUT method, if you execute 20 times the URL defined in the API, the result has to be the same: the same item updated 20 times.
  • DELETE: deletes the resource.

Based on these methods, the server must process each request and return an appropriate response. The response format can be XML, JSON, YAML, text, among others. Plus, these responses are separated into five groups:

  • 1XX — General information
  • 2XX — Success
  • 3XX — Redirect
  • 4XX — Client error
  • 5XX — Server error

But isn’t that REST? No, it’s not. REST, an acronym for Representational State Transfer, is an abstraction of the architecture detail above. It is a software architecture style that defines a series of restrictions for creating web services. That is, it restricts how its components interact. This term was introduced and explained by Roy Fielding in his doctoral thesis in the late 90s and early 2000s.

In the thesis, Fielding defined the REST principles known as the HTTP object model. It started to be used in the design of HTTP 1.1 and URI (Uniform Resource Identifiers) standards. Thus, we can say that in its semantics, REST uses HTTP methods. Besides, it is also worth remembering that a REST service must be Stateless: every request must be self-sufficient. That is, each request is a different and independent request. There must be no way in the request to save the information’s status.

So, what does it mean to be RESTful? An API is RESTful if we guarantee that the implementation is following this REST architecture. Conceptually, in RESTful, both data and functionality are considered resources accessible to customers URIs. These URIs are usually web addresses to identify the server on which the application is hosted. Furthermore, it represents the application itself and which of its resources are being requested.

That way, exposing your API (or your API services, if you prefer) in RESTful means that the REST principles and their restrictions apply to you. After laying our cards on the table, let’s get our hands dirty! Besides the use of Java (version 11) with Spring Framework as the API basis, we will use as tools:

  • Apache Maven (for dependencies management)
  • Postman (for the execution of API tests and requests in general)
  • JUnit5 (for unit and integration tests)
  • Lombok (to reduce boilerplate code)
  • Log4j (for application logging)
  • TravisCI (for continuous integration)

All the code produced in this article, a simplified API version, can be accessed here:

Suppose you want to skip the tutorial. The final code, an optimized version, applying best practices, can be accessed in this repository on Github:

Building the API

The API should create, update, delete, and list financial transactions. Also, it must calculate statistics on the transactions created. Note: at this point, we will not deal with the persistence layer. So, technically, the Transaction entity will contain an explicit field called id. The API will have the following endpoints:

POST/transaction: creates a transaction.

Request Body:

{   
"id": 1,
"nsu": "220788",
"authorizationNumber": "010203",
"amount": "22.88",
"transactionDate": "2019–09–11T09:59:51.312Z",
"type": "CARD"
}

Where:

  • id: unique transaction number.
  • nsu: identification number of a credit card transaction. It can be void if the transaction is paid in cash.
  • authorizationNumber: is a unique code for online transactions.
  • amount: transaction amount; it must be a string of arbitrary length that can be parsed as a BigDecimal.
  • transactionDate: transaction date in ISO 8601 format YYYY-MM-DDThh: mm: ss.sssZ. We considered the local timezone.
  • type: whether the transaction is in card (CARD) or cash (MONEY).

You must return with an empty body with one of the following codes:

  • 201: if successfully created.
  • 400: if the request JSON is invalid.
  • 422: if any of the request fields are not parsable or the transaction date is in the future.

PUT/transaction /{id}: update a transaction.

Request Body:

{   
"id": 1,
"nsu": "220788",
"authorizationNumber": "010203",
"amount": "30.06",
"transactionDate": "2019–09–11T09:59:51.312Z",
"type": "CARD"
}

The object to be modified must be sent. The return must be the object itself.

{   
"id": 1,
"nsu": "220788",
"authorizationNumber": "010203",
"amount": "30.06",
"transactionDate": "2019–09–11T09:59:51.312Z",
"type": "CARD"
}

The response must contain the following codes:

  • 200: if successfully updated.
  • 400: if the request JSON is invalid.
  • 404: if you try to update a record that doesn’t exist.
  • 422: if any of the fields is not parsable (JSON poorly formatted).

GET/transactions: returns all created transactions.

A list of transactions must be returned.

{   
"id": 1,
"nsu": "220788",
"authorizationNumber": "010203",
"amount": "30.06",
"transactionDate": "2019–09–11T09:59:51.312Z",
"type": "CARD"
},
{
"id": 2,
"nsu": "300691",
"authorizationNumber": "040506",
"amount": "120.0",
"transactionDate": "2019–09–11T10:22:30.312Z",
"type": "CARD"
}

The response must contain the following codes:

  • 200: if there are registered transactions.
  • 404: if there are no created transactions.

DELETE/transactions: remove all transactions.

You must accept a request with an empty body and return 204.

GET/statistics: returns basic statistics about the transactions created.

{   
"sum": "150.06",
"avg": "75.3",
"max": "120.0",
"min": "30.06",
"count": "2"
}

Where:

  • sum: a BigDecimal with the total of the transactions.
  • avg: a BigDecimal with the average of the values of the transactions.
  • max: a BigDecimal with the highest value among the created transactions.
  • min: a BigDecimal with the lowest value among the transactions.
  • count: a Long with of the total number of transactions.

All fields that are BigDecimal must have only two decimal places. E.g., 15.385 must be returned as 15.39.

Once we detailed the features that we need to implement, let’s work!

1) The first step is to create a Spring Boot project in Spring Initializr.

Image for post
Image for post
Example of creating a Spring Boot project by Spring Initializr

As dependencies, we will select Spring Web (Spring MVC) and Lombok. Spring MVC is a framework that helps develop web applications in the MVC standard (model-view-controller). Lombok is a Java library focused on productivity and code reduction through annotations that teach the compiler how to create and manipulate Java code. That is, you will no longer need to write methods such as getter, setter, equals, hashCode, and constructors.

2) With the project created, we will create the model, controller, and service packages.

Image for post
Image for post
Example of creating project packages

Next, we will create the models: Transaction and Statistic.

Example of the Transaction class of the model package
Example of the Statistic class of the model package

3) With the models created, we will add the API services layer: TransactionService and StatisticService.

The class TransactionService must contain seven methods to meet the lifecycle requirements of this service in the API:

  • isJSONValid: to verify that the JSON is valid.
  • parseId: to parse the JSON id field.
  • parseAmount: to parse the amount field of JSON.
  • parseTransactionDate: to parse the JSON transactionDate field.
  • isTransactionInFuture: to verify that the transaction is being created in the future.
  • create: to create a transaction.
  • update: to update a transaction.
  • add: to add a transaction to the list.
  • findById: to retrieve a transaction by id.
  • find: to retrieve all created transactions.
  • delete: remove transactions.

The class StatisticService should contain only the method for creating API statistics: createStatistics.

4) Next step, we will create the routes in the controllers: TransactionController (for routes related to transactions) and StatisticController (for routes related to statistics).

The TransactionController should contain all the operations detailed above: create a transaction (POST), update a transaction (PUT), list all transactions (GET), and remove all transactions (DELETE).

Once the routes are created, let’s go up the API with the command:

mvn spring-boot:run

It is also possible to run the API through your IDE as a Java Application. The result of both commands should be similar to the figure below.

Image for post
Image for post
Spring Boot initializing the application

By default, the API will be available at http://localhost:8080/. With the API working, we can now test the routes. For the initial tests, I used Postman and, as a reference, the examples showed in the section Building the API.

POST/transaction via Postman

Image for post
Image for post
Example of a POST done via Postman

PUT/transaction/1 via Postman

Image for post
Image for post
Example of a PUT done via Postman

GET/transactions via Postman

Image for post
Image for post
Example of a GET done via Postman

DELETE/transactions via Postman

Image for post
Image for post
Example of a DELETE done via Postman

Also, the StatisticController should contain the detailed statistics operation mentioned at the beginning of the section.

Statistics controller example

GET/statistics via Postman

Image for post
Image for post
Example of a GET on the statistics route, done via Postman

Performing unit and integration tests

How to guarantee quality in the API routes? In this tutorial, I used JUnit5 to create unit tests and Spring Boot’s TestRestTemplate to create integration tests. For both tests running correctly, we need to add some plugins to our: maven-failsafe-plugin and build-helper-maven-plugin. The configuration should be the same as below:

<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<trimStackTrace>false</trimStackTrace>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId
<executions>
<execution>
<id>add-integration-test-sources</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/it/java</source>
</sources>
</configuration>
</execution>
<execution>
<id>add-integration-test-resources</id>
<phase>generate-test-resources</phase>
<goals>
<goal>add-test-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/it/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>

Also, we will create two packages: src/test/java for unit testing. The goal here is to validate the business logic implemented in services and controllers; and src/it/java with the FinancialJavaApiIntegrationTest class for integration testing. The goal here is to validate the integration and flow of the API routes.

Example of the FinancialJavaApiUnitTests unit test class
FinancialJavaApiIntegrationTest integration test class example

To perform only unit tests, use the command:

mvn test

To perform both tests, use the command:

mvn integration-test

The result should be similar to below:

Image for post
Image for post
Example of tests execution with Maven

The last step is to create continuous integration with the tests ready. That means to make the API’s evolution and quality regularly monitored, maintaining integration with the code developed. In the next section, we will use TravisCI to create this behavior in our ecosystem.

Configuring Continuous Integration with TravisCI

The first step to enable the continuous integration workflow with TravisCI is to create a .travis.yml file, in the project’s root path, with the following configuration:

In this file, we need to configure the virtual machine that will boot our app. The main configurations are:

  • SO distribution: we use Linux, Ubuntu (14.04).
  • Language: Java
  • Sudo: option to run with (true) or without (false) root permission
  • JDK or Java version: we use Java/JDK 11.
  • Git: how to download the source code. For example, the number of commits considered (up to 2 commits).
  • Script: how to perform the package installation and the tests.

Alongside, you need to enable the API flow in the TravisCI dashboard:

Image for post
Image for post

Guaranteed that everything is correct, as soon as the project has an update, the main branch’s build is triggered automatically.

Image for post
Image for post

The expected result is that the build is successful.

Image for post
Image for post

By checking the build log, we can see that the branch was built and the tests performed successfully.

Image for post
Image for post

Finally, our API is ready and validated!

The topic addressed here was an introduction to Java and Spring. If you have improvements to make, please fork the finance-api project on Github and code!

At the end of the series, the code will have best practices recommended in developing an API, such as versioning, HATEOAS, Spring Error Handling, and other topics. Hence, If you are ready for these topics, go ahead and look at the financial-java-api project and enjoy it! If you have any further questions, please, contact me.

I hope you enjoyed it! See you in the next post!

Written by

Senior Software Developer/Tech Lead, master in Computer Science/Software Engineering, Java, open source, and software quality enthusiast.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store