9

I'm refactoring a microservice according to Clean Architecture:

enter image description here

Frameworks should be at the utmost layer. So I used the Adapter Pattern and Dependency Inversion to put org.springframework.data.repository.CrudRepository at the utmost layer. But how can I use @Entity (from Java Persistence API) to persist my entities, if entities are in the center and frameworks are at the utmost layer?


Example: Demo-Entity:

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.constraints.NotNull;

@Entity
public class Demo implements Serializable {
    @Id
    @GeneratedValue
    private long id;
    
    @NotNull
    private String foo;

}

GenericRepostioryInterface (in the Usecase Layer)

public interface CrudRepositoryInterface<S,T> {
    public <U extends S> U save(U u) ;    
    public <U extends S> Iterable<U> saveAll(Iterable<U> itrbl) ;    
    public Optional<S> findById(T id) ;    
    public boolean existsById(T id) ;    
    public Iterable<S> findAll() ;    
    public Iterable<S> findAllById(Iterable<T> itrbl) ;    
    public long count() ;    
    public void deleteById(T id) ;    
    public void delete(S t);    
    public void deleteAll(Iterable<? extends S> itrbl) ;    
    public void deleteAll() ;  
}

Some usecase:

    @Autowired
    private CrudRepositoryInterface<Demo,Long> demoRepository;
    ...
    
    private void deleteAll(){
      this.demoRepository.deleteAll();
    }
    ...

Adapter (DB Layer)

public interface DemoRepositoryAdapter extends CrudRepository<Demo,Long>,CrudRepositoryInterface<Demo,Long>{    
}

Config for Injection (I put that in the DB Package/Layer as well)

@Configuration
public class InjectRepositoryConfig {    
    @Bean
    public CrudRepositoryInterface<Demo,Long> animalOwnerRepository(@Autowired DemoRepositoryAdapter demoRepositoryAdapter){
        return demoRepositoryAdapter;
    }
}

This works fine so far but I'm unsure how to remove / replace / refactor JPA out of the core layer?

Jesse
  • 3,243
  • 1
  • 22
  • 29
Barney Stinson
  • 962
  • 12
  • 29
  • 1
    JPA is a standard and not framework so you there is no need to refactor entity classes. – niemar Sep 30 '18 at 10:21
  • 2
    @niemar, I don't think that changes a lot. The project requirements can force you to change the used standard the same way they force you to change the used framework. For example, "the marketing person" may decide the project should use NoSQL because many customers love this fancy word. JPA does not support NoSQL so you would be required to use another standard/framework. – Eneko Jun 16 '20 at 15:12

3 Answers3

15

I think the general confusion here is due to the overloading of the term Entity, which has different semantics across different contexts. In the context of JPA, an Entity is a persistence abstraction representing rows in a table and ORM. In the context of Clean Architecture, an Entity is a business domain abstraction and is completely independent of persistence.

They can co-exist in a Clean Architecture, but they each serve distinct purposes. Attempting to combine them and leverage JPA capabilities in your business domain entities violates the principles of Clean Architecture and will couple your domain to your persistence implementation.

Cory Serratore
  • 161
  • 1
  • 4
  • 7
    I'm not sure why my comment was down voted. Allow me to quote The Clean Architecture book. Page 198 states "Your Entity objects should be plain old objects that have no dependencies on frameworks or databases or other complications." Page 215 goes on to clarify "Where should such ORM systems reside? In the database layer of course." Cleary, JPA "entities" and Clean Architecture "entities" are distinct. – Cory Serratore Dec 02 '19 at 20:43
6

If you really want to follow clean architecture, then no reference to external libraries should be in your domain classes. To overcome that, a couple of strategies could be:

  • User jpa/hibernate XML mappings. This way you can externalize your mappings and change between sql/noSql easily just importing a different config file (each file will setup a different set of XML mappings).

The downside of this approach, is that your domain will still require to implement Serializable, and most of them will require setters/getters for its fields, or force hibernate/jpa to ignore Java limitations and access private fields. Also, there are things you can do with Annotations that is not possible with XML, or the workaround is problematic. It works, but still your domain layer looks like simple DTOs.

  • When crossing a boundary, use DTO objects. This means you will have your domain object Demo, and a DTO object DemoData for example. Data will be retrieved using DemoData instances, and repository should convert between those two when saving/retrieving.

This last approach makes you project very easy to change. You can change persistence layer any time, but doing so will require a whole new datastore layer to integrate. Let's say you want to change to cassandra, you will need a new DTO DemoDataCassandra, its mappers between it to Demo, new annotations for mappings, etc.

Jhovanni
  • 189
  • 1
  • 12
0

The interface for the repository to persist the entities goes in the center. Implementation of that interface goes in the outside layer, and the implementation is injected where it is needed.

Brad Irby
  • 2,397
  • 1
  • 16
  • 26
  • so is it ok to use JPA-Annotation inside the entity because they are ?interfaces? and the implementation is injected by using hibernate dependency? – Barney Stinson Oct 02 '18 at 17:33
  • The repository interface should not require any references to outside libraries. It should only reference domain level objects so that you can implement the interface in any library, not just JPA. So to answer your question, if you need JPA references in the domain, you've done something wrong. The interface should be something like the following: interface MyEntityRepository { MyEntity Get(int id); void Save(MyEntity ent); void Delete(MyEntity ent); } – Brad Irby Oct 03 '18 at 09:11