Skip to content

Commit bed282f

Browse files
Copilotkamilbaczek
andcommitted
Add comprehensive Aspire implementation documentation
Co-authored-by: kamilbaczek <74410956+kamilbaczek@users.noreply.github.com>
1 parent a96f259 commit bed282f

1 file changed

Lines changed: 263 additions & 0 deletions

File tree

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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

Comments
 (0)