Why Do You Need To Know Package Cohesion Fundamentals? 🔊

Packages exist anywhere, and it's your job to organize them

The picture of many cardboard boxes, piled up in the format of a pyramid

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:

A file structure showing the "src" and "test" directories. Inside the "src", the files are separated by "css" and "js" directories, each of them containing the files pertinent to their respective technologies.
  • The app.js and base-app.css files represent everything that is applicable to the whole application, like base styles, initialization, etc.
  • The login-component.js and login-component.css files are responsible for the root component that will render the login page of the app.
  • The header-component.css and header-component.js files 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:

A file structure showing the login-component.js and header-component.js files extracted to the "login-page" and "header-component" directories, respectively.

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:

A file structure showing the login-component.css and header-component.css files extracted to the “login-page” and “header-component” directories, respectively.

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:

A file structure showing the login-username-field.js and login-username-field.css files extracted to the “login-page” directory.

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:

A file structure showing the base-app.css and app.js files moved to their own "base-app" directory. Also, the tests are moved from the "test" directory to their respective packages closer to the source files.

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:

The file structure with all the files separated into their own packages, including the tests and the CSS files. There are no "test" or "src" directories.

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-component can be put inside a navigation package if it turns out there's a need to have multiples ways to navigate through the website.
  • The login-page can be put inside an authentication package if it turns out there are multiple ways for the user to log in.
The file structure with all the files inside a directory called "customer-service". The base-app.css and app.js are located in the root level of it. The other files are inside the "authentication" and "navigation" feature directories.

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?

Thanks for reading. If you have some feedback, reach out to me on Twitter, Facebook or Github.