Decoupled architecure between business and data layers in Spring JPA / Hibernate

business-logicdatahibernatejpaspring

I'm using Spring Boot with JPA / Hibernate and I'm trying to decouple the business layer from the data layer. I would like to be able to (relatively easily) switch from a relational database to a rdf triple store.

Here is a partial simplified description of my current try. I have entities such as:

@Entity(name="Drawer")
public class DrawerEntity {
    @Id
    private int id;
    private String label;

    public int getId(){return id;}
    public void setId(int id){this.id=id;}

    public String getLabel(){return label;}
    public void setLabel(String label){this.label=label;}

}

I have business objects wich are acting as proxy to entities such as:

public class Drawer{
    @Autowired
    private CardRepository cardRepository;
    private DrawerEntity drawerEntity;

    public Drawer(){this.drawerEntity = new DrawerEntity();}
    public Drawer(DrawerEntity drawerEntity){this.drawerEntity = drawerEntity;}

    public int getId(){return drawerEntity.getId();}
    public void setId(int id){drawerEntity.setId(id);}

    public String getLabel(){return drawerEntity.getLabel();}
    public void setLabel(String label){drawerEntity.setLabel(label);}

    public int getNumberOfCards(){return cardRepository.countByDrawerId(this.getId());}
}

I've also a service layer :

public interface DrawerService {
    Collection<Drawer> findAll();
    Drawer findOne(int id);
    Drawer create(Drawer drawer);
    void delete(int id);   
}

Implemented by a DrawerServiceBean

@Service
public class DrawerServiceBean implements DrawerService {

    @Autowired
    private DrawerRepository drawerRepository;

    @Override
    public Collection<Drawer> findAll() {
        Collection<Drawer> allCard = new ArrayList<>();
        for(DrawerEntity drawerEntity : drawerRepository.findAll()){
           allCard.add(new Drawer(drawerEntity)); 
        }
        return allCard;
    }

    @Override
    public Drawer findOne(int id) {return new Drawer(drawerRepository.findOne(id));}

    @Override
    public Drawer create(Drawer drawer) {return new Drawer(drawerRepository.save(drawer.getDrawerEntity()));}

    @Override
    public void delete(int id) {drawerRepository.delete(id);}
}

Which is linked to a JPA repository

@Repository
public interface DrawerRepository extends JpaRepository<DrawerEntity, Integer> {}

Following the advices given in this answer, I'm not injecting anything into the entities and I'm using business proxy objects. But then, it seems that, I have to use @Configurable and AspectJ LTW for injecting the repositories into the business objects because "Spring AOP only supports method execution join points for Spring beans".

The goal of this architecture was to be able to define methods such as getNumberOfCards in business objects that use different types of repositories (CardRepository, DrawerRepository). But the result seems complex with a lot of boilerplate code. I wonder how I can simplify this? Is Aspect Oriented Programming necessary? Can I reduce the size of the boilerplate code? Is there some solutions provided by Spring Data?

Best Answer

Sometime ago, i asked on SO a question that include if i have to implements my service as they're using JPA managed entities ? Here are the two points of my own answer that should interest you :

  • I don't have to abstract the fact that i use managed entities, this will lead to complex and unefficient code

  • I choosed JPA, i won't switch for it which is true unless rewriting the full model to stand for something based on non relationnal database.

If you try to abstract the fact that you're using lazy-loading/managed entities, you will just have troubles. For instance, i saw a project where there was DAO Pojo and business POJO, they were always converting the DAO to business POJO using adapter, and thus always triggering loading lazy list in the N+1 select way.

So if I adapt what i was told in that question, at the very least, your should have 2 differents business implementations, one relying on the strength of JPA, and the other, either one that don't care about JPA/RDF, either one that will get the best of RDF DAO layer.

I know that doesn't seems to follow the rule of POO about totally encapsulating behaviour in each layer, but really, try to abstract complex JPA model without triggering cascading N+1 select on every relations or manually fetching each relations when you shouldn't need to with JPA. It just can't work. JPA is really invasive, but really powerful too, as you have very few code to write (and maintain!) to use it.

Of course if you're just doing something really simple having quite few data, a demo, ... you can stick to one business layer that is not using JPA at his best.

But if you need some basic performance, like loading all your database line-by-line in the ORM is not an option, you will probably end up stuck with one implementations that don't use JPA right.

Related Topic