6

How do I achieve two things at the same time writing Java Spring Application:

  1. Clean Architecture
  2. JPA goodies (@Transactional, Optimistic Locking, dirty checking, etc.)

I like the idea of database/ORM framework being an unimportant detail that can be easily replaced, but I'm not sure how to effectively implement it.

Let's say potentially I want to replace JPA/Hibernate with jOOQ or Spring Data JDBC.

But at the same time, I want to have support for transactions, dirty checking, etc.

How should I process use case in an application/domain layer so it can have merits of two worlds?

I don't have to mention that with clean architecture I have to convert domain type to entity type every time I speak with database.

Glorfindel
  • 3,137
  • 6
  • 25
  • 33
Dariusz Mydlarz
  • 201
  • 1
  • 6

1 Answers1

1

Achieving a clean architecture while leveraging the benefits of JPA can be challenging, but it's definitely possible. Here are some suggestions on to achieve this and considerations to be made:

  1. Separation of Concerns: Start by defining clear boundaries between your application layers:

    • Domain Layer: Contains your business logic and domain entities.
    • Application Layer: Contains use cases and orchestrates the flow of data to and from the domain layer.
    • Infrastructure Layer: Contains all the external concerns like database, third-party services, etc.
  2. Domain Entities vs Persistence Entities:

    • Domain Entities: Pure business objects without any persistence annotations.
    • Persistence Entities: Annotated with JPA annotations. These are the ones you'll convert to/from domain entities.

    Use mappers or converters to transform between domain and persistence entities. Libraries like MapStruct can help automate this.

  3. Repository Abstraction:

    • Define repository interfaces in the domain or application layer. These interfaces should be agnostic of the persistence mechanism.
    • Implement these interfaces in the infrastructure layer using JPA, jOOQ, Spring Data JDBC, etc.
  4. Transactional Boundaries:

    • Use the @Transactional annotation at the application layer (on use case methods) to define transactional boundaries. This ensures that your business logic is wrapped in a transaction.
    • If you switch from JPA to something else, you can still use Spring's @Transactional as it's not tied to JPA.
  5. Leverage JPA Features:

    • Dirty Checking: JPA automatically checks for changes in managed entities. If you're using domain entities, you'll need to manually merge changes into the managed persistence entity.
    • Optimistic Locking: Use @Version on your persistence entity. When converting back to the domain entity, ensure you carry over the version information so it can be checked during the next operation.
  6. Switching Persistence Mechanisms:

    • If you decide to switch from JPA to jOOQ or Spring Data JDBC, you'll need to reimplement your repository interfaces in the infrastructure layer using the new mechanism.
    • Ensure that the new implementation still respects transactional boundaries and other features you've come to rely on.
  7. Testing:

    • With a clean architecture, you can easily mock or stub out your repositories to test your application and domain layers in isolation.
    • Integration tests can be written to ensure your infrastructure layer correctly implements the repository interfaces.
  8. Consider CQRS: If you find that your read and write models diverge significantly, consider using the Command Query Responsibility Segregation (CQRS) pattern. This allows you to optimize your read and write operations separately, potentially using different persistence mechanisms for each.

Achieving a clean architecture while leveraging JPA features requires a clear separation of concerns, abstracting away the persistence mechanism, and being diligent about converting between domain and persistence entities. It's more work upfront, but it provides flexibility and maintainability in the long run.

jimmykurian
  • 286
  • 1
  • 6