0

Currently, all REST applications I work with have a three-layered architecture:

  1. Persistence -> Contains "entity" classes with JPA annotations / Spring Data JPA repositories
  2. Core -> Contains "model" classes which are POJOs, along with "service" classes that perform business logic by using repositories from the persistence layer to serialize / de-serialize models to/from entities.
  3. API -> Contains DTOs and controller classes, along with mappers.

The application essentially models the same "concept" or "model" three times in three difference classes. As an example, if I had a "Product" class, I would have:

  1. ProductEntity -> JPA specific annotations
  2. Product -> Plain POJO
  3. ProductDTO -> API response

along with mappers to map between the different layers.

This architecture feels clean and right, but in practice, maintaining three separate classes for the same concept, along with fighting with mappers makes a non-insignificant amount of overhead, and it also opens up the possibility of not keeping fields in sync between the different layers.

I definitely want to at least keep separate classes between entities and DTOs, but I'm not convinced I should have the "model" layer. The issue with that though is that the business layer then would also directly manipulate JPA entities, and calling getters on JPA entities can create database calls.

Is there a best practice / industry standard for this? I'm leaning towards starting with entity and DTO classes, and only adding intermediate "model" classes if I need ot add state / data to a model and it wouldn't be appropriate to put it on the JPA entity.

2
  • Write you domain model following OOP principloes or/and DDD. Leverage business logic on domain model rules and make persistence just an abstraction. At the applicaiton level, write concrete persistence implementations but get rid of JPA and use a different ORM or mapper. For example MyBatis. This one allows you to presreve your domain model as is (encpasultaed data and behaviour), make it persistible and compatible with row mapping (from DB back to domain model), leaving the decision of implementing DTO only where they are needed... Usually controllers.
    – Laiv
    Commented Oct 12, 2023 at 11:38
  • The kind of layered architectures you describe are caused most of the time by braindead implementions of opinionated frameworks and ORMs, leaving you with the feeling that there's no other way to implement applications in Java these days. It's, perhaps, the most productive and quicker one, but it's IMO, the one that sacrifices most of the good practices and principles we try to adere to
    – Laiv
    Commented Oct 12, 2023 at 11:43

1 Answer 1

0

This architecture feels clean and right, but in practice...

Definitely kudos for challenging your feelings based on facts. I mean that! It is not easy. Let me help you by telling you, that these feelings may be partly someone else's fault. For at least 2 decades we've been told that separating these things, separating technical things is "clean". Even now, there's a recent thing called "Clean Architecture" which is essentially this same thing with even more complexity added. There's also lots of tools that suggest to do it this way.

There's much better ways to design software than this. The following is one:

Model the business, not the database. Your REST service is presumably doing something business-like. You need to model that. That behavior. If your behavior is returning entities (i.e. CRUD), then your architecture is already problematic. Databases already solve CRUD, that's not something you should be implementing as a service.

Let's assume that your "business" is a shopping cart. Then I would do something like:

interface Product {
   void addToCart();
}

That is a model of behavior. Let's add a behavior to remove the added item from the cart. Then I would do:

interface Product {
   CartItem addToCart();
}

interface CartItem {
   void removeFromCart();
}

This may of course not apply directly to you, but see what I mean? I'm not modeling database rows. I'm modeling behavior.

Whether this is implemented through ORM, Spring, or they fire direct SQL statements into the database. I don't really care. Implementation detail. That's why it's not even visible.

Some people call this "rich domain", but as always if you search for this, you'll get other interpretations too, some even contradicting this one.

3
  • I don't disagree with anything you said here. Ideally, you would want a pure business only / logic only layer isolated from other systems (persistence layer / rest layer), but in practice I'm asking if that should be implemented by having separate classes and mapping between layers, or if doing all of that logic in JPA entities (persistence tech models) is appropriate. The "correct" answer of separating the layers that I've been told before generates so much work that in my opinion it hinders effective development and makes changes harder. Commented Oct 11, 2023 at 15:09
  • From reading up on the subject of DDD, I've found that the design of APIs at the company is more or less "anemic". Classes directly model database tables, and we've had mapping between what is essentially the same class between three different layers. I agree that having objects that actually encapsulate their business logic in methods is a great idea; it just doesn't lend itself to the tools we're using. In your example interface with the Product, adding it to the cart in memory is fine, but then how do you actually save it to the persistence layer without introducing deps.? Commented Oct 11, 2023 at 15:15
  • It's not "in memory" in the above example. The implementation will do what it says it does. There is no other layer. Anyway, if your company wants you to model tables and records, I guess that's what you have to do. However, the problems you mention will not go away. I'm afraid the level of unmaintainability you mention are pretty "normal" for these kinds of designs. Commented Oct 11, 2023 at 20:02

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.