Skip to content

ADR-001: Microservices Architecture with Clean Architecture per Service

Status

Accepted

Date

2023-Q1


Context

Microtec ERP needed to support a broad scope of business modules: Accounting, HR, Inventory, Sales, Purchase, Finance, Distribution, and more. These modules are developed by different teams, have different scaling requirements, and are released on different cadences.

The initial prototype was a monolithic .NET application. As the feature set grew, the monolith created bottlenecks:

  • A bug in Inventory would require redeploying all modules
  • The HR team's release schedule blocked the Accounting team
  • Scaling the reporting engine for export-heavy operations scaled the entire application
  • Database schema changes for one module required careful coordination across all modules
  • Growing codebase made onboarding new developers slow

We considered three architectural approaches:

OptionDescriptionKey Concerns
MonolithSingle deployable, shared databaseAlready hitting limits; scaling/team constraints
Modular MonolithSingle deployable, isolated modulesBetter, but still coupled deployment
MicroservicesIndependent services per domainOperational complexity, but maximum flexibility

Decision

Adopt microservices architecture with Clean Architecture applied independently within each service.

Each business domain (Accounting, HR, Inventory, etc.) is an independent microservice with:

  • Its own deployable unit (Docker container)
  • Its own database (database-per-tenant, see ADR-003)
  • Its own CI/CD pipeline
  • Clean Architecture layers: Domain → Application → Infrastructure → API

Cross-cutting concerns (auth, messaging, persistence patterns) are shared via NuGet packages (see ADR-006).

Clean Architecture per Service

{ServiceName}/
├── {ServiceName}.Apis/          # REST controllers, Swagger, DI
├── {ServiceName}.Application/   # CQRS handlers, validators, DTOs
├── {ServiceName}.Domain/        # Entities, domain events, value objects
└── {ServiceName}.Infrastructure/# EF Core, external integrations

CQRS with MediatR

All operations go through MediatR:

  • Commands: write operations (AddInvoiceCommand, EditEmployeeCommand)
  • Queries: read operations (GetAllInvoicesQuery, GetByIdEmployeeQuery)
  • Handlers are co-located with their commands/queries in feature folders

Consequences

Positive

  • Independent deployment: Each service deploys independently — a bug in HR doesn't affect Accounting
  • Team autonomy: Each team owns their service end-to-end (code, database, deployment)
  • Independent scaling: The reporting service can scale separately from the transactional API
  • Technology flexibility: Individual services can evolve their stack independently
  • Fault isolation: A failure in one service degrades only that domain — not the entire application
  • Smaller codebases: Each service is focused and easier to understand

Negative

  • Operational complexity: 14+ services to deploy, monitor, and troubleshoot
  • Distributed system challenges: Network failures, partial failures, eventual consistency
  • Inter-service communication overhead: HTTP calls vs in-process method calls
  • Data consistency: Transactions span multiple services — requires saga patterns or eventual consistency
  • Higher infrastructure cost: 14+ containers vs 1 monolith
  • Onboarding complexity: New developers must understand the service mesh, not just one app

Neutral

  • Shared NuGet packages became necessary to avoid code duplication — see ADR-006
  • API gateway became necessary for unified entry point — see ADR-007
  • Service mesh (mTLS via ACA) became the default — see ADR-002

Implementation Notes

Current Services

ServiceDomainSolution
AppsPortal.ApisAccounting (complete)Microtec.Platforms.sln
HR.ApisHuman ResourcesMicrotec.Platforms.sln
Inventory.ApisInventoryMicrotec.Platforms.sln
Sales.ApisSales (partial)Microtec.Platforms.sln
BusinessOwners.ApisTenant admin portalMicrotec.BusinessOwner.sln
Attachment.ApisFile storageInfrastructure service
Notification.ApisEmail/SMS/PushInfrastructure service
Workflow.ApisBusiness workflowsInfrastructure service
Integration.ApisExternal integrationsInfrastructure service
Gateway.APIAPI gatewayMicrotec.Platforms.sln

Naming Convention

  • Feature folders: {Feature}/Commands/, {Feature}/Queries/
  • Commands: Add{Entity}, Edit{Entity}, Delete{Entity}
  • Queries: GetById{Entity}, GetAll{Entity}, GetDropdown{Entity}

  • ADR-002: Azure Container Apps (runtime for microservices)
  • ADR-003: Database-per-tenant (data isolation)
  • ADR-005: Azure Service Bus (inter-service messaging)
  • ADR-006: NuGet packages (shared cross-cutting concerns)
  • ADR-007: YARP gateway (unified entry point)
  • ADR-008: Dual-token design (auth for microservices)

Internal Documentation — Microtec Platform Team