Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
- Implemented MigrationCategoryTests to validate migration categorization for startup, release, seed, and data migrations. - Added tests for edge cases, including null, empty, and whitespace migration names. - Created StartupMigrationHostTests to verify the behavior of the migration host with real PostgreSQL instances using Testcontainers. - Included tests for migration execution, schema creation, and handling of pending release migrations. - Added SQL migration files for testing: creating a test table, adding a column, a release migration, and seeding data.
756 lines
21 KiB
Markdown
756 lines
21 KiB
Markdown
# Step 28: Agent Process Guidelines
|
|
|
|
## Overview
|
|
|
|
This document provides comprehensive guidelines for AI agents (Claude, Copilot, etc.) implementing the Stella Router. It establishes conventions, patterns, and decision frameworks to ensure consistent, high-quality implementations across all phases.
|
|
|
|
## Goals
|
|
|
|
1. Define clear coding standards and patterns for Router implementation
|
|
2. Establish decision frameworks for common scenarios
|
|
3. Provide checklists for implementation quality
|
|
4. Document testing requirements and coverage expectations
|
|
5. Define commit and PR conventions
|
|
|
|
## Implementation Standards
|
|
|
|
### Code Organization
|
|
|
|
```
|
|
src/Router/
|
|
├── StellaOps.Router.Core/ # Core abstractions and contracts
|
|
│ ├── Abstractions/ # Interfaces
|
|
│ ├── Configuration/ # Config models
|
|
│ ├── Extensions/ # Extension methods
|
|
│ └── Primitives/ # Value types
|
|
│
|
|
├── StellaOps.Router.Gateway/ # Gateway implementation
|
|
│ ├── Routing/ # Route matching
|
|
│ ├── Handlers/ # Route handlers
|
|
│ ├── Pipeline/ # Request pipeline
|
|
│ └── Middleware/ # Gateway middleware
|
|
│
|
|
├── StellaOps.Router.Transport/ # Transport implementations
|
|
│ ├── InMemory/ # In-process transport
|
|
│ ├── Tcp/ # TCP transport
|
|
│ └── Tls/ # TLS transport
|
|
│
|
|
├── StellaOps.Router.Microservice/ # Microservice SDK
|
|
│ ├── Hosting/ # Host builder
|
|
│ ├── Endpoints/ # Endpoint handling
|
|
│ └── Context/ # Request context
|
|
│
|
|
├── StellaOps.Router.Security/ # Security components
|
|
│ ├── Jwt/ # JWT validation
|
|
│ ├── Claims/ # Claim hydration
|
|
│ └── RateLimiting/ # Rate limiting
|
|
│
|
|
└── StellaOps.Router.Observability/ # Observability
|
|
├── Logging/ # Structured logging
|
|
├── Metrics/ # Prometheus metrics
|
|
└── Tracing/ # OpenTelemetry tracing
|
|
```
|
|
|
|
### Naming Conventions
|
|
|
|
| Element | Convention | Example |
|
|
|---------|------------|---------|
|
|
| Interfaces | `I` prefix, noun/adjective | `IRouteHandler`, `IConnectable` |
|
|
| Classes | PascalCase, noun | `JwtValidator`, `RouteTable` |
|
|
| Async methods | `Async` suffix | `ValidateTokenAsync`, `SendAsync` |
|
|
| Config classes | `Options` or `Configuration` suffix | `JwtValidationOptions` |
|
|
| Event handlers | `On` prefix | `OnConnectionEstablished` |
|
|
| Factory methods | `Create` prefix | `CreateHandler`, `CreateConnection` |
|
|
| Boolean properties | `Is`/`Has`/`Can` prefix | `IsValid`, `HasExpired`, `CanRetry` |
|
|
|
|
### File Structure
|
|
|
|
```csharp
|
|
// File: StellaOps.Router.Core/Abstractions/IRouteHandler.cs
|
|
|
|
// 1. License header (if required)
|
|
// 2. Using statements (sorted: System, Microsoft, Third-party, Internal)
|
|
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Router.Core.Configuration;
|
|
|
|
// 3. Namespace (one per file, matches folder structure)
|
|
namespace StellaOps.Router.Core.Abstractions;
|
|
|
|
// 4. XML documentation
|
|
/// <summary>
|
|
/// Handles requests for a specific route type.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Implementations must be thread-safe and support concurrent request handling.
|
|
/// </remarks>
|
|
public interface IRouteHandler
|
|
{
|
|
// 5. Interface members (properties, then methods)
|
|
|
|
/// <summary>
|
|
/// Gets the handler type identifier.
|
|
/// </summary>
|
|
string HandlerType { get; }
|
|
|
|
/// <summary>
|
|
/// Determines if this handler can process the given route.
|
|
/// </summary>
|
|
bool CanHandle(RouteConfiguration route);
|
|
|
|
/// <summary>
|
|
/// Processes an incoming request.
|
|
/// </summary>
|
|
Task<ResponsePayload> HandleAsync(
|
|
RequestPayload request,
|
|
RouteConfiguration route,
|
|
CancellationToken cancellationToken = default);
|
|
}
|
|
```
|
|
|
|
### Error Handling Patterns
|
|
|
|
```csharp
|
|
// Pattern 1: Result types for expected failures
|
|
public readonly struct Result<T>
|
|
{
|
|
public T? Value { get; }
|
|
public Error? Error { get; }
|
|
public bool IsSuccess => Error == null;
|
|
|
|
private Result(T? value, Error? error)
|
|
{
|
|
Value = value;
|
|
Error = error;
|
|
}
|
|
|
|
public static Result<T> Success(T value) => new(value, null);
|
|
public static Result<T> Failure(Error error) => new(default, error);
|
|
|
|
public Result<TNext> Map<TNext>(Func<T, TNext> map) =>
|
|
IsSuccess ? Result<TNext>.Success(map(Value!)) : Result<TNext>.Failure(Error!);
|
|
|
|
public async Task<Result<TNext>> MapAsync<TNext>(Func<T, Task<TNext>> map) =>
|
|
IsSuccess ? Result<TNext>.Success(await map(Value!)) : Result<TNext>.Failure(Error!);
|
|
}
|
|
|
|
public record Error(string Code, string Message, Exception? Inner = null);
|
|
|
|
// Usage
|
|
public async Task<Result<JwtClaims>> ValidateTokenAsync(string token)
|
|
{
|
|
try
|
|
{
|
|
var claims = await _validator.ValidateAsync(token);
|
|
return Result<JwtClaims>.Success(claims);
|
|
}
|
|
catch (SecurityTokenExpiredException ex)
|
|
{
|
|
return Result<JwtClaims>.Failure(new Error("TOKEN_EXPIRED", "JWT has expired", ex));
|
|
}
|
|
catch (SecurityTokenInvalidSignatureException ex)
|
|
{
|
|
return Result<JwtClaims>.Failure(new Error("INVALID_SIGNATURE", "JWT signature invalid", ex));
|
|
}
|
|
}
|
|
|
|
// Pattern 2: Exceptions for unexpected failures
|
|
public class RouterException : Exception
|
|
{
|
|
public string ErrorCode { get; }
|
|
public int StatusCode { get; }
|
|
|
|
public RouterException(string errorCode, string message, int statusCode = 500)
|
|
: base(message)
|
|
{
|
|
ErrorCode = errorCode;
|
|
StatusCode = statusCode;
|
|
}
|
|
}
|
|
|
|
public class ConfigurationException : RouterException
|
|
{
|
|
public ConfigurationException(string message)
|
|
: base("CONFIG_ERROR", message, 500) { }
|
|
}
|
|
|
|
public class TransportException : RouterException
|
|
{
|
|
public TransportException(string message, Exception? inner = null)
|
|
: base("TRANSPORT_ERROR", message, 503) { }
|
|
}
|
|
```
|
|
|
|
### Async Patterns
|
|
|
|
```csharp
|
|
// Pattern 1: CancellationToken propagation
|
|
public async Task<ResponsePayload> HandleAsync(
|
|
RequestPayload request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
// Always check at start of long operations
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
// Propagate to all async calls
|
|
var validated = await _validator.ValidateAsync(request, cancellationToken);
|
|
var enriched = await _enricher.EnrichAsync(validated, cancellationToken);
|
|
var response = await _handler.ProcessAsync(enriched, cancellationToken);
|
|
|
|
return response;
|
|
}
|
|
|
|
// Pattern 2: Timeout handling
|
|
public async Task<T> WithTimeoutAsync<T>(
|
|
Func<CancellationToken, Task<T>> operation,
|
|
TimeSpan timeout,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
|
cts.CancelAfter(timeout);
|
|
|
|
try
|
|
{
|
|
return await operation(cts.Token);
|
|
}
|
|
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
throw new TimeoutException($"Operation timed out after {timeout}");
|
|
}
|
|
}
|
|
|
|
// Pattern 3: Fire-and-forget with logging
|
|
public void FireAndForget(Func<Task> operation, ILogger logger, string operationName)
|
|
{
|
|
_ = Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
await operation();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Fire-and-forget operation {Operation} failed", operationName);
|
|
}
|
|
});
|
|
}
|
|
```
|
|
|
|
### Dependency Injection Patterns
|
|
|
|
```csharp
|
|
// Pattern 1: Constructor injection with validation
|
|
public class JwtValidator : IJwtValidator
|
|
{
|
|
private readonly JwtValidationOptions _options;
|
|
private readonly IKeyProvider _keyProvider;
|
|
private readonly ILogger<JwtValidator> _logger;
|
|
|
|
public JwtValidator(
|
|
IOptions<JwtValidationOptions> options,
|
|
IKeyProvider keyProvider,
|
|
ILogger<JwtValidator> logger)
|
|
{
|
|
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
|
_keyProvider = keyProvider ?? throw new ArgumentNullException(nameof(keyProvider));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
|
|
ValidateOptions(_options);
|
|
}
|
|
|
|
private static void ValidateOptions(JwtValidationOptions options)
|
|
{
|
|
if (string.IsNullOrEmpty(options.Issuer))
|
|
throw new ConfigurationException("JWT issuer is required");
|
|
if (options.ClockSkew < TimeSpan.Zero)
|
|
throw new ConfigurationException("Clock skew cannot be negative");
|
|
}
|
|
}
|
|
|
|
// Pattern 2: Factory registration for complex objects
|
|
public static class ServiceCollectionExtensions
|
|
{
|
|
public static IServiceCollection AddStellaRouter(
|
|
this IServiceCollection services,
|
|
Action<RouterOptions> configure)
|
|
{
|
|
services.Configure(configure);
|
|
|
|
// Core services
|
|
services.AddSingleton<IRouteTable, RouteTable>();
|
|
services.AddSingleton<IRequestPipeline, RequestPipeline>();
|
|
|
|
// Keyed services for handlers
|
|
services.AddKeyedSingleton<IRouteHandler, MicroserviceHandler>("microservice");
|
|
services.AddKeyedSingleton<IRouteHandler, GraphQLHandler>("graphql");
|
|
services.AddKeyedSingleton<IRouteHandler, ReverseProxyHandler>("proxy");
|
|
|
|
// Factory for route handler resolution
|
|
services.AddSingleton<IRouteHandlerFactory>(sp => new RouteHandlerFactory(
|
|
sp.GetServices<IRouteHandler>().ToDictionary(h => h.HandlerType)));
|
|
|
|
return services;
|
|
}
|
|
}
|
|
|
|
// Pattern 3: Scoped services for request context
|
|
public static class RequestScopeExtensions
|
|
{
|
|
public static IServiceCollection AddRequestScope(this IServiceCollection services)
|
|
{
|
|
services.AddScoped<IRequestContext, RequestContext>();
|
|
services.AddScoped(sp => sp.GetRequiredService<IRequestContext>().User);
|
|
services.AddScoped(sp => sp.GetRequiredService<IRequestContext>().CorrelationId);
|
|
|
|
return services;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Decision Framework
|
|
|
|
### When to Create New Types vs. Reuse
|
|
|
|
| Scenario | Decision | Rationale |
|
|
|----------|----------|-----------|
|
|
| Similar data, different context | Create new type | Type safety, clear intent |
|
|
| Same data, same context | Reuse type | DRY, reduce cognitive load |
|
|
| Third-party type | Create wrapper | Abstraction, testability |
|
|
| Config vs. runtime | Separate types | Immutability guarantees |
|
|
|
|
```csharp
|
|
// Example: Separate types for config vs runtime
|
|
public record RouteConfiguration(
|
|
string Path,
|
|
string Method,
|
|
string HandlerType,
|
|
Dictionary<string, string> Metadata);
|
|
|
|
public class CompiledRoute
|
|
{
|
|
public RouteConfiguration Config { get; }
|
|
public Regex PathPattern { get; }
|
|
public IRouteHandler Handler { get; }
|
|
// Runtime-computed fields
|
|
}
|
|
```
|
|
|
|
### When to Use Interfaces vs. Abstract Classes
|
|
|
|
| Use Interface | Use Abstract Class |
|
|
|---------------|-------------------|
|
|
| Multiple inheritance needed | Shared implementation |
|
|
| Contract-only definition | Template method pattern |
|
|
| Third-party implementation | Internal hierarchy only |
|
|
| Mocking/testing priority | Code reuse priority |
|
|
|
|
### Logging Level Guidelines
|
|
|
|
| Level | When to Use | Example |
|
|
|-------|-------------|---------|
|
|
| `Trace` | Internal flow details | `"Route matching attempt for {Path}"` |
|
|
| `Debug` | Diagnostic information | `"Cache hit for key {Key}"` |
|
|
| `Information` | Significant events | `"Request completed: {Method} {Path} → {Status}"` |
|
|
| `Warning` | Recoverable issues | `"Rate limit approaching: {Current}/{Max}"` |
|
|
| `Error` | Failures requiring attention | `"Failed to connect to Authority: {Error}"` |
|
|
| `Critical` | System-wide failures | `"Configuration invalid, router cannot start"` |
|
|
|
|
```csharp
|
|
// Structured logging patterns
|
|
_logger.LogInformation(
|
|
"Request processed: {Method} {Path} → {StatusCode} in {ElapsedMs}ms",
|
|
request.Method,
|
|
request.Path,
|
|
response.StatusCode,
|
|
stopwatch.ElapsedMilliseconds);
|
|
|
|
// Use LoggerMessage for high-performance paths
|
|
private static readonly Action<ILogger, string, string, int, long, Exception?> LogRequestComplete =
|
|
LoggerMessage.Define<string, string, int, long>(
|
|
LogLevel.Information,
|
|
new EventId(1001, "RequestComplete"),
|
|
"Request processed: {Method} {Path} → {StatusCode} in {ElapsedMs}ms");
|
|
|
|
// Usage
|
|
LogRequestComplete(_logger, method, path, statusCode, elapsed, null);
|
|
```
|
|
|
|
## Implementation Checklists
|
|
|
|
### Before Starting a Component
|
|
|
|
- [ ] Read the step documentation thoroughly
|
|
- [ ] Understand dependencies on previous steps
|
|
- [ ] Review related existing code patterns
|
|
- [ ] Identify configuration requirements
|
|
- [ ] Plan test coverage strategy
|
|
|
|
### During Implementation
|
|
|
|
- [ ] Follow naming conventions
|
|
- [ ] Add XML documentation to public APIs
|
|
- [ ] Implement `IDisposable`/`IAsyncDisposable` where needed
|
|
- [ ] Add structured logging at appropriate levels
|
|
- [ ] Handle cancellation tokens throughout
|
|
- [ ] Use result types for expected failures
|
|
- [ ] Validate all configuration at startup
|
|
|
|
### Before Marking Complete
|
|
|
|
- [ ] All public types have XML documentation
|
|
- [ ] Unit tests achieve >80% coverage
|
|
- [ ] Integration tests cover happy path + error cases
|
|
- [ ] No compiler warnings
|
|
- [ ] Code passes all linting rules
|
|
- [ ] Configuration is validated
|
|
- [ ] README/documentation updated if needed
|
|
|
|
### Pull Request Checklist
|
|
|
|
- [ ] PR title follows convention: `feat(router): description`
|
|
- [ ] Description explains what and why
|
|
- [ ] All tests pass
|
|
- [ ] No unrelated changes
|
|
- [ ] Breaking changes documented
|
|
- [ ] Reviewable size (<500 lines preferred)
|
|
|
|
## Testing Requirements
|
|
|
|
### Unit Test Coverage Targets
|
|
|
|
| Component Type | Target Coverage |
|
|
|---------------|-----------------|
|
|
| Core logic | 90% |
|
|
| Handlers | 85% |
|
|
| Middleware | 80% |
|
|
| Configuration | 75% |
|
|
| Extensions | 70% |
|
|
|
|
### Test Structure
|
|
|
|
```csharp
|
|
// Test file naming: {ClassName}Tests.cs
|
|
// Test method naming: {Method}_{Scenario}_{ExpectedResult}
|
|
|
|
public class JwtValidatorTests
|
|
{
|
|
private readonly JwtValidator _sut; // System Under Test
|
|
private readonly Mock<IKeyProvider> _keyProviderMock;
|
|
private readonly Mock<ILogger<JwtValidator>> _loggerMock;
|
|
|
|
public JwtValidatorTests()
|
|
{
|
|
_keyProviderMock = new Mock<IKeyProvider>();
|
|
_loggerMock = new Mock<ILogger<JwtValidator>>();
|
|
|
|
var options = Options.Create(new JwtValidationOptions
|
|
{
|
|
Issuer = "https://auth.example.com",
|
|
Audience = "stella-router"
|
|
});
|
|
|
|
_sut = new JwtValidator(options, _keyProviderMock.Object, _loggerMock.Object);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidateAsync_ValidToken_ReturnsSuccessWithClaims()
|
|
{
|
|
// Arrange
|
|
var token = GenerateValidToken();
|
|
_keyProviderMock
|
|
.Setup(x => x.GetSigningKeyAsync(It.IsAny<string>()))
|
|
.ReturnsAsync(TestKeys.ValidKey);
|
|
|
|
// Act
|
|
var result = await _sut.ValidateAsync(token);
|
|
|
|
// Assert
|
|
Assert.True(result.IsSuccess);
|
|
Assert.NotNull(result.Value);
|
|
Assert.Equal("test-user", result.Value.Subject);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ValidateAsync_ExpiredToken_ReturnsFailure()
|
|
{
|
|
// Arrange
|
|
var token = GenerateExpiredToken();
|
|
|
|
// Act
|
|
var result = await _sut.ValidateAsync(token);
|
|
|
|
// Assert
|
|
Assert.False(result.IsSuccess);
|
|
Assert.Equal("TOKEN_EXPIRED", result.Error!.Code);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
public async Task ValidateAsync_NullOrEmptyToken_ReturnsFailure(string? token)
|
|
{
|
|
// Act
|
|
var result = await _sut.ValidateAsync(token!);
|
|
|
|
// Assert
|
|
Assert.False(result.IsSuccess);
|
|
Assert.Equal("INVALID_TOKEN", result.Error!.Code);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Integration Test Patterns
|
|
|
|
```csharp
|
|
public class RouterIntegrationTests : IClassFixture<RouterTestFixture>
|
|
{
|
|
private readonly RouterTestFixture _fixture;
|
|
|
|
public RouterIntegrationTests(RouterTestFixture fixture)
|
|
{
|
|
_fixture = fixture;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EndToEnd_AuthenticatedRequest_ReturnsSuccess()
|
|
{
|
|
// Arrange
|
|
var client = _fixture.CreateAuthenticatedClient(claims: new()
|
|
{
|
|
["sub"] = "test-user",
|
|
["role"] = "admin"
|
|
});
|
|
|
|
// Act
|
|
var response = await client.GetAsync("/api/users/123");
|
|
|
|
// Assert
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
|
|
var user = await response.Content.ReadFromJsonAsync<UserDto>();
|
|
Assert.NotNull(user);
|
|
Assert.Equal("123", user.Id);
|
|
}
|
|
}
|
|
|
|
// Test fixture
|
|
public class RouterTestFixture : IAsyncLifetime
|
|
{
|
|
private IHost? _gatewayHost;
|
|
private IHost? _microserviceHost;
|
|
|
|
public async Task InitializeAsync()
|
|
{
|
|
// Start microservice
|
|
_microserviceHost = await CreateMicroserviceHost();
|
|
await _microserviceHost.StartAsync();
|
|
|
|
// Start gateway
|
|
_gatewayHost = await CreateGatewayHost();
|
|
await _gatewayHost.StartAsync();
|
|
}
|
|
|
|
public async Task DisposeAsync()
|
|
{
|
|
if (_gatewayHost != null)
|
|
await _gatewayHost.StopAsync();
|
|
if (_microserviceHost != null)
|
|
await _microserviceHost.StopAsync();
|
|
|
|
_gatewayHost?.Dispose();
|
|
_microserviceHost?.Dispose();
|
|
}
|
|
|
|
public HttpClient CreateAuthenticatedClient(Dictionary<string, object> claims)
|
|
{
|
|
var token = GenerateTestToken(claims);
|
|
var client = new HttpClient
|
|
{
|
|
BaseAddress = new Uri("http://localhost:5000")
|
|
};
|
|
client.DefaultRequestHeaders.Authorization =
|
|
new AuthenticationHeaderValue("Bearer", token);
|
|
return client;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Git and PR Conventions
|
|
|
|
### Branch Naming
|
|
|
|
```
|
|
feat/router-<step>-<description>
|
|
fix/router-<issue-number>
|
|
refactor/router-<description>
|
|
test/router-<description>
|
|
docs/router-<description>
|
|
```
|
|
|
|
### Commit Messages
|
|
|
|
```
|
|
<type>(<scope>): <description>
|
|
|
|
[optional body]
|
|
|
|
[optional footer]
|
|
```
|
|
|
|
Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`
|
|
|
|
Examples:
|
|
```
|
|
feat(router): implement JWT validation with per-endpoint keys
|
|
|
|
- Add JwtValidator with configurable key sources
|
|
- Support RS256 and ES256 algorithms
|
|
- Add JWKS endpoint caching with TTL
|
|
|
|
Closes #123
|
|
```
|
|
|
|
### PR Template
|
|
|
|
```markdown
|
|
## Summary
|
|
Brief description of what this PR does.
|
|
|
|
## Changes
|
|
- Change 1
|
|
- Change 2
|
|
- Change 3
|
|
|
|
## Testing
|
|
- [ ] Unit tests added/updated
|
|
- [ ] Integration tests added/updated
|
|
- [ ] Manual testing performed
|
|
|
|
## Checklist
|
|
- [ ] Code follows project conventions
|
|
- [ ] Documentation updated
|
|
- [ ] No breaking changes (or documented if any)
|
|
- [ ] All tests pass
|
|
```
|
|
|
|
## Common Pitfalls to Avoid
|
|
|
|
### Performance
|
|
|
|
```csharp
|
|
// ❌ BAD: Allocating in hot path
|
|
public bool MatchRoute(string path)
|
|
{
|
|
var parts = path.Split('/'); // Allocation
|
|
// ...
|
|
}
|
|
|
|
// ✅ GOOD: Use Span for parsing
|
|
public bool MatchRoute(ReadOnlySpan<char> path)
|
|
{
|
|
// Zero-allocation parsing
|
|
foreach (var segment in path.Split('/'))
|
|
{
|
|
// ...
|
|
}
|
|
}
|
|
|
|
// ❌ BAD: Synchronous I/O blocking async context
|
|
public async Task ProcessAsync()
|
|
{
|
|
var config = File.ReadAllText("config.json"); // Blocking!
|
|
}
|
|
|
|
// ✅ GOOD: Async all the way
|
|
public async Task ProcessAsync()
|
|
{
|
|
var config = await File.ReadAllTextAsync("config.json");
|
|
}
|
|
```
|
|
|
|
### Thread Safety
|
|
|
|
```csharp
|
|
// ❌ BAD: Non-thread-safe collection
|
|
private readonly Dictionary<string, Route> _routes = new();
|
|
|
|
public void AddRoute(string key, Route route)
|
|
{
|
|
_routes[key] = route; // Not thread-safe!
|
|
}
|
|
|
|
// ✅ GOOD: Thread-safe collection
|
|
private readonly ConcurrentDictionary<string, Route> _routes = new();
|
|
|
|
public void AddRoute(string key, Route route)
|
|
{
|
|
_routes[key] = route; // Thread-safe
|
|
}
|
|
|
|
// ✅ GOOD: Immutable update
|
|
private ImmutableDictionary<string, Route> _routes =
|
|
ImmutableDictionary<string, Route>.Empty;
|
|
|
|
public void AddRoute(string key, Route route)
|
|
{
|
|
ImmutableInterlocked.AddOrUpdate(ref _routes, key, route, (_, _) => route);
|
|
}
|
|
```
|
|
|
|
### Resource Management
|
|
|
|
```csharp
|
|
// ❌ BAD: Not disposing resources
|
|
public async Task SendAsync(byte[] data)
|
|
{
|
|
var client = new TcpClient();
|
|
await client.ConnectAsync("host", 9100);
|
|
await client.GetStream().WriteAsync(data);
|
|
// client never disposed!
|
|
}
|
|
|
|
// ✅ GOOD: Proper disposal
|
|
public async Task SendAsync(byte[] data)
|
|
{
|
|
using var client = new TcpClient();
|
|
await client.ConnectAsync("host", 9100);
|
|
await using var stream = client.GetStream();
|
|
await stream.WriteAsync(data);
|
|
}
|
|
|
|
// ✅ GOOD: Connection pooling
|
|
public class ConnectionPool : IDisposable
|
|
{
|
|
private readonly Channel<TcpClient> _pool;
|
|
|
|
public async Task<TcpClient> RentAsync()
|
|
{
|
|
if (_pool.Reader.TryRead(out var client))
|
|
return client;
|
|
return await CreateNewConnectionAsync();
|
|
}
|
|
|
|
public void Return(TcpClient client)
|
|
{
|
|
if (!_pool.Writer.TryWrite(client))
|
|
client.Dispose();
|
|
}
|
|
}
|
|
```
|
|
|
|
## Deliverables
|
|
|
|
| Artifact | Purpose |
|
|
|----------|---------|
|
|
| This document | Agent implementation guidelines |
|
|
| Code templates | Consistent starting points |
|
|
| Checklists | Quality gates |
|
|
| Test patterns | Consistent testing approach |
|
|
|
|
## Next Step
|
|
|
|
[Step 29: Integration Testing & CI →](29-Step.md)
|