When an application code base is growing it may become complicated to maintain, cause understanding the whole thing requires more and more cognitive effort and the code entropy tends to grow exponentially with time.
The classical approach to address this problem is to make the application more modular : the whole application is split in several part that have clear boundary and dependencies between each other : let’s call it a module. It makes much easier to figure out the impact of adding or modifying a functionality. Also each module can be understood without having the the whole system in mind. As a consequence, teams can be split to concentrate on a subset of the system.
Modularity can be achieved at different level :
Process as module : The application is split in several smaller applications, each running in its own process and communicating each other through network. That is the micro-services approach. It offers highest isolation but may be considered as extreme in regards of technology shift and productivity overhead it implies (see controverse).
Project as module : The application still run in a single process but the code is split in several projects, each standing for a library with well defined interface. IDE can enforce dependency rules between sub-projects, preventing developers to break dependency scheme. The cons is that it makes build significantly more complicated and longer to run if we need to build the whole thing altogether. Developer experience also get worse cause of additional elements to manage and keep in sync within the IDE.
Java package as module : This is the natural mechanism to structure code within a Java project. Java classes belong to Java package which stands for a module. Unfortunately there is no native way to express package dependency rules in java and therefore no native natural way to enforce it. It’s where JDepend comes in play…