Documentation
adrs/007-aspire-microservices-migration.md
ADR-007: Evolution of Dynaplex - From Dynamic Loading to Microservices
Status
Accepted and Implemented
Updated: 2026-01-08 (db-manager consolidation)
Note: Dynaplex is the architectural system implemented in Acsis Core, combining structured component patterns with dynamic orchestration capabilities. This ADR documents the evolution of Dynaplex from its initial dynamic loading implementation to the current microservices architecture.
Context
The initial Dynaplex architecture implementation in Acsis Core used:
- Dynamic Loading: A custom component loading framework using Assembly Load Contexts (ALCs)
- Monolithic deployment: All components running in a single process with isolation
- Interface separation: Components communicating through
.Specassemblies
While this architecture successfully adapted Polylith to .NET, several challenges emerged:
- Runtime complexity: Dynamic loading errors only discovered at runtime
- Debugging difficulty: Complex to debug issues across ALC boundaries
- Deployment coupling: All components deployed together despite logical separation
- Scaling limitations: Cannot scale individual components independently
- Tooling friction: Non-standard patterns complicated IDE and tooling support
Simultaneously, .NET Aspire emerged as Microsoft's cloud-native application orchestration framework, offering:
- Built-in service discovery and orchestration
- Container-based deployment with health checks
- Distributed tracing and observability
- Simplified local development experience
Decision
Evolve the Dynaplex architecture from dynamic loading to a microservices implementation orchestrated by .NET Aspire, where:
- Each component becomes an independent ASP.NET Core service
- Components communicate via HTTP APIs instead of in-process interfaces
- .NET Aspire handles service orchestration, discovery, and dependencies
- Database migrations run as separate initialization services
- API clients are auto-generated using Microsoft Kiota
Architectural Changes
Component Structure
Before:
component/
├── src/
│ ├── Acsis.Component.Spec/ # Interfaces and contracts
│ └── Acsis.Component/ # Implementation loaded dynamically
After:
component/
├── src/
│ ├── Acsis.Component.Abstractions/ # Interfaces and contracts
│ ├── Acsis.Component.Database/ # EF Core DbContext and entities (ADR-036)
│ ├── Acsis.Component/ # Standalone ASP.NET Core service
│ └── Acsis.Component.ApiClient/ # Auto-generated HTTP client
Note (2026-01-08): Per-component
.DbMigratorprojects have been replaced by a centralizeddb-managercomponent atengines/db-manager/. This reduced deployment complexity from 13+ containers to 1 for migrations. See ADR-036.
Foundation Base Role
Before: Single monolithic host loading all components dynamically
After: Aspire AppHost orchestrating multiple microservices
Inter-Component Communication
Before: Direct interface calls within the same process
After: HTTP API calls between services with typed clients
Consequences
Positive
Development Experience:
- Standard patterns: Each component is a standard ASP.NET Core application
- Independent debugging: Services can be debugged in isolation
- Familiar tooling: Full IDE support without custom patterns
- Clear boundaries: Physical service separation enforces architectural boundaries
Operational Benefits:
- Independent scaling: Scale components based on individual load
- Fault isolation: Component failures don't crash the entire system
- Independent deployment: Deploy component updates without full system restart
- Progressive migration: Can migrate components incrementally
Technology Alignment:
- Microsoft support: First-class support from Microsoft tooling
- Cloud-native ready: Natural path to Kubernetes and cloud deployment
- Observability: Built-in distributed tracing and monitoring
- Service mesh ready: Can integrate with service mesh technologies
Negative
Increased Complexity:
- Network overhead: Inter-component calls now traverse the network
- Distributed system challenges: Must handle network failures, retries, timeouts
- Data consistency: Distributed transactions more complex than in-process
- Development overhead: More projects and configuration per component
Resource Requirements:
- Higher memory usage: Each service has its own runtime overhead
- More processes: Multiple .NET runtime instances required
- Container overhead: If using containers, additional resource requirements
Migration Effort:
- Significant refactoring: Components need restructuring for HTTP APIs
- API design: Must design proper HTTP APIs for component interfaces
- Client generation: Additional build step for API client generation
Migration Strategy
- Preserve Dynaplex principles: Maintain component structure and boundaries
- Component by component: Migrate components incrementally
- API-first design: Define OpenAPI specs before implementation
- Backward compatibility: Legacy Dynaplex loader maintained during transition
Implementation Details
Aspire Orchestration (Program.cs)
var builder = DistributedApplication.CreateBuilder(args);
// PostgreSQL database
var database = builder.AddPostgres("postgres")
.AddDatabase("acsis");
// Centralized db-manager handles ALL component migrations
var dbManager = builder.AddDynaplexDbManager()
.WithReference(database)
.WaitFor(database);
// All services wait for db-manager (migrations complete before startup)
var component = builder.AddProject<Component>("component")
.WithReference(database)
.WaitFor(dbManager);
Note (2026-01-08): Database has migrated from SQL Server to PostgreSQL. The centralized
db-managerruns all component migrations in dependency order (Prism first).
Service Defaults
Common configuration for all services via Acsis.Dynaplex.Strata.ServiceDefaults:
- OpenTelemetry integration
- Health checks
- Service discovery
- Resilience patterns
API Client Generation
Using Microsoft Kiota to generate strongly-typed clients from OpenAPI specifications:
<PackageReference Include="Microsoft.Kiota.Http.HttpClientLibrary" />
<PackageReference Include="Microsoft.Kiota.Serialization.Json" />
Related Decisions
- Evolves: The Dynaplex architecture from dynamic loading to microservices
- Supersedes: ADR-003 Dynamic Component Loading
- Modifies: ADR-002 → Now uses
.Abstractions - Relates to: ADR-001 Original architectural principles
- Relates to: ADR-036 Database project separation and db-manager consolidation
Future Considerations
- Container orchestration: Natural progression to Kubernetes
- API gateway: May need centralized API gateway for external access
- Service mesh: Consider Istio or similar for advanced traffic management
- Event-driven: Could complement HTTP with event bus for async operations