Multi-agent AI laboratory with ASP.NET Core 8.0 backend and Flutter frontend. Implements CQRS architecture, OpenAPI contract-first API design. BACKEND: Agent management, conversations, executions with PostgreSQL + Ollama FRONTEND: Cross-platform UI with strict typing and Result-based error handling Co-Authored-By: Jean-Philippe Brule <jp@svrnty.io>
6.6 KiB
API Contract Workflow
Single Source of Truth: Backend OpenAPI Specification
Overview
This project uses OpenAPI-driven development where the backend C# API is the authoritative source for API contracts. The frontend Flutter app automatically generates type-safe Dart code from the OpenAPI specification.
Architecture
Backend (C#) Frontend (Flutter/Dart)
───────────── ───────────────────────
Controllers with api-schema.json
XML docs ──────────► (copied from backend)
│
docs/openapi.json │
(auto-generated) ──────────► │
▼
lib/api/generated/
(auto-generated types)
│
▼
lib/api/client.dart
(CQRS API client)
│
▼
lib/api/endpoints/
(endpoint extensions)
Backend Responsibilities
1. XML Documentation
All controllers and DTOs must have complete XML documentation:
/// <summary>Gets paginated users with filtering</summary>
/// <param name="page">Page number (1-based)</param>
/// <response code="200">Returns paginated user list</response>
/// <response code="401">Unauthorized</response>
[HttpGet]
[ProducesResponseType(typeof(PagedResult<UserDto>), 200)]
[ProducesResponseType(401)]
public async Task<IActionResult> GetUsers([FromQuery] int page = 1) { }
2. OpenAPI Export
Backend generates docs/openapi.json:
cd backend
dotnet run --project Codex.Api &
sleep 5
curl https://localhost:7108/swagger/v1/swagger.json > docs/openapi.json
pkill -f "Codex.Api"
3. Schema Distribution
Frontend copies docs/openapi.json to api-schema.json:
cp ../backend/docs/openapi.json ./api-schema.json
Frontend Responsibilities
1. Install Dependencies
OpenAPI generator packages are in pubspec.yaml:
dependencies:
http: ^1.2.2
json_annotation: ^4.9.0
dev_dependencies:
build_runner: ^2.4.14
json_serializable: ^6.9.2
openapi_generator_annotations: ^5.0.1
2. Code Generation
Generate Dart types from OpenAPI spec:
flutter pub run build_runner build --delete-conflicting-outputs
3. Generated Output
Code is generated to lib/api/generated/:
- ✅ DO NOT EDIT - These files are auto-generated
- ✅ DO NOT COMMIT - Listed in
.gitignore - ✅ REGENERATE on every API schema update
4. Manual Code (Stable)
These files are manually maintained:
lib/api/client.dart- CQRS client frameworklib/api/types.dart- Core types (Result, ApiError, pagination)lib/api/endpoints/*.dart- Endpoint-specific extensions
Workflow: Making API Changes
Backend Developer Flow
- Update C# code with XML documentation
- Run API to regenerate Swagger
- Export OpenAPI spec:
curl https://localhost:7108/swagger/v1/swagger.json > docs/openapi.json - Commit
docs/openapi.jsonto git - Notify frontend that API contract changed
Frontend Developer Flow
- Pull latest backend changes
- Copy schema:
cp ../backend/docs/openapi.json ./api-schema.json - Regenerate types:
flutter pub run build_runner build --delete-conflicting-outputs - Update endpoint code if needed (new queries/commands)
- Test with new types
Type Safety Guarantees
Strict Typing Rules
All generated code follows project strict typing standards:
- ✅ No
dynamictypes - ✅ No
anytypes - ✅ Explicit type annotations everywhere
- ✅ Null safety enforced
Example: Generated Query
Backend defines:
public record HealthQuery();
Frontend generates:
class HealthQuery {
const HealthQuery();
Map<String, Object?> toJson() => {};
factory HealthQuery.fromJson(Map<String, Object?> json) =>
const HealthQuery();
}
Error Handling
Backend Contract
Backend returns structured errors:
{
"message": "Validation failed",
"statusCode": 422,
"details": "Email is required"
}
Frontend Handling
Client wraps all responses in Result<T>:
final result = await client.executeQuery<UserDto>(
endpoint: 'users/123',
query: const GetUserQuery(),
fromJson: UserDto.fromJson,
);
result.when(
success: (user) => print('Got user: ${user.name}'),
error: (error) => print('Error: ${error.message}'),
);
File Structure
Console/
├── api-schema.json # Copied from backend (DO NOT EDIT)
├── build.yaml # Code generation config
├── lib/api/
│ ├── client.dart # CQRS client (manual)
│ ├── types.dart # Core types (manual)
│ ├── generated/ # Auto-generated (git-ignored)
│ │ └── .gitkeep
│ └── endpoints/
│ └── health_endpoint.dart # Endpoint extensions (manual)
└── .claude-docs/
└── api-contract-workflow.md # This file
Benefits
For Backend
- ✅ Single source of truth (C# code with XML docs)
- ✅ Type-safe APIs enforced by compiler
- ✅ Swagger UI for testing
- ✅ Automatic client generation
For Frontend
- ✅ Type-safe API calls (no runtime errors)
- ✅ Auto-completion in IDE
- ✅ Compile-time validation
- ✅ No manual type definitions
- ✅ Always in sync with backend
For Team
- ✅ Clear contract boundaries
- ✅ Breaking changes caught early
- ✅ No API drift
- ✅ Shared understanding via OpenAPI spec
Troubleshooting
"Generated code has type errors"
Solution: Backend may have incomplete XML docs or invalid schema. Ask backend team to validate openapi.json.
"Types don't match backend"
Solution: Regenerate frontend types:
cp ../backend/docs/openapi.json ./api-schema.json
flutter pub run build_runner build --delete-conflicting-outputs
"Build runner fails"
Solution: Clean and rebuild:
flutter clean
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
References
- OpenAPI Spec:
api-schema.json - Backend Docs:
../backend/docs/ARCHITECTURE.md - Strict Typing:
.claude-docs/strict-typing.md