Pervasive Package Problems

Brett
3 min readApr 20, 2022
What should we put in our packages?

There is a pervasive problem with how our classes are packaged.

Often, classes are organised into packages by creating a package per stereotype.

A package for all of our “service” classes, a package for all our “repository” classes, a package for our “clients”, a package for all our “configuration” classes, etc.

I think this approach makes intuitive sense, which is why we see it so often. However, I think it leads to undesirable outcomes for our codebase.

Say we have an application with two components; Foo and Bar. Here’s what a package per stereotype structure looks like:

. 
`-- src/main/java/com/foobar
|-- client
| |-- FooClient.java
| |-- BarClient.java
|-- config
| |-- FooConfig.java
| |-- BarConfig.java
|-- service
| |-- BarService.java
| |-- BarInterface.java
| |-- FooInterface.java
| |-- FooService.java
`-- util
|-- FooUtil.java
`-- BarConverter.java

All interaction with Foo and Bar happens via the interfaces; FooInterface and BarInterface. These interfaces are implemented by FooService and BarService. FooService has two dependencies; FooClient and FooUtil. Bar service also has two dependencies; BarClient and BarConverter.

We are using Spring so in our config files we register a @Bean for FooInterface and BarInterface.

class FooConfig {

@Bean
FooInterface foo() {
return new FooService(
new FooClient(),
new FooUtil()
);
}
}

Can you see the problem? Unfortunately it’s hard to spot, and only once your application has grown large will you start feeling the effect.

Because the configuration files lives in their own package, all the classes they make use of must be made public so they can be accessed by the configuration.

Ideally only FooInterface would be public. All interaction with Foo would be done via the Foo interface. FooService, FooClient, FooUtil would be hidden from the rest of the application, treated as internal implementation details.

When classes are public they may be used anywhere in the codebase. BarService may start using FooUtil for example. This is bad because it couples Foo and Bar. We can no longer refactor the internals of Foo without possibly breaking Bar. Left unchecked this will lead to our code becoming the proverbial big ball of mud.

How should we structure our packages?

I propose: Package per component.

. 
`-- src/main/java/com/foobar
|-- foo
| |-- FooClient.java // package private
| |-- FooConfig.java // package private
| |-- FooInterface.java // public
| |-- FooService.java // package private
| |-- FooUtil.java // package private
`-- bar
|-- BarClient.java // package private
|-- BarConfig.java // package private
|-- BarInterface.java // public
|-- BarService.java // package private
`-- BarConverter.java // package private

Some codebases are structured like this but fail to make classes package private where they could. This means BarService can still make use of FooUtil.

I think this partly is a problem with how Java is taught; we default to making all our classes public. Take a look at any tutorial on https://www.baeldung.com/. It may also be partly due to the lack of an explicit package private keyword.

Don’t despair. We can overcome our poor intuition and bad habits.

Package per component instead of package per stereotype.

Default to package private classes instead of public

Originally published at http://github.com.

--

--