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 .Spec assemblies

While this architecture successfully adapted Polylith to .NET, several challenges emerged:

  1. Runtime complexity: Dynamic loading errors only discovered at runtime
  2. Debugging difficulty: Complex to debug issues across ALC boundaries
  3. Deployment coupling: All components deployed together despite logical separation
  4. Scaling limitations: Cannot scale individual components independently
  5. 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:

  1. Each component becomes an independent ASP.NET Core service
  2. Components communicate via HTTP APIs instead of in-process interfaces
  3. .NET Aspire handles service orchestration, discovery, and dependencies
  4. Database migrations run as separate initialization services
  5. 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 .DbMigrator projects have been replaced by a centralized db-manager component at engines/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

  1. Preserve Dynaplex principles: Maintain component structure and boundaries
  2. Component by component: Migrate components incrementally
  3. API-first design: Define OpenAPI specs before implementation
  4. 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-manager runs 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" />
  • 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

  1. Container orchestration: Natural progression to Kubernetes
  2. API gateway: May need centralized API gateway for external access
  3. Service mesh: Consider Istio or similar for advanced traffic management
  4. Event-driven: Could complement HTTP with event bus for async operations

References