Development of Restful API with Java can become straightforward when following best practices, leveraging compiler checks and code consistency.
Here is an example of an application which follows Java API developmnet best practices with Spring Boot and Docker.
Run the application
The source code is freely available on github: https://github.com/melphi/java-api-best-practices. You can run the application from Maven, from Docker or as Java executable. To build the application you will need Maven and Java8.
To run from maven:
mvn spring-boot:run
To run with docker:
sh build-docker.sh sudo docker run java-api-guidelines
To run as java standalone executable:
mvn package java -jar target/java-api-guidelines.jar
After few seconds the application should be ready, for example http://localhost:8080/health returns the health status.
The code can be freely customised with your implementation, just double check all the TODO to see the parts which need some configuration.
Folders structure
The project structure follows these guidelines:
java-api-best-practices |-- pom.xml |-- Dockerfile |-- src/main/java/net/dainco |-- container |-- configuration |-- filter |-- WebApplication.java |-- module |-- common |-- abc |-- exception |-- domain |-- dao |-- service |-- rest |-- util
Packages
Container
The application container configuration, for example the startup class, security and persistence configuration, web filters, etc. In the example the WebApplication.java is the entry point which starts the application.
Module
Application is split in modules, think of a module of as a separated domain. For example user module contains all the classes related to user operations.
When another module, for example company module, needs to comunicate with the user module it will use the UserService interface. Ideally the application should be designed in a way in which modules can be split in separated applications following microservice principles.
A common module contains base classes used by other modules.
Domain
The domain contains interfaces, dto and entities.
Dto, which stands for Data Transfer Object, are object returned by the service layer and serialised to Json.
Entities are the object to database mapping, which can use for example Hibernate or Spring Data annotations.
Interfaces are used to enforce compile time checks while converting Entities to Dto. This way if a new field is introduced the compiler can be used to check that all the related objects contain the required fields.
A best practice is to structure the Dto as an immutable object as follow:
public class UserPrivateDto implements UserPrivate { private String id; private String email; private String fullName; private String phoneNumber; public UserPrivateDto() { // Intentionally empty. } public UserPrivateDto(UserPrivate user) { this(user.getId(), user.getEmail(), user.getFullName(), user.getPhoneNumber()); } public UserPrivateDto(String id, String email, String fullName, String phoneNumber) { this.id = id; this.email = email; this.fullName = fullName; this.phoneNumber = phoneNumber; } @Override public String getId() { return id; } @Override public String getFullName() { return this.fullName; } @Override public String getPhoneNumber() { return this.phoneNumber; } @Override public String getEmail() { return this.email; } }
We can have a version of Dto for public data and for private data or compose various domain objects in one in order to produce the required JSON output.
Dao
Dao stands for Data Access Object and contains the interfaces to get and store objects in the database. It returns entitiy objects but not dto, which are returned by the service layer. The Dao depends on the persistence implemetation used, for example Hibernate or MongoDB.
In Spring Boot a Dao could be implemented following this guideline.
@Component public class UserDaoImpl extends AbstractDao<UserEntity> implements UserDao { @Autowired public UserDaoImpl(MongoTemplate mongoTemplate) { super(mongoTemplate); }
@Override public UserPrivate createUser(UserRegistration userRegistration) { // TODO: Implement this method. } }
Service
Service classes implement all the the business logic. They connect to the Dao layer via dao interfaces. Also they return Dto object which will be serialised from the rest layer as Json object.
This is an example wich follows some best API practices:
@Component public class UserServiceImpl implements UserService { private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class); private UserDao userDao; @Autowired public UserServiceImpl(UserDao userDao) { this.userDao = userDao; } @Override public UserRegistrationResponseDto registerUser(UserRegistrationDto userRegistrationDto) throws UserRegistrationException { validateUserRegistration(userRegistrationDto); try { userDao.createUser(userRegistrationDto); return new UserRegistrationResponseDto(); } catch (Exception e) { throw new UserRegistrationException(e.getMessage(), e); } } }
Rest
Rest layer contains the REST API controllers. They link to the service layer via service interfaces. A rest layer should never implement any business logic, but just the endpoint mapping, request parameters and security.
@Api(tags = "users") @RestController @RequestMapping(value = "/users") public class UserController { private UserService userService; @Autowired public UserController(UserService userService) { this.userService = userService; } @ApiOperation(value = "Registers a new user.", tags = "users") @RequestMapping(path = "/register", method = RequestMethod.POST) public UserRegistrationResponseDto registerUser( @RequestBody UserRegistrationDto userRegistrationDto) throws UserRegistrationException { return userService.registerUser(userRegistrationDto); } }
Exception and Util
It is a good practice to use your own custom exceptions, which can be defined in the exception package.
Also exception messages should be encoded with a prefix, for example ERROR_, this way the user interface can translate all the messages starting with ERROR_. The error messages and other utility classes can be stored in the util package.
Conculsion
The source code with Java API best practices can be found at https://github.com/melphi/java-api-best-practices and can be freely extended.
You can just follow the guidelines of the skeleton application and complete the TODO tasks. Some missin parts are:
- A full security implementation, for example OAuth2
- Persistence layer, for example Spring Data with Mongo or a RBMS with Hibernate