|
| 1 | +# .NET Aspire Implementation in Chapter 3 |
| 2 | + |
| 3 | +This document describes the .NET Aspire implementation added to Chapter 3 of the Evolutionary Architecture by Example project. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +.NET Aspire is a new cloud-ready stack for building observable, production-ready, distributed applications. This implementation adds Aspire to Chapter 3, providing: |
| 8 | + |
| 9 | +- **Service Orchestration**: Automatic startup and configuration of all services |
| 10 | +- **Observability**: Built-in distributed tracing, metrics, and structured logging |
| 11 | +- **Service Discovery**: Automatic service-to-service communication |
| 12 | +- **Health Checks**: Monitoring of service and dependency health |
| 13 | +- **Resilience**: Automatic retry policies and circuit breakers |
| 14 | + |
| 15 | +## Components Added |
| 16 | + |
| 17 | +### 1. Fitnet.ServiceDefaults Project |
| 18 | + |
| 19 | +A shared library that provides common Aspire configurations for all services. |
| 20 | + |
| 21 | +**Location**: `Chapter-3-microservice-extraction/Fitnet.ServiceDefaults/` |
| 22 | + |
| 23 | +**Key Features**: |
| 24 | +- OpenTelemetry configuration for logs, metrics, and traces |
| 25 | +- Service discovery client |
| 26 | +- HTTP resilience handlers (retries, circuit breakers) |
| 27 | +- Health check endpoints (`/health` and `/alive`) |
| 28 | +- OTLP exporter for telemetry data |
| 29 | + |
| 30 | +**Usage**: |
| 31 | +```csharp |
| 32 | +var builder = WebApplication.CreateBuilder(args); |
| 33 | +builder.AddServiceDefaults(); |
| 34 | + |
| 35 | +var app = builder.Build(); |
| 36 | +app.MapDefaultEndpoints(); |
| 37 | +``` |
| 38 | + |
| 39 | +### 2. Fitnet.AppHost Project |
| 40 | + |
| 41 | +The orchestration layer that manages all services and resources. |
| 42 | + |
| 43 | +**Location**: `Chapter-3-microservice-extraction/Fitnet.AppHost/` |
| 44 | + |
| 45 | +**Managed Resources**: |
| 46 | +- PostgreSQL database (`postgres` with `fitnet` database) |
| 47 | +- RabbitMQ message broker (with management plugin) |
| 48 | +- Fitnet modular monolith (on port 8080) |
| 49 | +- Fitnet.Contracts microservice (on port 8081) |
| 50 | + |
| 51 | +**Configuration**: |
| 52 | +```csharp |
| 53 | +var postgres = builder.AddPostgres("postgres") |
| 54 | + .WithImage("postgres") |
| 55 | + .WithImageTag("14.3") |
| 56 | + .WithHealthCheck(); |
| 57 | + |
| 58 | +var rabbitmq = builder.AddRabbitMQ("rabbitmq") |
| 59 | + .WithManagementPlugin() |
| 60 | + .WithHealthCheck(); |
| 61 | +``` |
| 62 | + |
| 63 | +## Changes to Existing Code |
| 64 | + |
| 65 | +### Connection String Support |
| 66 | + |
| 67 | +All database and messaging modules have been updated to support both Aspire connection strings and legacy configuration: |
| 68 | + |
| 69 | +#### EventBusModule (RabbitMQ) |
| 70 | +**Files Modified**: |
| 71 | +- `Fitnet/Src/Passes/Fitnet.Passes.Api/Common/EventBus/EventBusModule.cs` |
| 72 | +- `Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/EventBus/EventBusModule.cs` |
| 73 | + |
| 74 | +**Pattern**: |
| 75 | +```csharp |
| 76 | +// Try Aspire connection string first |
| 77 | +var connectionString = configuration.GetConnectionString("rabbitmq"); |
| 78 | + |
| 79 | +if (!string.IsNullOrEmpty(connectionString)) |
| 80 | +{ |
| 81 | + factoryConfigurator.Host(new Uri(connectionString)); |
| 82 | +} |
| 83 | +else |
| 84 | +{ |
| 85 | + // Fallback to legacy configuration |
| 86 | + var options = context.GetRequiredService<IOptions<EventBusOptions>>(); |
| 87 | + factoryConfigurator.Host(options.Value.Uri, hostConfigurator => { ... }); |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +#### DatabaseModule (PostgreSQL) |
| 92 | +**Files Modified**: |
| 93 | +- `Fitnet/Src/Passes/Fitnet.Passes.DataAccess/Database/DatabaseModule.cs` |
| 94 | +- `Fitnet/Src/Offers/Fitnet.Offers.DataAccess/Database/DatabaseModule.cs` |
| 95 | +- `Fitnet.Contracts/Src/Fitnet.Contracts.Infrastructure/Database/DatabaseModule.cs` |
| 96 | +- `Fitnet/Src/Reports/Fitnet.Reports/DataAccess/DatabaseConnectionFactory.cs` |
| 97 | + |
| 98 | +**Pattern**: |
| 99 | +```csharp |
| 100 | +// Try Aspire connection string first |
| 101 | +var connectionString = configuration.GetConnectionString("fitnet"); |
| 102 | + |
| 103 | +if (string.IsNullOrEmpty(connectionString)) |
| 104 | +{ |
| 105 | + // Fallback to legacy configuration |
| 106 | + var databaseOptions = serviceProvider.GetRequiredService<IOptions<DatabaseOptions>>(); |
| 107 | + connectionString = databaseOptions.Value.ConnectionString; |
| 108 | +} |
| 109 | + |
| 110 | +options.UseNpgsql(connectionString); |
| 111 | +``` |
| 112 | + |
| 113 | +### Program.cs Updates |
| 114 | + |
| 115 | +**Files Modified**: |
| 116 | +- `Fitnet/Src/Fitnet/Program.cs` |
| 117 | +- `Fitnet.Contracts/Src/Fitnet.Contracts/Program.cs` |
| 118 | + |
| 119 | +**Changes**: |
| 120 | +```csharp |
| 121 | +// Added Aspire service defaults |
| 122 | +builder.AddServiceDefaults(); |
| 123 | + |
| 124 | +// Added default health check endpoints |
| 125 | +app.MapDefaultEndpoints(); |
| 126 | +``` |
| 127 | + |
| 128 | +### Project References |
| 129 | + |
| 130 | +Both main projects now reference `Fitnet.ServiceDefaults`: |
| 131 | +- `Fitnet/Src/Fitnet/Fitnet.csproj` |
| 132 | +- `Fitnet.Contracts/Src/Fitnet.Contracts/Fitnet.Contracts.csproj` |
| 133 | + |
| 134 | +## How to Run with Aspire |
| 135 | + |
| 136 | +### Prerequisites |
| 137 | +- .NET SDK 9.0 or higher |
| 138 | +- Docker (for containers) |
| 139 | +- GitHub Personal Access Token with `read:packages` scope |
| 140 | + |
| 141 | +### Running the Application |
| 142 | + |
| 143 | +1. Configure NuGet credentials for GitHub Packages |
| 144 | +2. Navigate to the AppHost directory: |
| 145 | + ```bash |
| 146 | + cd Chapter-3-microservice-extraction/Fitnet.AppHost |
| 147 | + ``` |
| 148 | +3. Run the AppHost: |
| 149 | + ```bash |
| 150 | + dotnet run |
| 151 | + ``` |
| 152 | + |
| 153 | +The Aspire Dashboard will open automatically in your browser. |
| 154 | + |
| 155 | +### Accessing Services |
| 156 | + |
| 157 | +- **Fitnet Modular Monolith**: http://localhost:8080 |
| 158 | +- **Contracts Microservice**: http://localhost:8081 |
| 159 | +- **Swagger UI (Fitnet)**: http://localhost:8080/swagger |
| 160 | +- **Swagger UI (Contracts)**: http://localhost:8081/swagger |
| 161 | +- **Aspire Dashboard**: URL shown in console output |
| 162 | + |
| 163 | +### Aspire Dashboard Features |
| 164 | + |
| 165 | +The dashboard provides: |
| 166 | +- **Resources Tab**: View all containers and services |
| 167 | +- **Console Logs**: Real-time logs from all services |
| 168 | +- **Structured Logs**: Queryable structured logs |
| 169 | +- **Traces**: Distributed tracing across services |
| 170 | +- **Metrics**: Performance metrics and charts |
| 171 | + |
| 172 | +## Benefits of Aspire Implementation |
| 173 | + |
| 174 | +### For Development |
| 175 | +- **Simplified Setup**: No need to manually run `docker-compose` |
| 176 | +- **Integrated Dashboard**: Single place to monitor all services |
| 177 | +- **Better Debugging**: Distributed tracing shows request flows |
| 178 | +- **Quick Iteration**: Fast service restarts |
| 179 | + |
| 180 | +### For Production Readiness |
| 181 | +- **Observability**: Built-in telemetry collection |
| 182 | +- **Resilience**: Automatic retry and circuit breaker policies |
| 183 | +- **Health Monitoring**: Continuous health checks |
| 184 | +- **Service Discovery**: Dynamic service location |
| 185 | + |
| 186 | +### Backward Compatibility |
| 187 | +- **Legacy Support**: Existing `docker-compose.yml` still works |
| 188 | +- **Configuration Fallback**: Services work without Aspire |
| 189 | +- **Minimal Changes**: Existing code patterns preserved |
| 190 | + |
| 191 | +## Architecture Improvements |
| 192 | + |
| 193 | +### Before Aspire |
| 194 | +``` |
| 195 | +Developer → docker-compose → PostgreSQL + RabbitMQ |
| 196 | + → Fitnet API |
| 197 | + → Contracts API |
| 198 | +``` |
| 199 | + |
| 200 | +### With Aspire |
| 201 | +``` |
| 202 | +Developer → Aspire AppHost → PostgreSQL (container) |
| 203 | + → RabbitMQ (container) |
| 204 | + → Fitnet API (with ServiceDefaults) |
| 205 | + → Contracts API (with ServiceDefaults) |
| 206 | + → Aspire Dashboard |
| 207 | +``` |
| 208 | + |
| 209 | +## Key Design Decisions |
| 210 | + |
| 211 | +### 1. Backward Compatibility |
| 212 | +All changes support both Aspire and legacy configurations. Services can run: |
| 213 | +- With Aspire (recommended for local development) |
| 214 | +- With docker-compose (still supported) |
| 215 | +- Standalone (with manual infrastructure setup) |
| 216 | + |
| 217 | +### 2. Connection String Priority |
| 218 | +The implementation checks connection strings in this order: |
| 219 | +1. Aspire-provided connection string (`GetConnectionString()`) |
| 220 | +2. Legacy configuration (`EventBusOptions`, `DatabaseOptions`) |
| 221 | + |
| 222 | +This ensures smooth migration and flexibility. |
| 223 | + |
| 224 | +### 3. Minimal Code Changes |
| 225 | +The implementation adds Aspire capabilities without significant refactoring: |
| 226 | +- Two new projects (AppHost, ServiceDefaults) |
| 227 | +- Minor updates to existing modules |
| 228 | +- Preserved existing architecture patterns |
| 229 | + |
| 230 | +### 4. PostgreSQL and RabbitMQ Versions |
| 231 | +The AppHost uses the same versions as docker-compose: |
| 232 | +- PostgreSQL: `postgres:14.3` |
| 233 | +- RabbitMQ: `rabbitmq:management` |
| 234 | + |
| 235 | +This ensures consistency across deployment methods. |
| 236 | + |
| 237 | +## Testing Considerations |
| 238 | + |
| 239 | +### Integration Tests |
| 240 | +Integration tests continue to work unchanged because: |
| 241 | +- They use Testcontainers for dependencies |
| 242 | +- Aspire ServiceDefaults don't interfere with test infrastructure |
| 243 | +- Configuration fallback ensures compatibility |
| 244 | + |
| 245 | +### Local Development |
| 246 | +Developers can choose their preferred approach: |
| 247 | +- Aspire for full observability |
| 248 | +- docker-compose for simpler setup |
| 249 | +- Mix and match as needed |
| 250 | + |
| 251 | +## Future Enhancements |
| 252 | + |
| 253 | +Potential improvements for future iterations: |
| 254 | + |
| 255 | +1. **Deployment Profiles**: Different configurations for dev/staging/prod |
| 256 | +2. **Additional Integrations**: Redis, Azure Service Bus alternatives |
| 257 | +3. **Custom Metrics**: Business-specific metrics in dashboard |
| 258 | +4. **Performance Testing**: Load testing with Aspire infrastructure |
| 259 | +5. **Cloud Deployment**: Azure Container Apps integration |
| 260 | + |
| 261 | +## Summary |
| 262 | + |
| 263 | +This Aspire implementation enhances Chapter 3 with modern cloud-native capabilities while maintaining full backward compatibility. It provides developers with powerful observability and orchestration tools without requiring changes to the core business logic or existing deployment methods. |
0 commit comments