Documentation
adrs/033-base-orchestration-pattern.md
ADR-033: Base Orchestration Pattern - Shared Infrastructure and Component Composition
Status
Accepted and Implemented
Context
The Dynaplex architecture uses "bases" as deployment scenario helpers that compose components into complete deployments. Initially, bases like FoundationBuilder and EdgeLiteBuilder contained significant code duplication (~85% overlap), with each base implementing:
- Infrastructure setup (Azure resources, databases, monitoring)
- Component registration and dependency management
- Service orchestration and health checks
- Validation logic
Initial Problems
- Code Duplication: FoundationBuilder and EdgeLiteBuilder shared 85% identical code
- Maintenance Burden: Infrastructure changes required updates in multiple bases
- Unclear Responsibility: Mixed concerns between infrastructure management and deployment orchestration
- Limited Composability: Difficult to create new deployment scenarios without copying large amounts of code
- Hidden Infrastructure: Developers couldn't easily see what infrastructure was being configured
Design Philosophy Clarifications
Through analysis and discussion, key principles emerged:
- Bases are deployment scenario helpers, not infrastructure providers
- Infrastructure should be explicit, not hidden behind abstractions
- Component groupings should be reusable utilities that projects compose
- The project AppHost is the infrastructure provider, bases provide tools for orchestration
- Dynaplex is inspired by Polylith, not replicating it
Decision
Introduce a shared orchestration layer (Acsis.Dynaplex.Strata.Orchestration) that:
- Abstracts Aspire Infrastructure: Centralizes Azure/database/monitoring setup
- Provides Component Registry: Standardizes component registration with dependency validation
- Defines Base Scenarios: Reusable component groupings for common deployment patterns
- Enables Explicit Configuration: Bases expose infrastructure methods individually
- Supports Easy Composition: New bases compose scenarios without duplicating code
Architecture Components
1. InfrastructureBuilder
Centralizes Aspire infrastructure setup with explicit methods:
public class InfrastructureBuilder {
public IDistributedApplicationBuilder Builder { get; }
public IResourceBuilder<IResourceWithConnectionString>? Database { get; }
public IResourceBuilder<AzureAppConfigurationResource>? AppConfig { get; }
public InfrastructureBuilder WithDatabase() { }
public InfrastructureBuilder WithAppConfiguration() { }
public InfrastructureBuilder WithAppInsights() { }
public InfrastructureBuilder WithContainerAppEnvironment() { }
public ComponentRegistry CreateComponentRegistry() { }
}
Benefits:
- Single source of truth for infrastructure
- Enables migration away from Aspire if needed
- Consistent naming and configuration across all bases
2. ComponentRegistry
Manages component registration with dependency validation:
public class ComponentRegistry {
public ComponentRegistration RegisterWithMigrator<TComponent, TMigrator>(
string name,
string abbreviation,
Action<ComponentDependencies>? configureDependencies = null);
public ComponentRegistration Register<TComponent>(
string name,
string abbreviation,
Action<ComponentDependencies>? configureDependencies = null);
public ComponentRegistration Get(string name);
public void ValidateRequiredComponents(params string[] requiredComponents);
}
Features:
- Dependency declaration via fluent API
- Automatic dependency ordering
- Validation of required components
- Consistent naming (component name + abbreviation)
3. BaseScenarios
Pre-defined component groupings for composition:
public static class BaseScenarios {
// Core infrastructure (required for all deployments)
public static void CoreInfrastructure<...>(ComponentRegistry registry);
// Full catalog stack (asset management)
public static void CatalogStack<...>(ComponentRegistry registry);
// Edge/RFID components
public static void EdgeComponents<...>(ComponentRegistry registry);
// Analytics and reporting
public static void AnalyticsComponents<...>(ComponentRegistry registry);
// Data import and integration
public static void IntegrationComponents<...>(ComponentRegistry registry);
}
Purpose:
- Standardized component groupings
- Reusable across multiple bases
- Documents common deployment patterns
- Easy to extend with new scenarios
Refactored Base Pattern
Before (512 lines):
public class FoundationBuilder {
// 500+ lines of infrastructure setup, component registration,
// validation, and orchestration code
}
After (130 lines - 75% reduction):
public class FoundationBuilder {
private readonly InfrastructureBuilder _infrastructure;
private ComponentRegistry? _registry;
public FoundationBuilder WithDatabase() {
_infrastructure.WithDatabase();
_registry = _infrastructure.CreateComponentRegistry();
return this;
}
// Explicit infrastructure methods (developers see what's configured)
public FoundationBuilder WithAppConfiguration() { }
public FoundationBuilder WithAppInsights() { }
public FoundationBuilder WithContainerAppEnvironment() { }
public IDistributedApplicationBuilder Build() {
_registry!.ValidateRequiredComponents(
"core-data", "system-environment", "identity", "events", "edge");
return _infrastructure.Builder;
}
}
Project Usage:
var foundation = builder.AddFoundation()
.WithAppConfiguration() // Explicit - developers see what's configured
.WithAppInsights()
.WithContainerAppEnvironment()
.WithDatabase();
// Component registration happens in project code
BaseScenarios.CoreInfrastructure<...>(foundation.Components);
BaseScenarios.CatalogStack<...>(foundation.Components);
BaseScenarios.EdgeComponents<...>(foundation.Components);
foundation.Build();
New Deployment Scenarios
The refactoring enabled creation of distinct deployment scenarios:
Foundation (Complete Stack)
- Purpose: Full Dynaplex experience with all components
- Includes: Core + Catalog + Edge + Analytics + Integration
- Use Case: Enterprise deployments, complete AssetTrak installations
- Required Components: core-data, system-environment, identity, events, edge
RfidLite (RFID-Focused)
- Purpose: Lightweight RFID tracking
- Includes: Core + Edge + Basic catalog
- Use Case: IoT edge deployments, simple RFID tracking
- Required Components: core-data, system-environment, identity, events, edge
- Note: BBU can be optionally layered on
WarehouseHub (Operations-Focused)
- Purpose: Warehouse operations without RFID
- Includes: Core + Full catalog + Analytics + Integration
- Use Case: Manual inventory, barcode scanning, traditional workflows
- Required Components: core-data, system-environment, identity, events, catalog, spatial, file-service, printing, transport, intelligence, workflow, importer
- Note: No Edge/RFID components
Consequences
Positive
Code Quality:
- 75% reduction in base code size (512 → 130 lines)
- Eliminated duplication across all bases
- Clear separation of concerns (infrastructure vs. orchestration)
- Improved testability through smaller, focused components
Developer Experience:
- Explicit configuration: Developers see exactly what infrastructure is configured
- Discoverability: BaseScenarios document standard component groupings
- Flexibility: Easy to create custom scenarios by composing scenarios
- Consistency: All bases follow the same pattern
Maintainability:
- Single source of truth for infrastructure setup
- Centralized validation logic in ComponentRegistry
- Easier to update: Change once in orchestration layer
- Clear documentation of deployment patterns
Extensibility:
- New bases are trivial: ~130 lines following established pattern
- Aspire migration path: InfrastructureBuilder isolates Aspire-specific code
- Easy scenario composition: Mix and match BaseScenarios
- Custom scenarios: Projects can register components manually if needed
Neutral
Learning Curve:
- Developers need to understand the distinction between:
- Bases: Deployment scenario helpers with validation
- Orchestration: Shared infrastructure and component utilities
- Projects: Actual infrastructure providers (AppHost)
Component Registration:
- Component registration moved from base to project code
- More explicit, but requires additional code in AppHost
Negative
None Identified:
- The refactoring maintains all existing functionality
- Builds successfully with no regressions
- Existing projects (assettrak-classic) migrated successfully
Implementation Details
File Structure
strata/
├── orchestration/
│ └── src/Acsis.Dynaplex.Strata.Orchestration/
│ ├── InfrastructureBuilder.cs # Aspire abstraction
│ ├── ComponentRegistry.cs # Registration & validation
│ ├── ComponentRegistration.cs # Component metadata
│ ├── ComponentDependencies.cs # Dependency declaration
│ └── BaseScenarios.cs # Pre-defined scenarios
projects/
├── bbu-rfid/
│ └── src/Acsis.Dynaplex.Projects.BbuRfid/
│ ├── AppHost.cs # Aspire AppHost entry point
│ └── ...
├── rfid-lite/
│ └── src/Acsis.Bases.RfidLite/
│ ├── RfidLiteBuilder.cs # 130 lines
│ ├── RfidLiteOptions.cs
│ └── RfidLiteExtensions.cs
└── warehouse-hub/
└── src/Acsis.Bases.WarehouseHub/
├── WarehouseHubBuilder.cs # 145 lines
├── WarehouseHubOptions.cs
└── WarehouseHubExtensions.cs
Migration Path
Projects using old FoundationBuilder pattern:
Before:
var foundation = builder.AddFoundation()
.WithDefaultDatabase()
.WithCoreData<T1, T2>()
.WithIdentity<T3, T4>()
// ... more WithXxx() calls
After:
var foundation = builder.AddFoundation()
.WithDatabase(); // Changed name
BaseScenarios.CoreInfrastructure<...>(foundation.Components);
BaseScenarios.CatalogStack<...>(foundation.Components);
foundation.Build();
Migration completed for:
- ✅ assettrak-classic project (builds successfully)
Roslyn Analyzer Update
Updated AbstractionReferenceRequirementAnalyzer to allow the orchestration project:
private static readonly string[] AllowedProjects = [
"Acsis.Dynaplex",
"Acsis.Dynaplex.Strata.ServiceDefaults",
"Acsis.Dynaplex.Strata.Orchestration", // ← Added
"Acsis.RoslynAnalyzers",
"Acsis.EntLibCompatShim",
"Acsis.Encoding.Gs1"
];
This allows bases to reference the shared orchestration utilities while maintaining architectural boundaries.
Related Decisions
- ADR-007: Aspire Microservices Migration - Established .NET Aspire as orchestration framework
- ADR-004: Roslyn Analyzer Enforcement - Enforces architectural rules at compile time
- ADR-001: Polylith Architecture Adoption - Initial inspiration for component architecture
Future Considerations
- EdgeLite Migration: Refactor existing EdgeLite base using new pattern (currently 495 lines)
- Additional Scenarios: Create bases for other common deployment patterns
- Aspire Alternatives: If Microsoft abandons Aspire, InfrastructureBuilder provides migration path
- Documentation Base: Consider base for doc-web-only deployments
- Component Templates: Generate new bases using templates/scaffolding
References
- Implementation:
/strata/orchestration/src/Acsis.Dynaplex.Strata.Orchestration/ - Refactored projects:
/projects/bbu-rfid/,/projects/rfid-lite/,/projects/warehouse-hub/ - Analysis document:
/docs/analysis/BASE_DUPLICATION_ANALYSIS.md - Project migration:
/projects/assettrak-classic/src/Acsis.Dynaplex.Projects.AssetTrakClassic/AppHost.cs
Evolution: Aspire-Native Extensions (November 2025)
Status: Implemented and superseding InfrastructureBuilder pattern
What Changed
The InfrastructureBuilder + ComponentRegistry pattern has evolved to Aspire-native extension methods that are simpler, more discoverable, and follow Microsoft's standard patterns.
Before (InfrastructureBuilder):
var infrastructure = new InfrastructureBuilder(builder);
infrastructure.WithDatabase();
infrastructure.WithAppConfiguration();
var registry = infrastructure.CreateComponentRegistry();
registry.Register<ComponentA>(metadata).WithDependencies(/* ... */);
After (Extension Methods):
builder.UseDynaplex("elbbudev");
builder.AddDynaplexDatabase();
builder.AddDynaplexAppConfiguration();
// Centralized db-manager handles all migrations
var dbManager = builder.AddDynaplexDbManager()
.WaitFor(database);
builder.AddComponent<ComponentA>(ComponentIndex.ComponentA.Metadata)
.WaitFor(dbManager); // All services wait for db-manager
Note (2026-01-08): The original per-component
.WithMigrator<>()pattern has been replaced by a centralizeddb-managercomponent. See ADR-036 for details.
Why This Evolution?
- Aspire Alignment: Native builder extension methods (feels like built-in Aspire)
- Discoverability: IntelliSense shows all available methods naturally
- No Wrapper Objects: Direct builder extension, no InfrastructureBuilder wrapper
- Metadata as Annotations: Uses Aspire's IResourceAnnotation pattern
- Context via DI: Stateful sharing through singleton context (clean)
- Familiar to .NET Developers: Follows standard Microsoft patterns
Key Implementation Details
- DynaplexContext: Registered as DI singleton in UseDynaplex()
- Component Metadata: Attached as IResourceAnnotation to resources
- Extension Methods: All static methods in Aspire.Hosting namespace
- Hidden Registry: ComponentRegistry lives internal in DynaplexContext
- Dependency Flow: Metadata-driven, extracted from annotations
Migration from InfrastructureBuilder
Pattern change:
// OLD: Wrapper object
var infrastructure = new InfrastructureBuilder(builder);
var component = infrastructure.WithDatabase();
// NEW: Direct extension
builder.UseDynaplex();
builder.AddDynaplexDatabase();
var component = builder.AddComponent<T>(metadata);
Benefits of migration:
- Cleaner AppHost code
- Better IntelliSense support
- No wrapper object ceremony
- Easier to understand (more explicit)
Documentation of New Pattern
Complete documentation available in:
/strata/orchestration/src/Acsis.Dynaplex.Strata.Orchestration/README.md- Overview and usage/strata/orchestration/src/Acsis.Dynaplex.Strata.Orchestration/architecture.md- Detailed design decisions/strata/orchestration/src/Acsis.Dynaplex.Strata.Orchestration/patterns.md- Common patterns and examples
Backward Compatibility
The InfrastructureBuilder classes remain in the codebase for any legacy uses, but new development should use the extension method pattern. Existing AppHosts can migrate incrementally (extension methods coexist with old pattern).
Date: 2025-01-18 (Original)
Updated: 2025-11-12 (Evolution to Aspire-Native Extensions)
Updated: 2026-01-08 (db-manager consolidation)
Author: System Architecture Team
Reviewers: Development Team