{
  "id": 4583313,
  "title": "Enforcing Modular Monolith Boundaries in .NET: NDepend, Parallel Pipelines, and the Architecture That Holds",
  "url": "https://urgent.news/2026/08/31/enforcing-modular-monolith-boundaries-in-net-ndepend-parallel",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-08-31T06:03:32.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/aloknecessary/enforcing-modular-monolith-boundaries-in-net-ndepend-parallel-pipelines-and-the-architecture-37e1"
  },
  "original_language": "en",
  "account": "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.\n\nTo 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.\n\nFour 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.\n\nTo 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.",
  "summary": "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…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}