Modularity is not reviewed after the fact — it is enforced by the shape of the repository. Every repository must fit exactly one of three archetypes. There is no fourth option and no hybrid.
1 — Atom
The smallest possible unit of granularity. If a component can be broken down further, it is not an atom.
- Structure: flat. A single level of directory inside the source root.
- Rule: no subdivisions. It fulfils one specific contract.
- Test: can you describe it with one sentence that has no “and”?
- Examples: a value object, a string utility, a single HTTP client, a contract-only package.
2 — Assembly
A connection point. Assemblies contain no logic; they orchestrate the union of atoms so that one dependency declaration installs ten packages.
- Structure: no source code. Composition is declared in the package manifest.
- Rule: if it has behaviour, it is not an assembly.
- Test: can you delete its manifest and have nothing left but a README?
- Examples: a meta-package that bundles the HTTP atoms, a “commons” bundle.
3 — Application
The living system. Not a library, but the implementation of the business heart, following Clean Architecture and DDD.
- Structure: domain, application, infrastructure, interface — dependencies pointing inwards.
- Rule: stay lean. Anything generic is pushed down into atoms.
- Test: does it contain a use case that a business expert would recognize?
- Examples: a payment gateway, an identity service, a kanban board.
The same idea in four ecosystems
The names of the artifacts change; the shapes do not.
| Shape | PHP / Composer | C# / NuGet | Java / Maven | C++ / Conan |
|---|---|---|---|---|
| Atom | type: library, one namespace | class library, one namespace | jar, one package | header + library, one namespace |
| Assembly | type: metapackage, only require | package with no compiled code, PackageReference only | pom with <dependency> only, no sources | conanfile that only requires |
| Application | type: project, src/Domain, src/Application… | executable / web project with layer folders | bootable app with layer modules | executable target with layer directories |
How to classify a repository
Ask the three questions in order and stop at the first “yes”.
| Question | If yes |
|---|---|
| Does it contain use cases and business rules? | Application |
| Does it contain only dependency declarations? | Assembly |
| Does it fit in one sentence with one responsibility? | Atom |
If none of the answers is “yes”, the repository is not ready to exist. That is not a stylistic judgement; it is the signal that the boundary has not been found yet.
Why three, and not more
More shapes mean more places to hide. A fourth archetype — “shared utilities”, “common”, “core” — always becomes the place where unrelated things accumulate until nobody can delete anything.
Three shapes also give a mechanical rule for leaks: code that belongs to an atom but lives in an application will eventually be copied into another application. The taxonomy makes that leak visible at review time, because the reviewer can ask a single question: does this file belong to this shape?
Enforcement
A taxonomy that lives in a document is a suggestion. To enforce it, put the shape in machine-readable metadata and check it in CI:
- PHP:
composer.json→typemust belibrary,metapackageorproject. - C#: an assembly must have no
.csfiles; the.csprojshould beMicrosoft.NET.Sdkwith no compile items. - Java: an assembly module must have an empty
src/main/java. - C++: an assembly is headers-only or absent; the
conanfilemust declarerequiresand nosources().
For the application shape, the check is structural: assert that the domain layer has no import of infrastructure, framework or database packages. Every ecosystem has a tool for that — deptrac (PHP), NetArchTest (C#),ArchUnit (Java), include-what-you-use plus build-graph rules (C++).