How to structure a Java codebase

Best practice for Java codebases, use domain driven design

Zak Knill
5 min readDec 5, 2022

The short answer is “by domain”.

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 operstates in. The knowledge it embeds, and the problem it solves.

Structure your codebase by domain

Your ‘domain’ is typically interchangable 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 domian 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, Resturants, 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, the splits would be based on the role the code 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 the following:

── 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 filful that packages type of code.

The java packages when split by ‘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 java packages when split by ‘type’ of code, including all the subdomains.

The code that lives in a package together has very little to do with the other code that lives in the same package.

This code is coupled.

Coupling and cohesion

Coupling is the degree of inter-dependence between two software pacakges. In order 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.

Showing high and low coupling and cohesion.

“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 code structure looks like

If we package our code by domain, instead of by type, it would look like this:

Code packaged by domain, and not by type. With an example of OrdersService using other package’s services.

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 couping, 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-packge 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 this tool:

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.

Generated graph of the packages

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 certian classes and interactions. Here’s the example when we filter by *OrdersService . This is awildcard 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. We can use these package structure diagrams to structure our code by domain and visualise the coupling and cohesion in our software packages.

You can use automated tooling like https://packagemap.co to generate and visualise your code structure.

--

--