Why Do You Need To Know Package Cohesion Fundamentals?
View originalCrawl details
- best
Summary (TL;DR)
The article applies package cohesion principles from Bob Martin's "Granularity" paper to JavaScript file organization. It argues that since the File System acts as the package system in JavaScript, directories can be treated as packages and files as classes. Three principles apply: the Common-Reuse Principle (files reused together belong in the same package), the Common-Closure Principle (files that change together belong together), and the Reuse/Release Equivalence Principle (whatever is released together can be packaged together). The author refactors a typical Customer Service app that groups files by language (css, js, test directories) into packages grouped by feature instead, placing each component's JavaScript, CSS, and tests in one directory. This makes reuse, change impact, and release boundaries clearer, and lets teams take ownership of separate packages without conflicts.
Why Do You Need To Know Package Cohesion Fundamentals?
It's pretty common to find projects where files are separated by the language they're written.
Let's take a very common file structure example for an application called Customer Service:
- The
app.jsandbase-app.cssfiles represent everything that is applicable to the whole application, like base styles, initialization, etc. - The
login-component.jsandlogin-component.cssfiles are responsible for the root component that will render the login page of the app. - The
header-component.cssandheader-component.jsfiles represent the main navigation menu which is a header and presents the company's logo.
In JVM-based languages, a package is a concept that represents a way of organizing classes in packages to make them more manageable. The same way that there are principles for software design, there are also principles for organizing packages.
Even though most papers refer to these principles as for "Object Oriented" applications. They can also be applied to a myriad of other paradigms.
In JavaScript, there's no documented concept for a package system. However, if you consider a directory as the package and a JavaScript file as the class, then you can say the package system is the File System and the classes are the files (check this note).
In a JavaScript project, the File System is the Package System
If the File System can be used as the packaging mechanism, and there are principles to make packages more manageable, you can conclude it's possible to use the same principles to organize files.
Packaging principles are grouped between cohesion and coupling. However, for the purpose of this post, I'll focus on how to refactor the file structure to increase cohesion.
The Common-Reuse Principle (CRP) states that:
The classes in a package are reused together. If you reuse one of the classes in a package, you reuse them all.
— The “Granularity” paper by Bob Martin (Uncle Bob)
In the Customer Service file structure, you’ll never use the login-component.js and the header-component.js JavaScript files together. They can exist on the same page visually, but their functionality will never be used simultaneously for the same purpose.
According to CRP, they should exist in separate packages:
The header-component.css and the login-component.css styles are used together only with their related JavaScript files. Therefore, they should also be packed together:
If it’s not organized like this, then it's hard for you to look at the contents of the package and know which related functionality of the files are afforded by the system to be reused together.
Without thinking about common reuse, it's hard for you to look at the contents of a package and know which related files can be reused together.
The Common-closure principle (CCP) states that:
The classes in a package should be closed together against the same kinds of changes. A change that affects a package affects all the classes in that package.
— The “Granularity” paper by Bob Martin (Uncle Bob)
In the Customer Service file structure, if login-component.js changes and the change is related to the style of the component, then it makes sense to have the style and the JavaScript files packed together.
For example, if the style of a “remember me” checkbox is changed in the login-component.js file, the new style will be changed in the existing login-component.css file. That justifies having the files together in the same package due to the common closure.
It’s not a definitive rule that the classes must always change together.
The Granularity paper says:
[…] such that they almost always change together;
You can choose to change the validation of the login-username-field.js without having to change the CSS styles. That still qualifies the login-username-field.css and login-username-field.js to belong together in the login-page package according to CCP, because they almost always change together:
This principle helps you to know what can happen if one of the files is changed and which parts can be related to it. If it’s not organized like this, you’ll have to inspect a lot of different files in different directories to understand the impact a change can have.
Changes in the JavaScript file may require changing the related CSS styles. Therefore, they share a common closure.
The Reuse/Release Equivalence Principle (REP) states that:
The granule of reuse is the granule of release. Only components that are released through a tracking system can be effectively reused. The granule is the package.
— The “Granularity” paper by Bob Martin (Uncle Bob)
This one is unnecessarily complicated.
It’s essentially saying that whatever can be released together can be packed together. The consumer of the package doesn’t need to care about related files elsewhere. Everything inside the package should be related enough to be able to be consumed separately from the rest of the system.
Despite what it looks like, REP is not applicable only to systems that are intended to be released to the public.
In the Customer Service application, there’s only a single team responsible for maintaining every JavaScript file, CSS file, and tests. However, if you separate the pieces that could be released, then you can group the base-app in a separate package and the tests with each of their related files, not in a separate test directory:
This way you can delegate the development ownership of the packages to another team at any time:
- If they end up working in the same repository, the chances of conflict will be drastically reduced.
- If they end up working in a different repository, then it will be very easy to extract all the contents of the package.
At this point of the refactoring work you don't even need the "src" directory anymore:
REP makes the distribution of responsibilities and scaling much easier.
Besides, you can publish the whole directory as an npm package if someday you want to make it available to the public.
It makes sense to pack together whatever can be released together.
If you look carefully, there's a pattern here.
If you organize your directories by feature, instead of by language or technology, and respect the package cohesion principles, you'll be able to understand what can be reused (CRP), what can be changed (CCP) and what can be released separately as necessary (REP).
That allows the File System to be more comprehensible.
The definition of a "feature" is subjective to the project and the company. It depends if it's a terminology that makes sense within the business domain.
A feature can also be separated by more than a "component feature" level:
- The
header-componentcan be put inside anavigationpackage if it turns out there's a need to have multiples ways to navigate through the website. - The
login-pagecan be put inside anauthenticationpackage if it turns out there are multiple ways for the user to log in.
Semantics and organization matter more than meets the eye. They are essential to ensure cohesion between a group of files that form a package.
The Common-Reuse Principle helps you identify how files are reused together.
The Common Closure Principle helps you identify how files are enclosed together.
The Reuse/Release Equivalence Principle helps you to identify how files are grouped by the possibility of being released.
JavaScript has packages as any other language.
But they are not explicit.
It's your responsibility as a developer to spot them.
See also Why Do You Need To Know Package Coupling Fundamentals?
If you liked this, you might like readplace.com, built for exactly this kind of reading.
Thanks for reading. If you have some feedback, reach out to me on LinkedIn, Reddit or Github.