Enforce architecture / layering with tests #12

Closed
opened 2026-06-27 00:06:23 +02:00 by timothy · 1 comment
Owner

Risk this addresses

The project's value comes from a clean layering (Core = domain with no infrastructure deps, Application = MediatR handlers, Infrastructure[.Sqlite/.MySql] = EF Core, FFmpeg/Scanner = process/scan). As features are added, that boundary erodes one "just import it here" at a time — which is what eventually forces big restructurings. Today nothing enforces it.

Approach

Add a small test project using NetArchTest.Rules (or equivalent) asserting dependency direction, run in CI:

  • Core must not depend on Application, Infrastructure*, EF Core, or MediatR handlers.
  • Application must not depend on Infrastructure.Sqlite/Infrastructure.MySql concretes (only abstractions).
  • UI/host must not reach into Infrastructure directly, etc.
  • Optionally: handlers live in Application, naming conventions for commands/queries.

Payoff

Layering violations fail the build instead of accumulating. Encodes the rules the contributors guide (#10) documents, so the guide and the build agree. Backlog.

## Risk this addresses The project's value comes from a clean layering (`Core` = domain with **no** infrastructure deps, `Application` = MediatR handlers, `Infrastructure[.Sqlite/.MySql]` = EF Core, `FFmpeg`/`Scanner` = process/scan). As features are added, that boundary erodes one "just import it here" at a time — which is what eventually forces big restructurings. Today nothing enforces it. ## Approach Add a small test project using **NetArchTest.Rules** (or equivalent) asserting dependency direction, run in CI: - `Core` must not depend on `Application`, `Infrastructure*`, EF Core, or MediatR handlers. - `Application` must not depend on `Infrastructure.Sqlite`/`Infrastructure.MySql` concretes (only abstractions). - UI/host must not reach into `Infrastructure` directly, etc. - Optionally: handlers live in `Application`, naming conventions for commands/queries. ## Payoff Layering violations fail the build instead of accumulating. Encodes the rules the contributors guide (#10) documents, so the guide and the build agree. Backlog.
Author
Owner

Done — closing

Merged via PR #27; full main pipeline green (test + image build).

What was done

  • New ErsatzTV.Architecture.Tests project (NUnit) using NetArchTest.eNhancedEdition asserting the layer dependency direction. Runs via the existing dotnet test in CI — no workflow change.
  • Rules (encode the intended layering, verified against today's reference graph):
    1. CoreApplication / Infrastructure* / Scanner
    2. CoreMicrosoft.EntityFrameworkCore / Pomelo / Microsoft.Data.Sqlite / Dapper
    3. FFmpeg (lowest layer) ↛ any other ErsatzTV layer
    4. Application ↛ the concrete Infrastructure.Sqlite / Infrastructure.MySql providers (only the Infrastructure abstraction)
    5. InfrastructureApplication or the concrete providers

Files: ErsatzTV.Architecture.Tests/ (new project + LayeringTests.cs), Directory.Packages.props (NetArchTest version), ErsatzTV.sln.

Deviation (stated): used NetArchTest.eNhancedEdition instead of the issue's literal NetArchTest.Rules — the original is unmaintained since 2021 and its older Mono.Cecil doesn't reliably parse .NET 10 assemblies; the fork is maintained with the same API.

Verification: 5/5 tests pass; full-solution Release build 0/0. Proven non-vacuous — a temporary negative control (asserting the known-true Infrastructure → Core dependency is forbidden) failed as expected and listed the real offending types, confirming the assemblies load and the rules have teeth.

Docs: the enforced layering will be documented in the contributors guide (#10, intentionally done last so it reflects the now-enforced rules — pairs with this issue as noted in the original).

## Done — closing Merged via PR #27; full `main` pipeline green (test + image build). **What was done** - New `ErsatzTV.Architecture.Tests` project (NUnit) using **NetArchTest.eNhancedEdition** asserting the layer dependency direction. Runs via the existing `dotnet test` in CI — no workflow change. - Rules (encode the intended layering, verified against today's reference graph): 1. `Core` ↛ `Application` / `Infrastructure*` / `Scanner` 2. `Core` ↛ `Microsoft.EntityFrameworkCore` / `Pomelo` / `Microsoft.Data.Sqlite` / `Dapper` 3. `FFmpeg` (lowest layer) ↛ any other ErsatzTV layer 4. `Application` ↛ the concrete `Infrastructure.Sqlite` / `Infrastructure.MySql` providers (only the `Infrastructure` abstraction) 5. `Infrastructure` ↛ `Application` or the concrete providers **Files**: `ErsatzTV.Architecture.Tests/` (new project + `LayeringTests.cs`), `Directory.Packages.props` (NetArchTest version), `ErsatzTV.sln`. **Deviation (stated)**: used **NetArchTest.eNhancedEdition** instead of the issue's literal `NetArchTest.Rules` — the original is unmaintained since 2021 and its older Mono.Cecil doesn't reliably parse .NET 10 assemblies; the fork is maintained with the same API. **Verification**: 5/5 tests pass; full-solution Release build 0/0. **Proven non-vacuous** — a temporary negative control (asserting the known-true `Infrastructure → Core` dependency is forbidden) failed as expected and listed the real offending types, confirming the assemblies load and the rules have teeth. **Docs**: the enforced layering will be documented in the contributors guide (**#10**, intentionally done last so it reflects the now-enforced rules — pairs with this issue as noted in the original).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: timothy/ersatztv#12