This repository provides a .NET 10 WebAPI boilerplate implementing a Modular Monolith approach, Domain-Driven Design ( DDD), and Vertical Slices architecture, along with Clean Architecture principles per feature. It is designed to facilitate the development of scalable and maintainable applications.
- Introduction
- Features
- Architecture Decisions
- Full-Text Search
- Requirements
- Getting Started
- API Documentation
- Contributing
- License
This boilerplate serves as a starting point for building applications with a focus on modularity, scalability, and maintainability. It leverages modern architectural patterns and practices to ensure a robust and flexible codebase.
This repository includes the following features:
- Event-Driven Architecture: Supports event-driven communication between components.
- Modular Monolithic Architecture: Organizes code into modules for better separation of concerns.
- Dynamic Module Registration: Configuration-driven module loading via
ModulesOptions, enabling explicit startup priority ordering and true split-deployment isolation: run any subset of modules per process without recompilation (see Split Deployment). - Vertical Slices, REPR, & Minimal APIs: Implements vertical slice architecture for feature-based organization.
- Domain-Driven Design & Clean Architecture: Adheres to DDD principles and clean architecture for maintainable code.
- Identity and Access Management via Keycloak: Users, roles, sessions, refresh-token rotation and fine-grained permissions (Keycloak Authorization Services) live in Keycloak, versioned as code in
keycloak/realm-modular-monolith.json. Clients only ever talk to this API: phone + SMS OTP login for mobile apps, email + password for staff; the backend brokers both through Keycloak and evaluatesresource#scopedecisions per request (cached per token). - Audit Log & Retention: Provides a transactional audit log (via domain events) with configurable automatic retention policies to manage database growth.
- OpenTelemetry Support: Integrates with Otel-Collector, Prometheus, Jaeger, and Aspire Dashboard for observability.
- Result Monad for Error Management: Utilizes result monads for error handling and flow control.
- Unit of Work: Ensures atomic operations across multiple repositories.
- Hangfire: Supports background job processing.
- Transactional Outbox Pattern: Ensures reliable message delivery via a custom outbox processor with lag tracking, cleanup, and distributed trace propagation.
- RabbitMQ & MassTransit: Integration events delivered over RabbitMQ via MassTransit; no application-side producers, publish via outbox only.
- Redis or In-Memory Caching: Provides caching mechanisms for performance optimization.
- Session Revocation: Logout, per-device sign-out and sign-out-everywhere revoke the Keycloak session; the first uncached permission decision after that is rejected by Keycloak, and access tokens are capped at 5 minutes.
- Consumer Idempotency:
EventHandlerBasechecks aprocessed_msg:{messageId}key in Redis before invoking the handler and writes it with a 24h TTL, ensuring at-least-once delivery without duplicate side effects. - Security Headers Middleware: Configurable
SecurityHeadersMiddlewareinjectsX-Content-Type-Options,X-Frame-Options,Content-Security-Policy, and related headers on every response. - Reverse Proxy / Forwarded Headers Support: Configurable
ForwardedHeadersmiddleware trusts known proxy networks and correctly propagates client IP and scheme behind load balancers. - Feature Management:
Microsoft.FeatureManagementwith targeting context support and automated endpoint filtering, feature flags configurable per-environment viaappsettings.json. - Rate Limiting: Modular per-policy rate limiting (registration, OTP/SMS, token, store creation) with override support via env vars for load testing.
- k6 Load Testing: Multi-scenario k6 suite (
docker compose -f docker-compose.yml -f docker-compose.perf.yml up k6) with Aspire Dashboard OTel traces live during runs. - Readiness Health Checks:
/health/readyprobe conditionally registers Redis and RabbitMQ checks so orchestrators only route traffic once all backing services are reachable. - Architecture Boundary Tests: NetArchTest rules assert no cross-module Domain references and that all
IntegrationEventtypes are declared exclusively inCommon.IntegrationEvents. - Strongly Typed IDs: Prevents primitive obsession by using strongly typed identifiers.
- Strongly-Typed Localization & Multi-Language Support: Leverages
Aigamo.ResXGeneratorto automatically generate strongly-typedIResxLocalizerproperties for error-free resource resolution, ensuring compile-time safety and eliminating missing key errors at runtime. - Pagination and Flexible Search: Implements pagination and flexible search capabilities.
- MassTransit's Request Client: Synchronous inter-module requests via
IInterModuleRequestClient<TRequest, TResponse>, transparent whether the handler is in-process or running in a separate instance on another host. No HTTP, no shared memory; pure RabbitMQ request/response. - Option Pattern: Utilizes the option pattern for configuration management.
- Fluent Validation: Provides fluent validation for input data.
- Functional Programming & Railway-Oriented Syntax: Encourages functional programming practices and method chaining.
- Docker-Compose Support: Simplifies development setup with Docker-Compose.
- Grafana Templates for Monitoring: Includes Grafana templates for monitoring and observability. and more.
To use or contribute to this project, you will need:
- .NET 10 SDK
- Docker
- GNU Make (
make)
- Clone the repository:
git@github.com:baranacikgoz/modular-monolith-ddd-vsa-webapi.git
- Start required infrastructure:
docker compose -f "docker-compose.yml" up -d --build mm.postgres mm.redis mm.aspire-dashboard mm.rabbitmq
Each module can run as its own process. The same image is used for all instances, only the ModulesOptions__EnabledModules environment variable differs. Inter-module communication still works transparently via MassTransit over RabbitMQ; no HTTP, no shared in-process memory.
# Infra must be running first (skip if already up)
docker compose up -d mm.postgres mm.rabbitmq mm.aspire-dashboard
# Start two instances: IAM on :5001, Products on :5002
docker compose -f docker-compose.split.yml up --build
# Prove the cross-process round-trip:
# Products instance has zero IAM code loaded, the request travels via RabbitMQ
curl "http://localhost:5002/v1/probe/cross-module?count=3"Open http://localhost:18888 (Aspire Dashboard) to inspect the distributed trace spanning both processes under a single TraceId.
See docs/split-deployment-poc.md for the full call-path walkthrough and concurrent-safety analysis.
- Use appropriate
tasks.jsondepending on your OS- Copy
tasks.windows.jsonortasks.unix.json, into a new file with the nametasks.json.
- Copy
- Make VS Code helper scripts executable:
chmod +x .vscode/exclude_docker_compose_dcproj.sh chmod +x .vscode/dotnet_build_and_cleanup.sh chmod +x .vscode/revert_exclude.sh
- Press
F5to start the application in debug mode
- Open the solution file
ModularMonolith.slnin Visual Studio - Set
Docker Composeas the startup project - Start application
All developer commands are centralized in the Makefile. Run make <target> from the repository root.
| Command | Description |
|---|---|
make build |
Build all projects (auto-excludes docker-compose.dcproj) |
make test |
Run all integration & unit tests |
make sonar |
Run SonarQube analysis (requires .env with SonarQube vars) |
⚠️ We do NOT auto-migrate at startup. All schema changes go through DBA review.
| Command | Description |
|---|---|
make ef-add-IAM name=<Name> |
Add migration to the IAM module |
make ef-add-Products name=<Name> |
Add migration to the Products module |
make ef-add-Inventory name=<Name> |
Add migration to the Inventory module |
make ef-add-Outbox name=<Name> |
Add migration to the Outbox module |
| Command | Description |
|---|---|
make ef-script-IAM |
Generate SQL script for IAM → migrations/IAM/ |
make ef-script-Products |
Generate SQL script for Products → migrations/Products/ |
make ef-script-Inventory |
Generate SQL script for Inventory → migrations/Inventory/ |
make ef-script-Outbox |
Generate SQL script for Outbox → migrations/Outbox/ |
make ef-script-all |
Generate scripts for all modules |
1. make ef-add-Products name=AddPriceColumn # Create the migration
2. make ef-script-Products # Generate idempotent SQL
3. git add . && git commit # Commit both .cs + .sql artifacts
4. DBA reviews & executes the .sql script # Applied to target DB
5. Deploy # App verifies, fails fast if pending
Links to API documentation (if available), or instructions on how to generate it.
Guidelines for contributing to the project, including how to report bugs, suggest features, or submit pull requests.
Information about the project's license.