How to structure a Java codebase
Best practice for Java codebases, use domain driven design
When you build a software application — no matter if it’s a web app, cli tool, or desktop software — your application is trying to solve some problem. This problem is the ‘domain’ of your application. The ‘domain’ is just the sphere of your application operates in. The knowledge it embeds, and the problem it solves.
Structure your codebase by domain
Your ‘domain’ is typically interchangeable with the high-level words you use to describe your application. For example;
- Banking software
- E-commerce site
- Food delivery app
In these examples, the domains are Banking, E-commerce, and Food delivery. You should structure your java code base by your domain.
Your software application’s domain is made up of lots of smaller sub-domains, which in-turn can be split smaller and smaller into;
- sub-sub domains
- sub-sub-sub-domains
- and so on, and so on.
These are the splits you should structure your application around. For the Food delivery app some sub-domains might be Payments, Users, Riders, Restaurants, and Orders. You should structure your codebase around the natural sub-divisions of the main problem your software is solving.
Don’t split by type
A very common mistake is to split your software by type. This is sometimes called the Layered Architecture.
In the Layered Architecture, your code would be split based on the role the it is fulfilling, and not the problem your software is solving. For example, the layers might be:
- Controller Layer — receiving and transforming HTTP requests.
- Service Layer — applying the logic to the request and data, processing the request.
- Repository Layer — storing the data, transforming it in and out of a persistent datastore or database.
You will commonly find layers that look like this:
── src/main/java
└── com/mycompany
├── controller
│ └── OrdersController.java
├── service
│ └── OrdersService.java
└── repository
└── OrdersRepository.java
Here we have a package for each type of code; controller, service, repository.
Each package contains an OrdersXYZ.java
class to fulfil the package’s type of code.
The packages we created (controller, service, repository) only help us to group code by it’s type. They don’t help us to:
- Represent the sub-domains in our application.
- Separate or hide details of some code from other code.
When we expand our application to contain our other sub-domains it creates a codebase that looks like this:
The code that lives in a package together has very little to do with the other code that lives in the same package.
The code is coupled.
Coupling and cohesion
Coupling is the degree of inter-dependence between two software packages. In other words; how much one package uses another package.
Cohesion is the opposite of coupling. Low coupling often means high cohesion. Cohesion is how closely related classes are within the same package.
Cohesion is good, coupling is bad.
Coupling is why you shouldn’t split your packages by type.
The good-bad coupling and cohesion diagram above looks very similar to our packages when we package our code by type. We can see this by:
-
There are lots of red-lines linking individual packages. (couping)
-
There are very few relationships between classes in the same package. (cohesion)
What a domain driven design package structure looks like
If we package our code by domain, instead of by type, it would look like this:
This is java packaging best practice. It has low coupling, and high cohesion.
Here we can see that a service, like the OrdersService, can still make use of other packages. But our coupling is much lower, because we’ve packaged the code by sub-domain and not by type.
Organising the code like this allow you to hide the details of each sub-domain from the other sub-domains. It reduces the package coupling, and increases the cohesion of each package.
How to visualise java packages
It’s easy to see the coupling and cohesion of your java code in these images. And we don’t want to manually hand-draw a package structure layout.
It’s easy to see the coupling an cohesion in these diagrams because:
- There are red-lines that represent the cross-package class usage.
- There are black lines that represent the class using within the same package.
- The classes age grouped by their package, showing the closely related code.
You don’t have to manually draw these package layout images yourself
We can automatically generate the package diagrams using a tool like PackageMap.
This diagrams shows the example we’ve been working with in this article. The diagram is auto-generated from source code by the PackageMap java-parser.
Here’s the same example from this blog post, but interactive and you can filter and explore it:
We can filter the diagram to see only certain classes and interactions. Here’s the example when we filter by *OrdersService
. This is a wildcard filter matching the OrdersService.
We can immediately see how the OrderService interacts with the other classes from other packages.
Use Java code structure diagrams to package your code by domain
By automatically generating a java code structure diagram, and visualising the java code structure we can easily see how our code is packaged. You can use these package structure diagrams to structure our code by domain and visualise the coupling and cohesion in our software packages.