13

I'm working in an architecture, it is going to offer a REST API for web client and mobile apps. I'm using Spring(spring-mvc, spring-data-jpa, ...). The domain model is coded with JPA specification.

I'm trying to apply some concepts of clean architecture (https://8thlight.com/blog/uncle-bob/2012/08/13/the-clean-architecture.html). Not all, because I'm going to keep the JPA domain model.

The actual flow through the layers is this:

Front end <--> API Service -> Service -> Repository -> DB

  • Front end: web client, mobile apps
  • API Service: Rest Controllers, here I use converters and dto's and call services
  • Service: Interfaces with implementations and they contain business logic
  • Repository: Repository interfaces with automatically implementations(done by spring data jpa) which contatin CRUD operations and maybe some sql queries

My doubt: Should I use an extra layer between service and repository?

I'm planning this new flow:

Front end <--> API Service -> Service -> Persistence -> Repository -> DB

Why to use this persistence layer? As it says in the clean architecture article I would like to have a service implementation(business logic or use case) that access an agnostic persistence layer. And not changes will be needed if I decide to use another "data access" pattern, for example if I decide to stop using repository.

class ProductServiceImpl implements ProductService {
    ProductRepository productRepository;
    void save(Product product) {
        // do business logic
        productRepository.save(product)
    }
}

So I'm thinking use a persistence layer like this:

class ProductServiceImpl implements ProductService {
    ProductPersistence productPersistence;
    void save(Product product) {
        // do business logic
        productPersistence.save(product)
    }
}

and implementation of the persistence layer like this:

class ProductPersistenceImpl implements ProductPersistence {
    ProductRepository productRepository;
    void save(Product product) {
        productRepository.save(product)
    }
}

So I only need to change the implementations of persistence layer, left the service without changes.Coupled with the fact that the Repository is related with the framework.

What do you think? Thanks.

3
  • 3
    the repository IS the abstraction layer. adding another one doesnt help
    – Ewan
    Commented May 10, 2017 at 9:35
  • Oh, but I would have to use the interface proposed by Spring, I mean, repository method names. And if I want to change the repository I would have to keep the calling names, No?
    – Alejandro
    Commented May 10, 2017 at 17:20
  • it looks to me like the spring repository doesnt force you to expose spring objects. Just use it to implement an agnostic interface
    – Ewan
    Commented May 10, 2017 at 18:02

3 Answers 3

13

Front end <--> API Service -> Service -> Repository -> DB

Right. This's, essentially, the design by layers proposed by Spring Framework. So you are in the "Spring's right way".

Despite Repositories, these are frequently used as DAOs, the truth is that Spring developers took the notion of Repository from Eric Evans' DDD. Repository interfaces will look often very similar to DAOs because of the CRUD methods and because many developers strive to make repositories' interfaces so generics that, in the end, they have no difference with the EntityManager (the true DAO here)1. Repositories tho add other abstractions like queries or criteria to enhance the data access.

Translated into Spring components, your design is similar to

@RestController > @Service > @Repository >  EntityManager

The Repository is already an abstraction in between services and data stores. When we extend Spring Data JPA repository interfaces, we are implementing this design implicitly. When we do this, we are paying a tax: a tight coupling with Spring's components. Additionally, we break LoD and YAGNI by inheriting several methods we might not need or wish not to have. Moreover, we are (implicitly) assuming that the persistence data model is also the domain data model. This is quite the endemic of Spring, after many years working with Spring frameworks, you end up making everything data-centric.

That said, extending Spring Data JPA repositories is not mandatory. We implement a more plain and custom hierarchy of classes.

    @Repository
    public class DBRepository implements MyRepository{
        private EntityManager em;
        
        @Autowire
        public MyRepository (EntityManager em){    
             this.em = em;
        }

        //Interface implentation
        //...
    }

Changing the data source now just takes a new implementation which replaces the EntityManager with a different data source.

    //@RestController > @Service > @Repository >  RestTemplate

    @Repository
    public class WebRepository implements MyRepository{
        private RestTemplate rt;
    
        @Autowire 
        public WebRepository (RestTemplate rt){    
             this.rt = rt;
        }

        //Interface implentation
        //...
    }
    //@RestController > @Service > @Repository >  File

    @Repository
    public class FileRepository implements MyRepository{
       
        private File file; 
        public FileRepository (File file){    
            this.file = file;
        }

        //Interface implentation
        //...
    }
    //@RestController > @Service > @Repository >  SoapWSClient

    @Repository
    public class WSRepository implements MyRepository{
       
        private MyWebServiceClient wsClient; 

        @Autowire
        public WSRepository (MyWebServiceClient  wsClient){    
               this.wsClient = wsClient;
        }

        //Interface implentation
        //...
    }

and so on.2

Back to the question, I don't think you need more layers. The layer you propose is going to end up as a proxy in between services and repositories or as a pseudo-service-repository where to place code you are not sure whether it belongs to the business or to the persistence.


1: Unlike many developers think, repository interfaces can be totally different from each other because each repository serves different domain needs. In Spring Data JPA, the role DAO is played by the EntityManager. It manages the sessions, the access to the DataSource, mappings, etc.

2: A similar solution is enhancing Spring's repository interfaces mixing them up with custom interfaces. For more info, look for BaseRepositoryFactoryBean and @NoRepositoryBean. However, I have found this approach cumbersome and confusing.

6
  • In the question the author refer to Entity class and not an EntityJPA class. So even if you can change the repository (database, file, etc ...) you continuing to depend on Entity and I think it's wrong. The repository must be aware of EntityJPA and not Entity. So I wrote down a new question softwareengineering.stackexchange.com/q/442397/385571 to suggest solution
    – Adri
    Commented Nov 24, 2022 at 2:54
  • Well, your question/answer is neither right nor wrong. It just omitted this Persistence -> Repository and that's what I tried to illustrate with my snippets. The Persistence layer between Service and Repository seems unnecessary because Repository is already an abstraction. The problem is that spring developers usually tight couple their repositories with Spring Data repositories (for convenience). If you define repositories interfaces yourself, then you don't couple the core to the framework (one of the CA premises).
    – Laiv
    Commented Nov 24, 2022 at 10:13
  • The problem is that spring developers usually tight couple their repositories with Spring Data repositories because JpaRepository define all methods save(), findAll(), etc ... In the case of DBRepository you need to create your owns methods and convert Product to ProductJPARepository But I agree with your answer because it answers the question perfectly, this being just a special case with Spring
    – Adri
    Commented Nov 24, 2022 at 14:32
  • Not necessarily. I don't use Spring Data anymore for that reason (imposes JPA). I use the abstraction that best serves my needs of decoupling. For example, I might use MyBatisDao or the standard EntityManager. But I pay a tax, I'm reinventing the wheel. Spring Data repositories do many of these things for me, but it imposes JPA and I might not want to go with JPA. I'm a huge fan of MyBatis, so I learnt to decouple my persistence from Spring Data too.
    – Laiv
    Commented Nov 24, 2022 at 16:29
  • 1
    @Laiv Spring Data does not impose JPA. In fact, it's a generic abstraction over a bunch of different persistence technologies. There are a bunch of sub-projects, such as Spring Data JPA, Spring Data Mongo, etc, that you can choose from based on the persistence technology you want. See spring.io/projects/spring-data
    – E-Riz
    Commented Apr 19, 2023 at 20:56
3

The best way to prove that a design is flexible is to flex it.

You want a place in your code that is responsible for persistence but is not tied to the idea of using a repository. Fine. It's not doing anything useful at the moment... sigh, fine.

OK, lets test if this shunt layer did any good. Create a flat file layer that will persist your products in files. Now where does this new layer go in this design?

Well it should be able to go where DB is. After all we don't need DB anymore since we're using flat files. But this was also supposed to not need repository.

Consider, maybe repository is an implementation detail. After all I can talk to the DB without using the repository pattern.

Front end <--> API Service -> Service -> Repository -> DB

Front end <--> API Service -> Service -> Repository -> Files

Front end <--> API Service -> Service -> Persistence -> DB

Front end <--> API Service -> Service -> Persistence -> Files

If you can make all that work without touching Service you have flexible code.

But don't take my word for it. Write it and see what happens. The only code I trust to be flexible is flexed code.

0
0

I would like to add a comment on the particular case of Spring Data.

UML Solution of Laiv and candied_orange

The solutions and arguments provided by the two authors are correct. I would just like to get into the particular case of Spring Data.

enter image description here

@Repository
public class DBRepository implements MyRepository{
    @Autowired
    private EntityManager em;

    public void create(Product product) {
         em.save(product)
    }
}

Use of Spring Data

Product and ProductEntityJPA

But, in reality we have to use two entity :

  • One for the domain entity Product
  • One for the database entity ProductEntityJPA

As a result, the EntityManager class doesn't longer takes a Product but an ProductEntityJPA.

JPARepository interface

JPARepository interface provide all the necessary methods for the database management : save(), findAll(), etc ...
As result, we don't have to create you own method in DBRepository

Solutions

Solution #1

enter image description here

Why isn't it the best :

  • We need to implement all JPARepository methods in DBRepository

Solution #2

We therefore prefer to use the methods of the JPARepository directly rather than reimplementing them.

enter image description here

Why isn't it the best :

  • Our application core is aware of the use of the JPA library. And we want to avoid this !!

Solution #3 - add a new layer

enter image description here

public class DBRepository implements MyRepository{
    @Autowired
    private ProductJPARepository jpaRepository;

    public void create(Product product) {
         jpaRepository.save( transform(product) );
    }

    private ProductEntityJPA transform(Product product) {
        return new ProductJpaEntity( product.getX(), ...);
    }
}

Why it's a good solution:

  • We take advantage of the JPARepository interface so we don't have to reimplements all methods
  • The core is not aware of the use of JPA.

Conclusion

  • We add a new layer that is an Adapter

Conclusion

In the case of Spring Data :

  • We want to have two distinct entities Product and ProductEntityJPA.

  • We want to take advantage of the JPARepository interface.

  • Solution #1 forces us to redefine all methods

  • Solution #2 is aware of the use of JPA

  • Solution #3 allows not to redefine all methods and not to be aware of JPA.

=> So the use of an intermediate layer seems necessary in Spring Data.

Finally, I change the name of the classes to match the class name in the Clean Architecure diagram.

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.