Urgent.News

What's breaking now, across thousands of outlets.

Tech

Enforcing Modular Monolith Boundaries in .NET: NDepend, Parallel Pipelines, and the Architecture That Holds

A modular monolith without enforcement is not an architecture — it is a monolith with good intentions. The Problem Most teams skip the modular monolith and jump straight to microservices. The ones that do attempt a modular monolith rely on convention — "don't cross module boundaries" — which fails the moment deadlines hit. The difference between a well-structured modular monolith and a mess is…

A modular monolith, when not enforced, does not constitute a real architecture; it remains a monolith with good intentions. Many teams bypass the modular monolith concept and immediately transition to microservices. Those that attempt a modular monolith often rely on convention - "don't cross module boundaries" - which fails as deadlines approach. The key difference between a well-structured modular monolith and a chaotic one lies in whether boundaries are maintained by tooling or convention alone.

To address this issue, a clear structure is recommended: each module is composed of two .NET projects - src/Modules/Orders/YourApp.Orders/ (internal: domain, application, infrastructure) and src/Modules/Payments/YourApp.Payments/ (internal: domain, application, infrastructure) - with src/Modules/Payments/YourApp.Payments.Contracts/ for public DTOs, interfaces, and events.

The rule is simple: modules may only reference each other's *.Contracts projects. This project reference enforces the physical limitation, preventing any type access when no project reference exists.

Four layers of enforcement are employed: the compiler (project references), NetArchTest (architecture tests), NDepend CQLinq (dependency cycles and coupling), and Quality Gates (block PRs introducing new boundary violations). Furthermore, each module owns a dedicated DbContext with a schema prefix (orders.*, payments.*), ensuring no module queries another module's tables.

Cross-module communication occurs via MediatR in-process events, with OrderPlaced published by Orders and consumed by Payments, without Orders needing to know Payments exists. This pattern will also serve as the extraction seam when a module eventually transitions into a service. The event contract remains unchanged throughout this process.

To expedite development, a parallel CI strategy is implemented, running module tests in parallel. CI time scales with the slowest module rather than the total count. The extraction path is straightforward: when a module genuinely requires independence, add an outbox table, publish to a real broker, replace MediatR handlers with broker consumers, deploy the module as a separate service, and publish *.Contracts as a NuGet package.

At this point, the boundary remains clean, and extraction amounts to a deployment change, not a redesign. This implementation guide covers NDepend CQLinq rule examples, Quality Gate configuration, GitHub Actions pipeline YAML, test isolation patterns, and a production checklist.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

Hyundai Card and Hyundai Commercial Open 2026 Graduate Recruitment, with Roles Chosen After Hiring

Hyundai Card and Hyundai Commercial have opened their 2026 graduate recruitment program, adopting a unified selection process that allows successful candidates to choose their specific roles after…

  • Hyundai Card and Hyundai Commercial launch 2026 graduate recruitment program
  • Applicants choose roles after employment based on comprehensive understanding of functions
  • Selection process includes application review, written exam, and two rounds of interviews

More from Monday 31 August →