Service Architecture for Spring Boot
Written and compiled by Anil Singh

Service Architecture for Spring Boot

Introduction

Are your spring boot services getting too complex to understand & maintain? Do you spend too much time trying to fit new functionalities into existing services/code instead of focusing on solutions? Writing service test cases & maintaining them has become painstaking?

No alt text provided for this image

If yes, let's see how applying SOLID design principles can solve some of these problems.

SOLID Principles

First, let's recall the SOLID principles before diving deep. SOLID is one of the most popular sets of design principles in object-oriented software development. It’s a mnemonic acronym for the following five design principles:

  • Single Responsibility Principle - A class should have one and only one reason to change, meaning that a class should have only one job.
  • Open/Closed Principle - Objects or entities should be open for extension but closed for modification.
  • Liskov Substitution Principle - Every subclass or derived class should be substitutable for their base or parent class.
  • ?Interface Segregation Principle - A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.
  • Dependency Inversion - Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but should depend on abstractions.

Let's take a look at how a typical service looked earlier in our projects.

No alt text provided for this image

Design problems with the existing structure

  • Single Responsibility Principle - The service class performs more than one responsibility like validation, mapping and saving to DB apart from saving a user.
  • Code duplication - If multiple services need the same validation or mapping logic, it can lead to code duplication.
  • Unit testing - Writing unit test cases for such service is difficult.
  • Code readability - If the service grows over time and new methods get added, it makes the service class huge enough to find and follow methods in that service.

A few other design problems with our code base were -

  • Cyclic dependencies - Utility classes created to avoid duplicate code lead to cyclic dependencies.
  • Side effects - Adding new code or modifying code becomes difficult with increasing complexity and leads to adding bugs with each change.
  • Concurrency control - Difficult to implement multiple granularity concurrency controls.

New Design Architecture for Services

The below figure shows the new architecture designed which tries to enforce certain aspects of the SOLID principle and solves problems with our old service design.

No alt text provided for this image

The following are the main components of the new architecture.

  • Service
  • Mapper
  • DAO Decorator
  • Service Validator

Let's understand each component in detail before understanding how it solves the problems discussed earlier.

Service

The service component can be a spring service or a spring component which interacts with external systems/services or service util. The service component depends on other components like mappers, DAO decorators, service validators, other external services, and service utils to perform expected operations.

The service component only decides the flow of data and interaction among other components. This makes unit testing of service easier since other components can be easily mocked and the flow of data and interaction with other components (mocks) can be easily verified.

Mapper

The mapper component is used to map DTOs (Data Transfer Object) to & from Spring JPA/Hibernate entities. If mapping requires custom logic or data from the database, the map struct mapper can be extended using a decorator pattern.

Let's see how to add custom logic with decorator pattern and map struct. Say you have a requirement where if an admin user has requested user details, you need to send SMS usage along with user details. We can use map struct @DecoratedWith annotation and create and custom abstract class to write custom mapping logic.

Following is a sample implementation -

No alt text provided for this image

You can use the above mapper in your service class as you would a non-decorated mapper.

No alt text provided for this image

DAO Decorator(DD)

DAO decorators are wrappers over Spring JPA repositories. They are used to enforce database validations, custom locking, handling type conversions, create abstractions like read-only DAO, and create abstract actions of database tables which modified multiple columns.

Let's see what a DAO decorator looks like -

No alt text provided for this image

Service Validator

Service validators abstract all business level validations. Service validators can throw different types of exceptions related to business validation.

Let's see what a service validator looks like -

No alt text provided for this image

Validating new design against SOLID principles

First, let's see what a service will look like after applying the new design

No alt text provided for this image

Now let's validate it against design problems with the older design

  • Single Responsibility Principle (SRP) - As can be seen in the code above, UserServiceImpl delegates different responsibilities related to updating a user to other dependent classes that themselves follow SRP.
  • Code duplication - The actual logic of validation, mapping etc. is in separate classes multiple services can simply inject those classes and avoid duplicate code.
  • Unit testing - The code now follows SRP each class with particular responsibility can be tested in isolation and service class unit tests can mock these classes, making it easier to write unit tests.
  • Code readability - Since custom logic is now moved to classes based on responsibilities, service classes are now less cluttered and readable.
  • Cyclic dependency - The architecture only allows a component to depend upon other components which are exactly one level below and components on the same level cannot depend on each other, ensuring that cyclic dependencies are avoided. Though dependencies within the same component are allowed, developers must ensure that there are no cyclic dependencies there.
  • Side Effects - SRP ensures that changing requirements changes only the classes responsible for it.
  • Concurrency control - The addition of a DAO decorator allows adding custom and/or common concurrency control techniques like locking.

Few other topics

  • Scaling TeckPortal's Rule Engine to process over million sensor data.
  • Angular end-to-end tests using Cypress.
  • Tips for hardening security & monitoring Linux servers.
  • Our high availability setup

Let us know which topic we should talk about next in the comments.

要查看或添加评论,请登录

Neptune Ubicom Pvt. Ltd.的更多文章

社区洞察

其他会员也浏览了