Pervasive Package Problems

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.

--

--

--

Software Engineer at Wise

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Reflections From a Summer Internship With MySwimPro

Romp! A Sinatra CMS Application

Sublime Text 3 TensorFlow GPU 1.13 Environment With RTX 2080 on Ubuntu 18.04 LTS

Flutter App- Send direct messages without saving number through WhatsApp

Nutanix SE Academy NCSE Level 1 Exam Questions

Browsing the web like a badass

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Brett

Brett

Software Engineer at Wise

More from Medium

Anonymous Inner Classes

Functional Interfaces in Java

Replace Text in Word Documents in Java

Beware of calling long running native code together with high throughput workload in the same JVM