I am a big fan of the Clean Architecture model as described by Robert C. Martin.
The big idea is that all dependencies in the picture below all point inwards.
The inner layer only contains the domain model and has no connection the the outer layers. Be aware that this also means that the domain model should not contain any database (e.g. JPA) specific annotations or classes.
The “Application Business Rules” layer can contain Domain Services that contain the logics that are not implemented in the domain model itself.
All application logic should be in these two inner layers.
It is allowed however to define the interfaces to the database or external systems in this layer. These can be used in the domain services.
As shown in the picture below, the entities exists in their own layer.
The “User Case Interactor” is a service that is located in the “Application Business Rules” layer, together with all the interfaces it needs.
The Controller, Presenter and View model are located in the “Interface Adapters” layer.
The concrete implementation of the database, the UI, the connection to any external system are all located in the most outer layer.
To implement this architecture in java, you can use a multi module for this. The domain model will be located in it’s own module (jar file). Depending on the size of your application, the logics of the seconds “Application Business Rules” can be included in the same module, or can be located in it’s own module (or multiple).
It is important that these modules contain all the logics of the application and that these modules have no dependencies to any other modules of your application, and preferably also not to any framework or database api used.
E.g. the database module can be created as a separate module (jar file) that implements the database interface that was specified in the “Application Business Rules” layer.
When using e.g. an oracle database, then this module alone may depend on that.
This layer should encapsulate completely which database is used, and how this is implemented.
Connections to any external system can be developed the same way.
The services in the inner layer who need access to the database or an external system must be injected with the correct implementation of that.
This is responsibility of the Main component. The Main component is the only component who know’s which implementation of the database or external system is used and injects that to the services who require that.
The advantage of that is that when e.g. the database is changed from oracle to mongodb, this can be written in a new module which lives besides the original implementation. With a configuration switch, it is possible to change implementation.
Another advantage you get is when you need to change anything to e.g. the connection to an external system. The module you need to change implements a well defined interface located in the inner layers. Since no other module depends on this module, you know that you can refactor anything as long as you provide a correct implementation without worrying that any other module might (incorrectly) depend on any implementation detail classes that might be public in this module.