Enforcing Clean Architecture
Ensures strict inward dependency flow between layers: only Presentation -> Application -> Domain. Flags outward or sideways dependencies (e.g. Domain -> Infrastructure).
How to fix
1) Move business rules used by multiple layers into Domain or Application. 2) Replace direct references to outer layers with abstractions defined in an inner layer. 3) Implement those abstractions in Infrastructure/Presentation, injected from the composition root. 4) Move DTOs/view models to their boundary layer; map at boundaries instead of sharing concrete outer-layer types.
CQLinq source
This rule is written in CQLinq, C# LINQ over the NDepend code model. It ships with every new NDepend project: you can read it, adapt it, or use it as a starting point for your own custom rules.
// <Name>Enforcing Clean Architecture</Name>
// <Id>ND1412:EnforcingCleanArchitecture</Id>
warnif count > 0
let getLayerTypes = new Func<string, HashSet<IType>>(
names => names.Split('|').SelectMany(name =>
Application.Namespaces.Where(n => n.Name.EndsWith("." + name) || n.Name.Contains("." + name + ".")).ChildTypes()
.Concat(Application.Assemblies.Where(a => a.Name.EndsWith("." + name)).ChildTypes())
).ToHashSetEx()
)
// Define layers
let layers = new[] {
new { Name = "Domain",
Types = getLayerTypes("Domain|Entities") },
new { Name = "Application",
Types = getLayerTypes("Application|UseCases") },
new { Name = "Infrastructure",
Types = getLayerTypes("Infrastructure|Persistence") },
new { Name = "Presentation",
Types = getLayerTypes("Presentation|UI|API|Web") }
}
// Define forbidden dependencies as anonymous types
let forbiddenDeps = new[] {
new { From = "Domain", To = "Application" },
new { From = "Domain", To = "Infrastructure" },
new { From = "Domain", To = "Presentation" },
new { From = "Application", To = "Infrastructure" },
new { From = "Application", To = "Presentation" },
new { From = "Infrastructure", To = "Presentation" }
}
// Search for forbidden dependencies
from dep in forbiddenDeps
let sourceLayer = layers.First(l => l.Name == dep.From)
let targetLayer = layers.First(l => l.Name == dep.To)
from tUser in sourceLayer.Types.UsingAny(targetLayer.Types)
let typesUsed = targetLayer.Types.Intersect(tUser.TypesUsed)
from tUsed in typesUsed
select new {
tUser,
tUsed,
error = dep.From + " must not use " + dep.To,
Debt = 7.ToMinutes().ToDebt(),
Severity = Severity.High
}
// <Explanation>
// The {0} shouldn't use {1} because {2}
// </Explanation>
// <Desc>
// Ensures strict inward dependency flow between layers: only
// Presentation -> Application -> Domain. Flags outward or sideways
// dependencies (e.g. Domain -> Infrastructure).
// </Desc>
// <HowToFix>
// 1) Move business rules used by multiple layers into Domain or Application.
// 2) Replace direct references to outer layers with abstractions defined in
// an inner layer.
// 3) Implement those abstractions in Infrastructure/Presentation, injected
// from the composition root.
// 4) Move DTOs/view models to their boundary layer; map at boundaries
// instead of sharing concrete outer-layer types.
// </HowToFix>
Open in the interactive Rules Explorer · All default rules
Architecture: all 13 items
- Avoid namespaces mutually dependent ND1400
- Avoid namespaces dependency cycles ND1401
- Avoid partitioning the code base through many small library Assemblies ND1402
- Enforcing Clean Architecture ND1412
- UI layer shouldn't use directly DB types ND1403
- UI layer shouldn't use directly DAL layer ND1404
- Assemblies with poor cohesion (RelationalCohesion) ND1405
- Namespaces with poor cohesion (RelationalCohesion) ND1406
- Assemblies that don't satisfy the Abstractness/Instability principle ND1407
- Avoid excessive class coupling ND1410
- Higher cohesion - lower coupling ND1408
- Avoid mutually-dependent types ND1409
- Example of custom rule to check for dependency ND9900