Skip to content

clean-architecture-layers

Enforce dependency direction in clean architecture — inner layers must not reference outer layers.

Clean architecture mandates that dependencies point inward: API → Application → Domain. The Domain layer must have zero external dependencies, the Application layer must not reference Infrastructure directly, and no lower layer should reference the API project. This rule checks using directives (C#) or import statements to enforce these boundaries.

The same pattern applies to any layered architecture in any language — adjust the import patterns and layer paths accordingly.

Domain/Entities/User.cs
using Microsoft.EntityFrameworkCore; // ✗ Domain → Infrastructure
using StavangerChallenge.Infrastructure; // ✗ Domain → Infrastructure
Application/Services/UserService.cs
using StavangerChallenge.Infrastructure; // ✗ Application → Infrastructure
using StavangerChallenge.API; // ✗ Application → API
Domain/Entities/User.cs
namespace StavangerChallenge.Domain.Entities;
// No external dependencies — pure domain logic
Application/Services/UserService.cs
using StavangerChallenge.Domain.Entities; // ✓ Application → Domain
// Uses IRepository<T> interface, not Infrastructure directly
/// <reference path="../rules.d.ts" />
export default {
rules: {
"no-infrastructure-in-domain": {
description:
"Domain layer must not reference Infrastructure or ORM packages",
async check(ctx) {
const domainFiles = await ctx.glob("backend/src/**/Domain/**/*.cs");
for (const file of domainFiles) {
const matches = await ctx.grep(
file,
/using\s+(Microsoft\.EntityFrameworkCore|MyApp\.Infrastructure)/
);
for (const match of matches) {
ctx.report.violation({
message: `Domain must not reference Infrastructure: "${match.content.trim()}"`,
file: match.file,
line: match.line,
fix: "Remove this using directive. Domain entities should have zero external dependencies.",
});
}
}
},
},
"no-infrastructure-in-application": {
description:
"Application layer must not reference Infrastructure directly",
async check(ctx) {
const appFiles = await ctx.glob("backend/src/**/Application/**/*.cs");
for (const file of appFiles) {
const matches = await ctx.grep(file, /using\s+MyApp\.Infrastructure/);
for (const match of matches) {
ctx.report.violation({
message: `Application must not reference Infrastructure: "${match.content.trim()}"`,
file: match.file,
line: match.line,
fix: "Define an interface in Application and implement it in Infrastructure.",
});
}
}
},
},
"no-upward-api-references": {
description: "No layer should reference the API project",
async check(ctx) {
const lowerLayerPatterns = [
"backend/src/**/Domain/**/*.cs",
"backend/src/**/Application/**/*.cs",
"backend/src/**/Infrastructure/**/*.cs",
];
for (const pattern of lowerLayerPatterns) {
const files = await ctx.glob(pattern);
for (const file of files) {
const matches = await ctx.grep(file, /using\s+MyApp\.API/);
for (const match of matches) {
ctx.report.violation({
message: `Lower layers must not reference the API project: "${match.content.trim()}"`,
file: match.file,
line: match.line,
fix: "Dependencies flow inward: API → Application → Domain.",
});
}
}
}
},
},
},
} satisfies RuleSet;

For TypeScript projects, adapt the patterns:

// Check that domain/ does not import from infrastructure/
/import\s+.*from\s+["'].*\/infrastructure\//
// Check that no layer imports from api/
/import\s+.*from\s+["'].*\/api\//

When your project follows clean architecture, hexagonal architecture, or any layered architecture where dependency direction must be enforced.

When your project does not use a layered architecture, or when layers are not organized into distinct directories.