How to build a full-fledged backend architecture in Java
The art of developing high-quality software has been in constant evolution. New frameworks, libraries, tools, IDEs and even
programming languages have come to existence, and old languages have become
very robust and have expanded its reach to satisfy the needs of large corporations. This is the case of Java, which is nowadays the 5th most used programming language in the world.
Such advancements in technology have made the
process of building software more complex; but to some extent, easier. The wide
variety of available frameworks and libraries out there has greatly facilitated
the job of developers. Nonetheless, devs still need to know what library works
best, which pattern design to use in every situation, how to write efficient
and clean code and -more importantly- what architecture standards to keep.
Therein lies the importance of architecture as
one of the ruling factors of efficiency and cleanliness of the code. The
selection of the appropriate architecture might lead to fewer bugs and less
work of maintenance in the future.
In this article, you’ll learn how to build a
full-fledged backend architecture that supports corporate systems.
Requirements
To build for this project you’ll need:
ü General knowledge about SOA
ü General knowledge about unit testing
in Java
ü Familiar with annotations in Java
ü Knowledge about design patterns
ü Advanced knowledge of Spring,
including CDI
ü Advanced knowledge of Spring MVC
ü Advanced knowledge of Maven
Also, the following tools:
ü IDE of your choice (Intellij IDEA or
Eclipse)
ü Java 8
The task
As the focus of this article is to establish a proper architecture for our software, we’ll rather vote for a simple task. Let’s say:
“Build an API to validate the existence of a user
based on its username. The username will be an alphanumeric string and the API
will be called via REST from frontend. The information of the users is not
stored in our databases, but in the database of one of our clients, who exposes
an API to get users, based on its username. The API Blueprint for the external
API (client’s API) and our API are shown in the table below.”
NOTE: (1) If you are not familiarized with API
Blueprint, I’d suggest you take a look here. (2) We’ll disregard the
authentication to our API for now.
Our API
## Validate the
existence of a user [/users/exists/{username}]
### validateUserExistence
[GET]
+ Parameters
+ username (string)
+ Request
(application/json;charset=UTF-8)
+ Response 404 – NOT
FOUND
+ Response 302 FOUND
|
External API
## Get users’ info [<CLIENT_URL>/user/get/{username}]
### getUser [GET]
+ Parameters
+ username (string)
+ Request
(application/json;charset=UTF-8)
+ Response 200
+ Attributes (UserInfo)
|
Also, UserInfo contains the following fields:
ü Id (String)
ü Name (String)
ü Surname (String)
ü DateOfBirth (Date)
ü Username (String)
Analysis
Before
proceeding, I’ll strongly suggest you to try solving this exercise by yourself
and see if you came up with a similar solution. Remember to consider the
different service layers that should be involved in the solution.
Now let’s get on with the analysis. First, we
need to receive REST requests from the frontend. Therefore, we need a controller to handle these requests.
Second, we’ll need a service bean with its respective interface to handle the
business logic related to Users. We also need to consume the API exposed by our
client, so we need another layer that will serve as client for external
services.
Our architecture will look as follows:
Code
EDIT: You can now download the code from GitHub!
Let’s start
by creating the Web Service Layer. For this, we’ll need a Controller that
accepts REST requests, a function to handle such requests and the appropriate
mapping to our function.
1. The Rest Controller is the easiest
part. We’ll use the Spring annotation @RestController to capture REST requests.
2. According to the API Blueprint, our
function should return either 404 or 302 Http Status. To facilitate this, we’ll
use the object ResponseEntity, from Spring. Our function will call a service in
the service layer and return the HTTP Status accordingly.
3. The mapping for the function is
given by the API Blueprint. It should be: /users/exists/{username}
From step 2
we see that there’s a dependency on the service layer. We need to create an
interface that allows us to access the service. Our interface will contain a
single method which will accept a String and return a boolean to identify if
the user exists. It will look as follows:
UsersService.java
public interface UsersService {
boolean userExists(String username);
}
boolean userExists(String username);
}
And our controller:
UsersController.java
@RestController
@RequestMapping(value = "/users", consumes = APPLICATION_JSON_UTF8_VALUE, produces = APPLICATION_JSON_UTF8_VALUE)
@Validated
public class UsersController {
private final UsersService usersService;
@Autowired
public UsersController(UsersService usersService) {
this.usersService = usersService;
}
@GetMapping("/exists/{username}")
public ResponseEntity validateUserExistence(@NotNull @PathVariable(value = "username") String username) {
return (usersService.userExists(username)) ? new ResponseEntity(HttpStatus.NOT_FOUND) :
new ResponseEntity(HttpStatus.FOUND);
}
}
@RequestMapping(value = "/users", consumes = APPLICATION_JSON_UTF8_VALUE, produces = APPLICATION_JSON_UTF8_VALUE)
@Validated
public class UsersController {
private final UsersService usersService;
@Autowired
public UsersController(UsersService usersService) {
this.usersService = usersService;
}
@GetMapping("/exists/{username}")
public ResponseEntity validateUserExistence(@NotNull @PathVariable(value = "username") String username) {
return (usersService.userExists(username)) ? new ResponseEntity(HttpStatus.NOT_FOUND) :
new ResponseEntity(HttpStatus.FOUND);
}
}
We have the first part of the architecture.
Now, we need to implement the business logic behind the interface and call the client
that gets the user from the external services. An architectural question arises:
How should this client look like? What library should we use to call these
external services?
Let’s keep it simple. We’ll use Feign,
which “is a declarative web service client”. Feign allows us to create web
service clients by creating an interface, mapping it accordingly, and forming
the body of the request and response by adding the respective entities.
We know-how
the response of the Feign client will look like. It is described by UserInfo in
the task’s statement. Therefore, we can easily proceed with its creation. As it
is the response of Feign, let’s name it: UserFeignClientResponse
UserFeignClientResponse.java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserFeignClientResponse {
String id;
String name;
String surname;
Date dateOfBirth;
String username;
}
@AllArgsConstructor
@NoArgsConstructor
public class UserFeignClientResponse {
String id;
String name;
String surname;
Date dateOfBirth;
String username;
}
Note that
we are using Lombok to facilitate our work.
The Feign client will look as follows:
UsersFeignClient.java
@FeignClient(name = "usersFeignClient", url = "${usersFeignClient.address}")
@RequestMapping(consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public interface UsersFeignClient {
@GetMapping("/user/get/{username}")
@Nullable UserFeignClientResponse getUser(@NotNull @PathVariable(value = "username") String username);
}
@RequestMapping(consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public interface UsersFeignClient {
@GetMapping("/user/get/{username}")
@Nullable UserFeignClientResponse getUser(@NotNull @PathVariable(value = "username") String username);
}
From the API
Blueprint, we can see that the Client’s API endpoint is not specified. It says:
<CLIENT_URL>. Thus, we need to make this URL configurable. We add the
entry usersFeignClient.address to our application.properties file. For the
scope of this tutorial, we’ll set this property to localhost with the port 8000.
Application.properties
usersFeignClient.address = http://localhost:8000
Last, let’s call the feign client from the implementation of UsersService:
UsersServiceImpl.java
@Service
public class UsersServiceImpl implements UsersService {
UsersFeignClient usersFeignClient;
@Autowired
public UsersServiceImpl(UsersFeignClient usersFeignClient) {
this.usersFeignClient = usersFeignClient;
}
public boolean userExists(String username) {
return (usersFeignClient.getUser(username) == null) ? true : false;
}
}
public class UsersServiceImpl implements UsersService {
UsersFeignClient usersFeignClient;
@Autowired
public UsersServiceImpl(UsersFeignClient usersFeignClient) {
this.usersFeignClient = usersFeignClient;
}
public boolean userExists(String username) {
return (usersFeignClient.getUser(username) == null) ? true : false;
}
}
Checklist
It seems that we have considered all the
requirements of the task as well as the aspects of the architecture. Let’s do a
quick review:
ü Web Service Layer: We provided an endpoint for frontend to communicate with us through the
use of a Rest Controller.
ü Service Layer – Internal Services: The service layer is decoupled from the
business logic through the use of the interface UsersService.
ü Business Logic Layer – Implemented by UsersServiceImpl. Calls the external client.
ü Service Layer – External services:
Represented by the FeignClient.
Also:
ü The API we just built as well as the
call of the external service is supported by REST, in accordance with the API
Blueprint.
Now you’ve learned how to build a full-fledged
backend architecture for your APIs. Now, what’s left? Exactly! We need to test
if the API and the underlying architecture works.
EDIT: The tutorial on how to test the architecture is now out!!
Comments
Post a Comment