Project Structuring in Modern .NET Applications: Clean Architecture, Vertical Slice, Modular Monolith

1. Clean Architecture

What is Clean Architecture?

Clean Architecture is a layered architectural pattern aimed at creating maintainable, testable, and scalable applications. It organizes codebases into concentric layers with clear separation of concerns, where the core business/domain logic is independent of UI, infrastructure, or external dependencies.

Why use Clean Architecture?

  • Ensures separation of concerns: business rules are isolated from frameworks and UI.
  • Promotes testability and maintainability by decoupling code.
  • Allows independent development and evolution of layers.
  • Facilitates dependency inversion, where outer layers depend on core abstractions, not implementations.
  • Makes it easier to replace or update UI, infrastructure, or data layers without affecting the core.

Typical Layers in Clean Architecture (from innermost to outermost)

Layer
Responsibility
Domain
Core business logic, entities, value objects, domain events, interfaces. No dependencies on other layers.
Application
Use cases, business rules orchestration, CQRS commands/queries, service interfaces. Depends on Domain.
Infrastructure
Implementation of data access, external services, messaging, caching. Depends on Application and Domain.
Presentation
UI, API controllers, UI-specific logic. Depends on Application for use cases.

Example Folder/Project Structure in .NET

MySolution/ |-- MyApp.Domain/ # Entities, value objects, domain events |-- MyApp.Application/ # Use cases, commands, queries, interfaces |-- MyApp.Infrastructure/ # EF Core, repositories, data access implementation |-- MyApp.API/ # ASP.NET Core Web API project with controllers |-- MyApp.Tests/ # Unit and integration tests per layer
  • The Domain does not reference any other projects.
  • The Application references Domain only.
  • The Infrastructure references Application and Domain.
  • The API references Application and Infrastructure at runtime but ideally compiles against Application only for core logic.

How to implement in .NET?

  • Use class library projects for layers (Domain, Application, Infrastructure).
  • Use dependency injection to provide infrastructure implementations to application services.
  • Implement repositories and external service integrations in Infrastructure.
  • Use MediatR for CQRS pattern in Application layer for commands and queries.
  • Controllers in API layer act as user input handlers invoking Application layer.

2. Vertical Slice Architecture

What is Vertical Slice Architecture?

Vertical Slice Architecture organizes the codebase around features or use cases rather than technical layers. Each "slice" or feature contains everything needed end-to-end to handle that feature: UI, validation, business logic, data access, etc.

Why use Vertical Slice?

  • Promotes feature-first modularity, making the codebase more understandable and maintainable.
  • Reduces indecision about where code belongs in traditional layered architecture.
  • Facilitates isolated development and testing of features.
  • Fits well with CQRS since each command and query can be a vertical slice.
  • Improves scalability and refactoring by grouping related logic together.

Typical Structure of a Vertical Slice

For example, in a single vertical slice "CreateOrder":
Slices/ |-- CreateOrder/ |-- Command.cs # Command definition |-- CommandHandler.cs # Command handler business logic |-- Validator.cs # Validation rules |-- DTOs.cs # Data Transfer Objects |-- MappingProfile.cs # AutoMapper configuration if needed
At the root or in a top-level folder, you can organize many vertical slices side by side.

Combining Vertical Slice with Clean Architecture

  • Keep Domain and Infrastructure layers as independent layers.
  • Combine Application and Presentation layers by slicing features vertically.
  • This means each feature folder contains handlers, commands, queries, validators, and sometimes API endpoints.
Example folder structure combining Vertical Slice into Clean Architecture:
MySolution/ |-- Domain/ |-- Infrastructure/ |-- Features/ |-- FeatureA/ |-- Commands/ |-- Queries/ |-- Handlers/ |-- FeatureB/ |-- API/ (or Presentation Layer)

3. Modular Monolith

What is Modular Monolith?

A Modular Monolith is a monolithic application that is carefully modularized into well-defined modules that encapsulate functionality and enforce boundaries internally, as opposed to splitting into distributed microservices.

Why use Modular Monolith?

  • Avoids the complexity and operational overhead of microservices while maintaining modularity.
  • Easier to deploy, test, and debug than distributed systems.
  • Modules can be independently developed, tested, and understood.
  • Encourages strong boundaries and encapsulation within the monolith.
  • Provides a stepping stone architecture for future microservices splitting.

Typical Modular Monolith Structure

  • Application divided into modules by business capabilities.
  • Each module encapsulates domain, application services, and infrastructure relevant to that module.
  • Communication between modules can be event-driven or via interfaces.
  • Modules communicate through shared contracts and abstractions only.
Example folder structure:
MyMonolith/ |-- Modules/ |-- Sales/ |-- Domain/ |-- Application/ |-- Infrastructure/ |-- Inventory/ |-- Billing/ |-- SharedKernel/ # Common utilities and base classes |-- API/

How to implement Modular Monolith in .NET?

  • Use .NET class libraries per module.
  • Define clear module boundaries and expose only necessary interfaces.
  • Use Dependency Injection to wire modules together.
  • Optionally use events or a mediator pattern for inter-module communication.
  • Design for high cohesion and low coupling between modules.

Summary Comparison

Aspect
Clean Architecture
Vertical Slice
Modular Monolith
Focus
Layered separation of concerns
Feature/use-case based organization
Modular, well-encapsulated monolith
Structure
Domain, Application, Infrastructure, Presentation layered projects
Feature folders with end-to-end code
Modules with domain, app, infra per module
Advantages
Testability, maintainability, scalability
Feature isolation, easier to navigate
Balance between monolith simplicity and modularity
Typical Usage
Medium to large applications with complex domain logic
Any size; great with CQRS and MediatR
Large monoliths aiming for modular design
Dotnet Implementation
Separate library projects per layer
Folders or projects per feature slices
Library projects per module with clear boundaries

References

  • Modular Monolith Concepts: Microsoft Docs & community blogs (search for modular monolith in .NET architecture)