Validate Dependencies According to Clean Architecture
You can use dependency-cruiser to keep the clean architecture through the evolution of your code
In my previous article, I explained how to convert (transform) your React-based code into clean architecture.
Your next challenge is to keep the clean architecture through the evolution of your code. This tends to be difficult when multiple members work as a team (especially when the team includes newbies). But here is some good news. Just like using lint to maintain the source code format, we can introduce a tool to keep the most important rule of clean architecture: the dependency rule, which says that source code dependencies can only point upwards (one-way).
For example, my previous article showed the following dependency graph for a TicTacToe program. Here is the entire source code of this example. Let’s see how the tool can protect the clean architecture through code evolution.

Getting Started With Dependency-Cruiser
dependency-cruiser is a tool that analyzes all your source files and validates dependencies among files.
You can easily install the tool and initialize its configuration file (dependency-cruiser.js
) for your repo as follows:
$ yarn add dependency-cruiser --dev
$ npx depcruise --init
Now it’s time to validate our source codes. Phew… I’m relieved to see there are no violations.
$ npx depcruise src --include-only '^src' --config --output-type err-long
✔ no dependency violations found (25 modules, 38 dependencies cruised)
But there is more. Combined with Graphviz, dependency-cruiser generates a cool dependency graph.
$ npx depcruise src --include-only '^src' --config --output-type dot | dot -T svg > dependency-graph.svg && open dependency-graph.svg
Cool! You can see all dependencies are aligned with your expectations.

Since we would use those two commands frequently, it’s a good idea to make scripts for them in your package.json
.
"scripts": {
"depcruise:validate": "depcruise src --include-only '^src' --config --output-type err-long",
"depcruise:tree": "depcruise src --include-only '^src' --config --output-type dot | dot -T svg > dependency-graph.svg && open dependency-graph.svg"
}
Then let’s see how dependency-cruiser detects invalid dependencies. We add the following invalid line in src/Domain/UseCase/ClickOnBoardUseCase.ts
.
// src/Domain/UseCase/ClickOnBoardUseCase.ts
import { TicTacToeView } from "../../Presentation";
Since the Presentation layer is lower than the Domain layer, this import
statement should be detected as an error. Voilà! Dependency-cruiser gives us a detailed analysis in text and a crystal clear graph.
$ yarn depcruise:validate
yarn run v1.22.17
$ depcruise src --include-only '^src' --config --output-type err-long
warn no-circular: src/Presentation/hook/useTicTacToeModelController.ts →
src/Domain/UseCase/index.ts →
src/Domain/UseCase/ClickOnBoardUseCase.ts →
src/Presentation/index.ts →
src/Presentation/TicTacToeView.tsx →
src/Presentation/hook/useTicTacToeModelController.ts
This dependency is part of a circular relationship. You might want to
revise your solution (i.e. use dependency inversion, make sure the modules
have a single responsibility)
warn no-circular: src/Domain/UseCase/ClickOnBoardUseCase.ts →
src/Presentation/index.ts →
src/Presentation/TicTacToeView.tsx →
src/Domain/UseCase/index.ts →
src/Domain/UseCase/ClickOnBoardUseCase.ts
This dependency is part of a circular relationship. You might want to
revise your solution (i.e. use dependency inversion, make sure the modules
have a single responsibility)
✖ 2 dependency violations (0 errors, 2 warnings). 25 modules, 39 dependencies cruised.
✨ Done in 0.81s.
$ yarn depcruise:tree
yarn run v1.22.17
$ depcruise src --include-only '^src' --config --output-type dot | dot -T svg > dependency-graph.svg && open dependency-graph.svg
✨ Done in 1.08s.

Dependency-cruiser detects this line as a violation against the “no-circular” rule predefined in the configuration file (dependency-cruiser.js) as follows:
forbidden: [
{
name: "no-circular",
severity: "warn",
comment:
"This dependency is part of a circular relationship. You might want to revise " +
"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ",
from: {},
to: {
circular: true,
},
},
...
]
In this way, the default configuration file already has generic rules, and you can detect violations such as no-circular.
Customize Rules for Clean Architecture
The default “no-circular” rule is a good start, but we need more strict dependency checking than generic rules. For example, can we detect the following invalid case?
// src/Presentation/TicTacToeView.tsx
import { RepositoryImpl } from "../Data";
According to our design policy, we don’t want any direct dependency between Presentation and Data, but the default configuration cannot detect this invalid dependency since it does not cause any circular. See the dependency link with purple marking below:

We need to declare that Presentation can only depend on Domain as follows:
allowedSeverity: "error",
allowed: [
{
from: { path: "(^src/Presentation)" },
to: { path: ["^src/Domain"] },
},
]
With this new configuration, you observe the dependency link from Presentation/TicTacToeView.tsx
to Data/index.ts
is labeled as “not-in-allowed.” Great! Now dependency-cruiser detects this dependency as a violation of our new rule.

But you may notice that other valid dependencies are also labeled as “not-in-allowed.” If you use “allowed” rules, dependency-cruiser
will emit a “not-in-allowed” message for each dependency that does not satisfy at least one of them. This means you need to specify all valid dependencies explicitly now. Although it seems cumbersome at first, it is a good habit to design dependencies and add new rules whenever you introduce new folders in your repo.
Here is the final configuration file:
allowedSeverity: "error",
allowed: [
{
from: { path: "(^src/Main)" },
to: {
// “$1” is introduced from regular expression’s “group matching”.
// You can reference the part matched between brackets in “from”
// string by using “$1” in “to”.
path: ["^$1", "^src/Presentation", "^src/Data", "^src/Domain"],
},
},
{
// Presentation other than Presentation/hook
from: { path: "(^src/Presentation)", pathNot: "^src/Presentation/hook" },
to: { path: ["^$1", "^src/Domain"] },
},
{
// We want no hooks to depend on Presentation to make hooks independent
// from any graphical presentation
from: { path: "(^src/Presentation/hook)" },
to: { path: ["^$1", "^src/Domain"] },
},
{
from: { path: "(^src/Data)" },
to: { path: ["^$1", "^src/Domain"] },
},
{
from: { path: "(^src/Domain/UseCase)" },
to: { path: ["^$1", "^src/Domain/Repository", "^src/Domain/Model"] },
},
{
from: { path: "(^src/Domain/Repository)" },
to: { path: ["^$1", "^src/Domain/Model"] },
},
{
from: { path: "(^src/Domain/Model)" },
to: { path: ["^$1"] },
},
{
// Files outside of established folders (e.g. src/index.tsx) can reference
// any files.
from: {
pathNot: ["^src/Main", "^src/Presentation", "^src/Data", "^src/Domain"],
},
to: {},
},
],
This comprehensive definition of valid dependencies gives you the following error and graph:
$ yarn depcruise:validate
yarn run v1.22.17
$ depcruise src --include-only '^src' --config --output-type err-long
error not-in-allowed: src/Presentation/TicTacToeView.tsx → src/Data/index.ts
✖ 1 dependency violations (1 errors, 0 warnings). 25 modules, 39 dependencies cruised.
error Command failed with exit code 1.

Visualize Dependencies in Big Repo
We have visualized a dependency graph showing all files. This is good when your repo is small. But soon, your repo will include dozens and hundreds of files once your team members start working on the same repo. And you would want to visualize the whole repo on a higher level to check the dependency links.
Dependency-cruiser provides a variant of graph (”ddot”) that summarizes modules on the folder level, and you can customize it with themes and filters. See the following high-level graph for the same repo.
$ npx depcruise src --include-only '^src' --config --output-type ddot | dot -T svg > dependency-graph.svg && open dependency-graph.svg

Integrate Dependency Validation in Git
When multiple members work on your repo, keeping the dependencies designed by clean architecture is hard. It is a good idea to run dependency-cruiser
whenever any member tries to commit changes.
Let’s use husky to automate dependency checking before any commit and prohibit the commit if a dependency violation is found.
$ npx husky-init && yarn
$ npx husky add .husky/pre-commit 'yarn depcruise:validate'
Once this precommit hook is configured, nobody can make commits that violate dependencies. For example, the following shows how you are notified of a violation when you try to make an invalid commit.
$ git commit -m 'chore: intentinal violation of dependencies'
yarn run v1.22.17
$ depcruise src --include-only '^src' --config --output-type err-long
error not-in-allowed: src/Presentation/TicTacToeView.tsx → src/Data/index.ts
✖ 1 dependency violations (1 errors, 0 warnings). 25 modules, 39 dependencies cruised.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
husky - pre-commit hook exited with code 1 (error)
You can see the final repo here.
Conclusion
I showed how to validate your repo from a clean architecture viewpoint. Dependency-cruiser is a powerful tool that can be integrated into your git repo’s precommit hook. That way, you can keep your repo aligned with clean architecture’s dependency rules. I hope this tool will help you to maintain your projects.