I can't name exact sources, but I feel like ever since I started learning how to program, I was reading tales and legends about "reusable" components, modules and so on. It was not until recently, that something clicked in my head and I found this concept pretty basic. In this text, I'll try to shed some light on creating components and making them reusable. Obviously, it won't be any close to complete, rather an inspiration to explore further.
What Is a Component?
You can find a lot of definitions and approaches to defining a component. For the sake of this article, I'll define it like this:
Component is a class or bunch of classes with cohesive responsibilities that form a larger whole.
Now, for some of you, this will be valid, whereas some may say that I'm actually defining a module, and some of you might want to nitpick other details in the definition. For the second group, my excuse is that the difference between a component and a module is very blurry (try Googling it; everyone seems to have their own understanding). For others, the comment section awaits!
What Goes Inside a Component?
Let's focus on the middle part of my definition — cohesive responsibilities. I'd describe this as the likelihood of changes happening to two classes together along with their conceptual cohesion. To provide a quick example, we can imagine that the
MailSender classes won't have too much in common in a typical project, so they won't be in the same component.
MailSender on the other hand seem to complement each other — every new piece of information
MailSender will have to reflect in the actual email sent. Therefore
MailSender are good candidates to be in the same component.
Does that mean imaginary
MailRepository will end up in the "mail component," too? There are consequences, so it depends. In my recent project, I decided to keep controllers inside components and so far I haven't experienced any issues. There's a great text by Simon Brown that gives some insight into his understanding of Layers, hexagons, features, and components. I definitely recommend reading this one, before you make up your mind for good. I also recommend you experiment on your own, because projects vary and so do the best solutions.
Creating a Component
Having identified two classes that are more cohesive to each other than to the rest, we should put them together — we want component structure to be explicit, not hidden in the forest of other things, not imaginary in the mind of an architect. We have multiple means of doing that - packaging, Maven modules, Java 9 modules, OSGi, etc. In most projects, this implies that we should seek packaging (or "modularization") by functionality, rather than by object type (see also: Package Structure).
Encapsulating a Component
Oh, I could write some cool stuff about component encapsulation, but it so happens that I already did. Make sure to check out Java Encapsulation for Adults, if you haven't already. TLDR; Use expressive packaging and/or visibility modifiers and interfaces to encapsulate your component's internals (and save your ass at the dance party).
Reusing a Component
To reuse a component, we just include it in our project (if necessary) and use its public API. It's so simple and yet we rarely hear about great systems built from reusable components. More often we hear about the dreaded "BIG BALL OF MUD." Why?
I believe it's not (yet) mathematically proven, but a component's reusability is inversely proportional to the amount of magic it uses and dependencies it requires. Since poor dependency management and magic seem to be ubiquitous in software, reusability tends to suffer. We'll work through this theorem using an example. Let's take a look at the git component from my blogging platform project.
I claim that this component keeps track of a configured Git repository by exposing an endpoint to notify it about changes and allows its clients to read from the repository. What can we say about this component's reusability?
Firstly, it's reusable inside this project, as long as we need to keep track of only one repository. If there were more repositories to track, we would need to change both service's and component's implementation.
Secondly, it might be reusable in other projects using Spring, but we would have to do a few things:
- Deploy it as an artifact.
- Bundle it with the
ExceptionUtilsclass that it's using, which might involve copying the class or setting a dependency on utils artifact.
- Make sure the other application is using a compatible version of Spring — example of how a dependency can lower reusability.
Thirdly, it might be reusable in other projects that are not using Spring, but we would have to:
- Deploy it with the necessary dependencies (same as the first two criteria above).
- Remove the Spring dependency (remove the controller, remove Spring's annotations from the service, expose lifecycle methods through component's interface) — example of how unwanted dependency and magic lifecycle can lower reusability.
Lastly, in this example, the only in-project dependency is a utility class, which is fine. Imagine what would happen if this component had a dependency on a more project-specific class e.g.
Post. Anybody who'd like to use this component would have to declare a dependency on blogging-specifc stuff or reimplement non-configuration logic in the blogging platform. Therefore, watch for project-specific dependencies in technical components like this example one!
Enough. I think that two things should be really clear about the example component:
- The current implementation is not very reusable.
- It has a lot of potential for reusability improvements.
HAHAHAHAHA! GRZEGORZ, YOU FAILED! TIDY JAVA? BS! NON-REUSABLE JAVA! GO AND REWRITE, HAHAHA!
No, no, no. YAGNI boy, I'd rather KISS a component than make it maximally reusable "just in case." Look at how much complexity could changes mentioned above introduce. You don't want that complexity, until absolutely necessary. Therefore, the key takeaway of this part should be:
Keep your components potentially reusable, so that's it's possible to reuse a component either directly or with a small change. Don't shape your code around unnecessary reusability.
By defining a component as a class or bunch of classes with cohesive responsibilities, we can move the term from the world of tales and legends to reality. We grab conceptually cohesive classes that might change for the same reasons and put them together into a package, module or whatever else we're using. A component should be well encapsulated to encourage loose coupling. Component's reusability is inversely proportional to the amount of magic it uses and dependencies it requires. We should acknowledge this truth, but not use it as a reason to actively fight every reusability flaw until we really need it — good software design rules still apply. Happy applying!