DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Enterprise AI Trend Report: Gain insights on ethical AI, MLOps, generative AI, large language models, and much more.

2024 Cloud survey: Share your insights on microservices, containers, K8s, CI/CD, and DevOps (+ enter a $750 raffle!) for our Trend Reports.

PostgreSQL: Learn about the open-source RDBMS' advanced capabilities, core components, common commands and functions, and general DBA tasks.

AI Automation Essentials. Check out the latest Refcard on all things AI automation, including model training, data security, and more.

Related

  • Building Mancala Game in Microservices Using Spring Boot (Part 2: Mancala API Implementation)
  • Cron Jobs vs. WatchService
  • AI in Java: Building a ChatGPT Clone With Spring Boot and LangChain
  • Using Spring Cloud Gateway and Discovery Service for Seamless Request Routing

Trending

  • Securing Cloud Storage Access: Approach to Limiting Document Access Attempts
  • Maximizing Developer Efficiency and Productivity in 2024: A Personal Toolkit
  • Exploring the Frontiers of AI: The Emergence of LLM-4 Architectures
  • Modern Python: Patterns, Features, and Strategies for Writing Efficient Code (Part 1)
  1. DZone
  2. Data Engineering
  3. Databases
  4. Implementation Best Practices: Microservice API With Spring Boot

Implementation Best Practices: Microservice API With Spring Boot

In this article, I share some useful tips for implementing Microservices API with Java and Spring Boot, along with a sample code to refer to.

By 
Amritendu De user avatar
Amritendu De
·
Feb. 12, 24 · Tutorial
Like (6)
Save
Tweet
Share
5.9K Views

Join the DZone community and get the full member experience.

Join For Free

Technical Architecture

First, let's turn to the architecture, which will be explained in detail. Let's look at each of these tiers in detail. 

Technical Architecture

Let me explain the architecture in detail. These components are commonly associated with the architecture of applications that follow the principles of Domain-Driven Design (DDD) and Model-View-Controller (MVC) or similar architectural patterns. Let me cover this one by one:

Entities

Entities represent the core business objects or concepts in your application. They encapsulate data related to the business domain. For example, in an Employee Management System, an employee entity might have attributes like name, email, and salary related to an employee.

Repositories

Repositories are responsible for handling the data access logic. They provide an abstraction over the data storage, allowing the application to interact with the data without worrying about the underlying storage details. For example, an EmployeeRepository would handle operations like storing, retrieving, updating, and deleting employee records in the database.

Services

Services contain business logic that doesn't naturally fit within the methods of an entity. They orchestrate interactions between entities and repositories to fulfill higher-level use cases. For example, an EmployeeService might have methods to calculate bonuses, process employee transfers, or handle complex business rules involving multiple entities.

Mappers

Mappers are responsible for transforming data between different layers of the application. They convert data from database entities to domain objects and vice versa. For example, an EmployeeMapper might convert an Employee entity to a data transfer object (EmployeeRequest) that can be sent over the network or used by the presentation layer.

Controllers

Controllers handle incoming requests from the user interface or external systems. They interpret user input, invoke the necessary services or business logic, and prepare the response to be sent back. In a web application, a controller receives an HTTP request, extracts the necessary data, and delegates the request to the appropriate service. It then formats the service response and sends it back to the client.

Frontend: You have the option of building Native Apps like Android and iOS. Desktop browser apps or mobile browser apps can be built using React or Angular frameworks. 

Best Practices for Implementation of the Architecture

Entity: Useful Tips

  • Name the package as “entities” under the feature name
  • Set id as Long and generation type as identity
  • Name the class and table in plurals like users
  • Use Lombok for constructor and getter/setter code
  • Have a length for every String field
  • Set nullable to either true/false
  • Use references to other tables like @ManyToOne. Remember, the table created is automatic, and what you write in the entity matters.
  • Use @OneToMany bidirectional if you wish to save the values in multiple tables in one call.
  • Use @ManyToMany to join tables. Create a separate Join class if there are fields in the join table apart from join id columns.
  • Identify the right inheritance type for is-a relationship. Pick between single table, class table, and concrete table inheritance based on the number of fields in every class.  

Example

Java
 
package org.project.feature.entities;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "users")
public class Users {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 100, nullable = false)
private String firstName;
@Column(length = 100, nullable = false)
private String lastName;
@Column(length = 100, nullable = false, unique = true)
private String email;
 }


Repository: Useful Tips

  • Name the package as “repositories” under the feature name
  • Extend the JpaRepository with the entity name and id as Long
  • As much as possible, use the style as a method for querying the entity like findByEmail
  • Use batch operations for multiple entries to the database, like saveAll
  • Use Optional for the return type as much as possible

Example

Java
 
package org.project.feature.repositories;
 
 import org.project.feature.entities.Users;
 import org.springframework.data.jpa.repository.JpaRepository;
 
 public interface UsersRepository extends JpaRepository<Users, Long> {
 
 }


Service: Useful Tips

  • Name the package as “services” under the feature name
  • Create an interface for all the operations within the service and create an implementation class
  • Use @AllArgsConstructor for @Autowired annotation
  • Accept the Request object and return the Response object from the model’s package.
  • If multiple repositories need to be called, it should be called in a transaction unless you wish to start a new transaction.
  • If you wish to call multiple services, the particular service has to be named as an aggregate service and within a transaction.
  • Do not return the ResponseEntity from the service; it is the job of the controller tier.

Example

Java
 
package org.project.feature.services;
 
 import org.project.feature.models.UsersRequest;
 import org.project.feature.models.UsersResponse;
 
 import java.util.List;
 
 public interface UsersService {
     UsersResponse createUser(UsersRequest usersRequest);
 
     UsersResponse getUserById(Long userId);
 
     List<UsersResponse> getAllUsers();
 
     UsersResponse updateUser(Long id, UsersRequest users);
 
     void deleteUser(Long userId);
 }

 

Java
 
package org.project.feature.services.impl;
 
 import lombok.AllArgsConstructor;
 import org.project.feature.entities.Users;
 import org.project.feature.mappers.UsersMapper;
 import org.project.feature.models.UsersRequest;
 import org.project.feature.models.UsersResponse;
 import org.project.feature.repositories.UsersRepository;
 import org.project.feature.services.UsersService;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
 
 @Service
 @AllArgsConstructor
 public class UsersServiceImpl implements UsersService {
 
     private final UsersRepository usersRepository;
 
     private final UsersMapper usersMapper;
     @Override
     public UsersResponse createUser(UsersRequest usersRequest) {
         Users users = usersMapper.convertRequestToEntity(usersRequest);
         Users saved = usersRepository.save(users);
         return usersMapper.convertEntityToResponse(saved);
     }
 
     @Override
     public UsersResponse getUserById(Long userId) {
         Optional<Users> optionalUser = usersRepository.findById(userId);
         return usersMapper.convertEntityToResponse(optionalUser.get());
     }
 
     @Override
     public List<UsersResponse> getAllUsers() {
         List<Users> users = usersRepository.findAll();
         List<UsersResponse> usersResponses = new ArrayList<>();
         for (Users user : users)
             usersResponses.add(usersMapper.convertEntityToResponse(user));
         return usersResponses;
     }
 
     @Override
     public UsersResponse updateUser(Long id, UsersRequest usersRequest) {
         Optional<Users> user = usersRepository.findById(id);
         Users existingUsers = user.orElse(null);
         existingUsers.setFirstName(usersRequest.getFirstName());
         existingUsers.setLastName(usersRequest.getLastName());
         existingUsers.setEmail(usersRequest.getEmail());
         Users updatedUsers = usersRepository.save(existingUsers);
         return usersMapper.convertEntityToResponse(updatedUsers);
     }
 
     @Override
     public void deleteUser(Long userId) {
         usersRepository.deleteById(userId);
     }
 }


Mappers: Useful Tips

  • Name the package as “mappers” under the feature name
  • Create an interface called Mapper using Generics and convert the entity to a model and vice versa using this mapper
  • Do not use the entity as a return object in the controller tier

Example

Java
 
package org.project.feature.mappers;
 
 import org.project.feature.entities.Users;
 import org.project.feature.models.UsersRequest;
 import org.project.feature.models.UsersResponse;
 import org.springframework.stereotype.Component;
 
 @Component
 public class UsersMapper {
 
     public UsersResponse convertEntityToResponse(Users users) {
         UsersResponse usersResponse = new UsersResponse();
         usersResponse.setId(users.getId());
         usersResponse.setFirstName(users.getFirstName());
         usersResponse.setLastName(users.getLastName());
         usersResponse.setEmail(users.getEmail());
         return usersResponse;
     }
 
     public Users convertRequestToEntity(UsersRequest usersRequest) {
         Users users = new Users();
         users.setFirstName(usersRequest.getFirstName());
         users.setLastName(usersRequest.getLastName());
         users.setEmail(usersRequest.getEmail());
         return users;
     }
 
 }


Model: Useful Tips

  • Name the package as “models” under the feature name
  • All requests and response objects will be stored here
  • Use @Data annotation for the model classes
  • The model should act as a frontend for the API, and the service should convert the model to an entity for talking to the repository.

Example

Java
 
package org.project.feature.models;
 
 import jakarta.persistence.Column;
 import lombok.Data;
 
 import java.io.Serializable;
 
 @Data
 public class UsersRequest implements Serializable {
     private String firstName;
     private String lastName;
     private String email;
 }


Java
 
package org.project.feature.models;
 
 import jakarta.persistence.Column;
 import lombok.Data;
 
 import java.io.Serializable;
 
 @Data
 public class UsersResponse implements Serializable {
     private Long id;
     private String firstName;
     private String lastName;
     private String email;
 }


Controller: Useful Tips

  • Name the package as “controllers” under the feature name
  • Try to create an API for every resource under a bounded context
  • Make the resource names in plural, like /API/users
  • For CRUD Operations:
    1. Use HTTP POST for create an operation with Request as the body
    2. Use HTTP PUT for update operation
    3. Use HTTP GET for retrieve all records
    4. Use HTTP GET with /{id} for retrieve with an identifier
    5. Use HTTP DELETE with /{id} to delete the record
  • For operation other, the CRUD try avoiding a verb as much as possible
  • Implement error handling at the controller tier
  • Implement validation at the controller tier with @Valid
  • Realize the difference between API thinking and RPC thinking. It is key to understanding APIs.

Example

Java
 
package org.project.feature.controllers;
 
 import lombok.AllArgsConstructor;
 import org.project.feature.models.UsersRequest;
 import org.project.feature.models.UsersResponse;
 import org.project.feature.services.UsersService;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
 @RestController
 @AllArgsConstructor
 @RequestMapping("/api/users")
 public class UsersController {
 
     private final UsersService usersService;
 
     @PostMapping
     public ResponseEntity<UsersResponse> createUser(@RequestBody UsersRequest usersRequest){
         UsersResponse savedUsers = usersService.createUser(usersRequest);
         return new ResponseEntity<>(savedUsers, HttpStatus.CREATED);
     }
 
     @GetMapping("{id}")
     public ResponseEntity<UsersResponse> getUserById(@PathVariable("id") Long userId){
         UsersResponse users = usersService.getUserById(userId);
         return new ResponseEntity<>(users, HttpStatus.OK);
     }
 
     @GetMapping
     public ResponseEntity<List<UsersResponse>> getAllUsers(){
         List<UsersResponse> users = usersService.getAllUsers();
         return new ResponseEntity<>(users, HttpStatus.OK);
     }
 
     @PutMapping("{id}")
     public ResponseEntity<UsersResponse> updateUser(@PathVariable("id") Long userId,
                                             @RequestBody UsersRequest usersRequest){
         UsersResponse updatedUsers = usersService.updateUser(userId, usersRequest);
         return new ResponseEntity<>(updatedUsers, HttpStatus.OK);
     }
 
     @DeleteMapping("{id}")
     public ResponseEntity<String> deleteUser(@PathVariable("id") Long userId){
         usersService.deleteUser(userId);
         return new ResponseEntity<>("User successfully deleted!", HttpStatus.OK);
     }
 }


Conclusion

In a long-term vision, as and when the code base becomes bulky, you might need to use the Strangler pattern to take out some of the services and deploy them as a separate microservice. This kind of coding structure will help then. If you get your basics right from the very beginning, then later on, the ride will be smooth. 

API Implementation Java (programming language) microservice Spring Boot

Opinions expressed by DZone contributors are their own.

Related

  • Building Mancala Game in Microservices Using Spring Boot (Part 2: Mancala API Implementation)
  • Cron Jobs vs. WatchService
  • AI in Java: Building a ChatGPT Clone With Spring Boot and LangChain
  • Using Spring Cloud Gateway and Discovery Service for Seamless Request Routing

Partner Resources


Comments

ABOUT US

  • About DZone
  • Send feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: