Documentation
reference/patterns/dynaplex-architecture.md
Dynaplex Architecture
Dynaplex is the architectural system powering Acsis Core - a modern microservices architecture that combines the modularity of Polylith with the power of .NET Aspire orchestration.
What is Dynaplex?
Dynaplex encompasses:
- Multi-project component structure (
.Abstractions,.Database, service,.ApiClient) - .NET Aspire orchestration for service management and discovery
- Auto-generated API clients using Microsoft Kiota for type-safe communication
- Database-per-service pattern with centralized migration management via db-manager
- Strict architectural boundaries enforced by analyzers
Etymology: Dynamic + complex = Dynaplex - a system that's both flexible and sophisticated.
Core Principles
1. Microservices with Monorepo
All services live in one repository but run as independent processes:
engines/
├── core-data/ # Independent service
├── identity/ # Independent service
├── workflow/ # Independent service
└── [15 more services]
Benefits:
- Atomic commits across services
- Shared build infrastructure
- Consistent development experience
- Easy cross-service changes
2. Contract-First Development
Every component defines its contract in an .Abstractions project:
// Acsis.Dynaplex.Engines.CoreData.Abstractions/ICoreDataApi.cs
public interface ICoreDataApi
{
Task<Asset?> GetAssetByIdAsync(int id);
Task<Asset> CreateAssetAsync(CreateAssetRequest request);
}
Services can only reference other services' abstractions, never their implementations.
3. Service-to-Service Communication
At runtime, services communicate via HTTP using auto-generated clients:
// In Workflow service
public class WorkflowApi : IWorkflowApi
{
private readonly ICoreDataApiClient _coreData; // HTTP client
public async Task ProcessAssetAsync(int assetId)
{
// HTTP call to CoreData service
var asset = await _coreData.GetAssetByIdAsync(assetId);
}
}
Why?
- Services can scale independently
- Failures are isolated
- Technology mixing possible (see
assettrak-uiNext.js component)
4. Aspire Orchestration
.NET Aspire manages the entire service ecosystem:
// projects/bbu-rfid/src/Acsis.Dynaplex.Projects.BbuRfid/Program.cs
var builder = DistributedApplication.CreateBuilder(args);
// Infrastructure
var postgres = builder.AddPostgres("postgres");
var db = postgres.AddDatabase("acsis");
// Services
var coreData = builder.AddProject<Acsis_Components_CoreData>("core-data")
.WithReference(db);
var identity = builder.AddProject<Acsis_Components_Identity>("identity")
.WithReference(db)
.WithReference(coreData); // Service dependency
builder.Build().Run();
Aspire automatically:
- Manages service lifecycle
- Configures service discovery
- Sets up health checks
- Provides telemetry
- Handles configuration
Component Structure
Every Dynaplex component follows a four-project pattern:
engines/my-component/
├── src/
│ ├── Acsis.Dynaplex.Engines.MyComponent.Abstractions/
│ │ ├── IMyComponentApi.cs # Contract
│ │ ├── Models/ # DTOs
│ │ └── Configuration/ # Options
│ │
│ ├── Acsis.Dynaplex.Engines.MyComponent/
│ │ ├── Program.cs # ASP.NET Core entry point
│ │ ├── MyComponentApi.cs # Implementation
│ │ └── Services/ # Business logic
│ │
│ ├── Acsis.Dynaplex.Engines.MyComponent.Database/
│ │ ├── MyComponentDb.cs # DbContext
│ │ ├── Entities/ # Entity models
│ │ └── Migrations/ # EF Core migrations
│ │
│ └── Acsis.Dynaplex.Engines.MyComponent.ApiClient/
│ └── [Generated by Kiota] # HTTP client
│
├── test/
│ └── Acsis.Dynaplex.Engines.MyComponent.Tests/
│
└── resources/
└── README.md # Component documentation
Why This Structure?
.Abstractions - Defines WHAT the service does
- Contains interfaces and contracts
- Can be referenced by other services
- Enables compile-time type safety
.Database - Manages data model
- Contains DbContext and entity models
- Migrations stored here
- Registered with centralized db-manager
Main Service - Implements HOW it's done
- ASP.NET Core web service
- Runs independently
- Exposes HTTP API
.ApiClient - Provides type-safe client
- Auto-generated from OpenAPI
- Used by other services
- Maintains type safety across HTTP
Note: Database migrations are run by the centralized db-manager component, not per-component migrators.
Library Structure
In addition to components, Dynaplex includes libraries—pure, stateless code packages that can be directly referenced without HTTP overhead.
What are Libraries?
Libraries provide cross-cutting utilities and shared primitives that don't fit the component (microservice) model:
- Technical utilities: GS1 encoding, parsing, validation helpers
- Domain primitives: Strongly-typed IDs (PTIDs), value objects, units of measure
- Infrastructure abstractions: Dynaplex core utilities, ServiceDefaults
Unlike components, libraries:
- Do NOT run as services
- Do NOT own database schemas
- Do NOT expose HTTP endpoints
- CAN be directly referenced (no HTTP overhead)
Library vs Component
| Aspect | Library | Component |
|---|---|---|
| Deployment | Not deployed independently | Independent microservice |
| Communication | Direct project reference | HTTP via ApiClient |
| Persistence | Cannot own data | Owns database schema |
| Structure | Single project | 4-project pattern |
| Purpose | Pure utilities | Business logic + APIs |
| Naming | Acsis.Dynaplex.Strata.* |
Acsis.Dynaplex.Engines.* |
Library Structure
libraries/
├── encoding/
│ └── src/Acsis.Dynaplex.Strata.Gs1/
│ └── [GS1 barcode encoding utilities]
│
├── domain-primitives/
│ └── src/Acsis.Dynaplex.Strata.DomainPrimitives/
│ └── [PTIDs, value objects, units]
│
├── validation/
│ └── src/Acsis.Dynaplex.Strata.Validation/
│ └── [Validation attributes and helpers]
│
└── infrastructure/
└── src/
├── Acsis.Dynaplex.Strata.Dynaplex/
├── Acsis.Dynaplex.Strata.Dynaplex.ServiceDefaults/
└── Acsis.Dynaplex.Strata.Dynaplex.Orchestration/
Qualification Criteria
A project qualifies as a library when ALL of these are true:
- No persistence: No DbContext, no migrations
- No endpoints: No Program.cs, no HTTP
- Pure logic: Deterministic, side-effect-free
- Cross-cutting: Used by ≥2 components
- Stable semantics: Changes rarely
- No business workflows: No domain policies or orchestration
The "workflow test": If you can describe the code as "business rule for [bounded context]", it belongs in a component, not a library.
Dependency Rules
Libraries are subject to strict constraints enforced by Roslyn analyzers:
Libraries CANNOT reference:
Acsis.Dynaplex.Engines.*(any component project).Databaseprojects.ApiClientprojects- Entity Framework Core
- ASP.NET Core
Libraries CAN reference:
- Other
Acsis.Dynaplex.Strata.*projects - BCL/System namespaces
- Logging abstractions
See ADR-046: Library Projects for complete details.
Evolution from Polylith
Dynaplex evolved from a pure Polylith architecture:
Original (Polylith)
- Dynamic component loading via AssemblyLoadContext
- Single process, multiple components
- Runtime composition
Current (Dynaplex)
- Multiple independent services via Aspire
- Microservices architecture
- HTTP communication
Why the change?
See ADR-007: Evolution of Dynaplex
Key insights:
- Better scalability with independent services
- Cloud-native deployment with containers
- Fault isolation between services
- Easier to reason about in distributed systems
What stayed?
- Four-project structure (evolved from Spec/Engine to Abstractions/Service)
- Contract-first development
- Strict architectural boundaries
- Monorepo organization
Architectural Enforcement
Dynaplex enforces architectural rules through:
1. Roslyn Analyzers
ACSIS0001: Only .Abstractions projects may be referenced
// ❌ Compile error
using Acsis.Dynaplex.Engines.CoreData;
// ✅ Correct
using Acsis.Dynaplex.Engines.CoreData.Abstractions;
2. Physical Service Boundaries
Services run in separate processes - cannot directly reference implementations.
3. Generated Clients
Type-safe HTTP clients prevent runtime errors:
// Compile-time error if API changes
var asset = await _coreData.GetAssetByIdAsync(id);
Key Patterns
Database-Per-Service
Each service owns its database schema:
PostgreSQL
├── core_data schema # Owned by CoreData service
├── identity schema # Owned by Identity service
└── workflow schema # Owned by Workflow service
Migrations run automatically before service startup.
Service Discovery
Aspire handles service discovery automatically:
// No manual configuration needed
var coreData = builder.AddProject<Acsis_Components_CoreData>("core-data");
var identity = builder.AddProject<Acsis_Components_Identity>("identity")
.WithReference(coreData); // Aspire configures URL automatically
Configuration
Services get configuration from multiple sources:
appsettings.json- Defaults- Environment variables - Deployment-specific
- Azure Key Vault - Secrets (in production)
- Aspire AppHost - Service URLs and references
Resilience
Built-in resilience patterns:
- Retries - Automatic retry on transient failures
- Circuit breakers - Prevent cascade failures
- Timeouts - Fail fast on slow operations
- Health checks - Monitor service health
Benefits of Dynaplex
✅ Modularity - Clear component boundaries
✅ Scalability - Scale services independently
✅ Testability - Test services in isolation
✅ Type Safety - Compile-time checks across services
✅ Developer Experience - Aspire dashboard for local development
✅ Cloud Native - Ready for containers and cloud platforms
✅ Observability - Built-in telemetry and tracing
✅ Consistency - Standardized patterns across all services
Trade-offs
❌ Complexity - More moving parts than a monolith
❌ Network overhead - HTTP calls between services
❌ Distributed debugging - Harder to debug across services
❌ Data consistency - No distributed transactions
Our stance: The benefits outweigh the trade-offs for our domain and scale.
Deployment Model
Local Development
dotnet run --project projects/bbu-rfid/src/Acsis.Dynaplex.Projects.BbuRfid/
Aspire starts:
- All component services
- PostgreSQL database
- Development dashboard
Production (Azure Container Apps)
azd up
Aspire deploys:
- Container per service
- Managed PostgreSQL
- Application Insights
- Service-to-service networking
Related ADRs
Key architectural decisions:
- ADR-001: Polylith Architecture - Original architecture
- ADR-007: Aspire Microservices Migration - Evolution to current state
- ADR-009: Database Schema per Service - Database isolation
- ADR-021: Kiota API Client Generation - Type-safe clients
- ADR-033: Base Orchestration Pattern - Aspire integration
Further Reading
- Component Patterns - Best practices
- Service Communication - Inter-service patterns
- Build System - Build architecture
- Migration Guide - Evolution history
Dynaplex represents a pragmatic evolution from monolithic Polylith to cloud-native microservices while retaining the best aspects of component-based architecture.