I’ve recently been working on a couple of new applications and wondering, why is it that even though they are greenfield is there something that makes them a bit to awkward to work with? I’m sure you’ve experienced these kinds of problem once or twice. You know there’s a class that does something but can you find it. You want to make a refactor but you can’t quite get everything to line up where you want it. Trying to reorganise the codebase but discovering that what looks nice on the whiteboard won’t work in real life because there’s this thing that will create a circular dependency.
After a bit of reflection I think that it’s our packaging strategy that’s causing these problems. I wonder if yours is similarly troublesome.
Now when I say strategy I’m at risk of aggrandisement. If you’re anything like me decisions on packaging happen on autopilot. Something that we learn through exposure to different code bases tells us to pop all the same kind of things in packages together and when a package is getting a bit cluttered in the ide, move a few things that either aren’t too important or there are lot of into a sub package. This will lead us to a code based that’s structured like this:
com.blah.awesomeapp.application
AwesomeApplication
com.blah.awesomeapp.controller
AlphaController
BetaController
DeltaController
GammaController
utils
DateConversion
com.blah.awesomeapp.exceptions
TooManyParticlesException
InvalidAlphaSettingException
NotAwesomeEnoughException
InvalidTLAException
ProcessingException
com.blah.awesomeapp.service
AlphaService
BetaService
DeltaService
GammaService
helpers
GammaHelper
DeltaConstants
BetaThingyFactory
com.blah.awesomeapp.repository
AlphaRepository
BetaRepository
DeltaRepository
GammaRepository
com.blah.awesomeapp.model
Alpha
BitOfAlpha
ComponentOfBeta
SomeOfDelta
PartOfGamma
Ion
Thingy
TLA
OddConstant
For a long time I’ve thought nothing of this approach that I’ll be calling packaging by type in the rest of this post. It’s the way most applications I’ve worked on are structured. It has a nice benefit that you can see all the parts that make up the codebase. Knowing the types of things an application is built from gives us some good clues about its architecture. However, there is a problem when it comes to understanding an application, its a lot like having a pile of jigsaw pieces without the picture that they make up. It’s easy to work out what an individual piece is especially if they are an edge or a corner but that understanding doesn’t necessarily lead to an understanding of the whole thing. Understanding the architecture of an application is not the same as understanding how the application works and understanding how an application works is much more valuable when it comes to making changes or tracking down bugs.
But what if we took a different approach and packaged our application by functionality our example structure from above could be refactored to look like:
com.blah.awesome
Application
DateConversion
com.blah.awesomeapp.alpha
Alpha
BitOfAlpha
AlphaController
AlphaRepository
InvalidAlphaSettingException
AlphaService
com.blah.awesomeapp.beta
Beta
BetaController
BetaRepository
InvalidTLAException
ProcessingException
BetaService
ComponentOfBeta
BetaThingyFactory
TLA
Thingy
com.blah.awesomeapp.delta
DeltaController
DeltaService
DeltaRepository
NotAwesomeEnoughException
DeltaConstants
SomeOfDelta
Ion
com.blah.awesomeapp.gamma
GammaController
GammaService
GammaRepository
TooManyParticlesException
GammaHelper
PartOfGamma
OddConstant
This way all the things that make a functional slice are packaged together, making each function of the application easier to comprehend and in turn the application as a whole becomes easier to work with. If you’re unconvinced try this thought experiment, imagine that you’re writing the package-info.java for the com.blah.awesomeapp.repository package in the first example, what would it include? Now, think about what you’d write in a package-info for the com.blah.awesomeapp.alpha package in the second structure. Which of these gives the most value? Which would you rather find in source code you’ve been given to maintain?
Switch off the autopilot
So far so good but as I mentioned earlier our packaging efforts are a mixture of popping the component in the correct drawer or else hiding things that we don’t care about that much. What happens when my real world functional slice is more complex than my contrived greek letter example and I feel the need to start hiding things in a subpackage or two? At this point resist your instinct and have a think about the right place for the classes to go. I’ve three mantras that I think will help
- Packages are field replaceable units
- Package tangle is a crime
- Classes in subpackages should not depend on anything that’s in their parent package
Packages are field replaceable units
Take a moment come back in time with me, to a land of programming without maven or other dependency management tool, this is where I started my Java development career, rocking the servlets without stackoverflow. In those days we had to workout our own dependency trees, source and zip all the jars needed and provide a handy run command that included the classpath we wanted to use. How happy was I to discover ant and maven? The one good thing about this circus was if it turned out that there was a bug caused by a dependency you could just put a new version in the right place and restart the app and the day has been saved. I’m not advocating a return to these days but this is the origin story for my first mantra. We know that our packaging has worked out well if we could see that code being in a jar of its own that we could change the version that we were using without needing to change anything else. Hopefully, you agree that taking one of the packages from the second structure in to it’s own jar would be an easier task. What is going to make splitting the first one a problem is the amount of interdependencies between packages. This brings us nicely to my second mantra.
Package tangle is a crime
This overstatement stems from a painful time of refactoring. My team were attempting to improve our sonar quality score in order to meet a release threshold handed to us after the development had taken place. One of the biggest problems for us was package tangle, this is where packages depend on other packages which in themselves depend on the first. When packaging by type, we unconsciously adopt the if its public then we can use it rule. An inadvertently create package tangle, which is an almost invisible form of technical debt as it doesn’t cause problems until you need to make a change. In our example above, good developers keeping the code DRY will make use of the DateConversion util we created across the code base. Creating a nice package tangle, if something in the repository package makes use of the DateConversion class we’ve got a problem as it’s likely the controller package depends on the service package that depends on the repository package that depends on the controller package. In this example resolving the tangle is probably not too hard but in real life systems I’ve worked on it has been a real blocker to significant refactoring especially when the dependencies are indirect. Adopting packaging by feature is not going to stop this happening but if we adopt the packages should not use things from their parent package rule we can help avoid problems later
Packages should not use things from their parent package
This isn’t what we normally do, often the things we pop in sub packages are specialisations or implementations of what’s in the parent package, but this means that its hard to replace a subpackage. Consider the beta package from our feature based packaging example. At its current size we’re probably happy to leave it as it is but as we add more classes to the package we’ll start getting into sub-packaging territory.
com.blah.awesomeapp.beta
Beta
BetaController
BetaRepository
InvalidTLAException
ProcessingException
BetaService
ComponentOfBeta
BetaThingyFactory
TLA
Thingy
Size
MapperFactory
BetaRepresentation
BetaByIdRequest
ComponentOfThingy
ThingyThing
BetaRepositoryMongoImpl
TimestampUtils
One place we might start is by noticing that we have an repository interface and implementation so lets try adding a repository subpackage
com.blah.awesomeapp.beta
repository
BetaRepository
BetaRepositoryMongoImpl
This is a great first start but its likely that some of our repository methods will return and accept a Beta instance so we’d need to move Beta and some of its components to that subpackage
com.blah.awesomeapp.beta
repository
BetaRepository
BetaRepositoryMongoImpl
Size
Beta
TLA
ComponentOfBeta
At this point we’d need to know more about the application to decide if a Thingy is a part of a beta, if it is we should probably move its related classes into this package too. That refactoring has moved 5 -10 classes out of our main package so we’re looking in good shape At this point I’d probably move the BetaRepositoryMongoImpl into its own subpackage. Rather than going in com.blah.awesomeapp.beta.repository.mongo I’d place it in com.blah.awesomeapp.beta.mongo, this means we’re not braking the things in subpackages should not depend on things in the parent package rule and we’ll be forced to work out a good public API for our repository and related model classes. This approach hasn’t caused us any problems beta depends on beta.mongo which depends on beta.repository. All these could be in separate jars if the complexity and reuse cases stack up, there’s no package tangle and we can still keep to our rule of subpackages don’t depend on their parent but can depend on the public api of other packages.
You may think that the example below has gone to far or not far enough, I can’t quite decide but the point was to show the principles in action.
com.blah.awesomeapp.beta
repository
BetaRepository
Size
Beta
TLA
ComponentOfBeta
BetaThingyFactory
TLA
Thingy
Size
ComponentOfThingy
ThingyThing
service
ProcessingException
BetaService
InvalidTLAException
controller
BetaController
BetaRepresentation
BetaByIdRequest
mongo
BetaRepositoryMongoImpl
Teams discuss source control, build systems, CD/CI, which languages and frameworks to adopt, spaces over tabs, monolith or microservices, which ides will be supported, which os should be used, which methodology to follow but rarely how the code will be structured. I hope this post has given you a prompt to think about how your codebase is divided into packages and whether its serving as an aid to understanding. If it’s got the cogs whirring maybe its time to have a conversation.