ADR-008: OpenAPI 3.0 Specification Generation¶
Status¶
Proposed - January 2025
Future enhancement to complement custom SDK generation (ADR-007) with industry-standard API documentation.
Context¶
Current State¶
LBS Foundry has implemented a custom SDK generation system (ADR-007) that creates TypeScript and Python clients from C# queries and commands. This provides excellent developer experience for our primary client languages but has some limitations:
Current SDK Generation (ADR-007):
- Best-in-class TypeScript/Python SDKs with custom builder patterns
- Auto-generated from C# [DataContract] attributes
- Optimized bundle sizes with tree-shaking support
- Published to npm and GitHub Releases
- Limited to 2 languages (TypeScript, Python)
- No interactive API documentation
- No standard tooling support (Postman, Swagger UI)
- No contract testing/validation infrastructure
Problem Statement¶
While our custom SDKs provide excellent DX, we lack:
- Interactive Documentation - No Swagger UI or similar tools for exploring the API
- Multi-Language Support - Cannot easily generate clients for other languages (C#, Go, Ruby, etc.)
- API Testing Tools - Cannot import into Postman/Insomnia for manual testing
- Contract Validation - No standard way to validate requests or catch breaking changes
- Industry Standards - Not using OpenAPI 3.0, the industry standard for REST APIs
API Structure¶
Our CQRS API exposes two polymorphic endpoints:
POST /api/readmodel - Accepts any query with discriminator "queryType"
POST /api/command - Accepts any command with discriminator "commandType"
Authentication:
- Bearer JWT (Clerk) for user authentication
- Basic Auth for service accounts
- Role-based authorization via [RequiresRoles] attributes
Example Query:
[DataContract(Name = "GetGroups", Namespace = LbsNamespace.SportQueries)]
public class GetGroupsQuery : ICacheableQuery, IRequireUserContext
{
public string QueryType => this.GetType().GetQueryTypeName();
public required Sport Sport { get; set; }
public string? Series { get; set; } = "premiership";
public int? Season { get; set; }
}
Decision¶
We will implement an OpenAPI 3.0 specification generator that complements (not replaces) our custom SDK generation:
Dual Approach Strategy¶
- Keep Custom SDKs (TypeScript/Python) - Better DX, optimized bundles, custom builder patterns
- Add OpenAPI Spec - Interactive docs, multi-language support, testing tools
This gives us the best of both worlds: - Optimized custom SDKs for primary clients - Standard OpenAPI for documentation and other languages
Architecture¶
Reuse the existing TypeScanner infrastructure from ADR-007 to generate OpenAPI schemas:
┌─────────────────────────────────────────────────────┐
│ LBS.Tools.OpenApiGenerator │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ TypeScanner │→ │ OpenApiBuilder│ │
│ │ (Existing) │ │ (New) │ │
│ └──────────────┘ └──────────────┘ │
└──────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ openapi.json / openapi.yaml │
│ │
│ paths: │
│ /api/readmodel: │
│ post: │
│ requestBody: │
│ oneOf: │
│ - $ref: '#/components/schemas/GetGroups' │
│ - $ref: '#/components/schemas/PlayerSearch'│
│ responses: │
│ 200: │
│ content: │
│ application/json: │
│ oneOf: │
│ - $ref: '#/components/schemas/GroupsViewModel'│
│ - $ref: '#/components/schemas/PlayerSearchResult'│
└─────────────────────────────────────────────────────┘
Key Components¶
1. OpenApiBuilder - Constructs OpenAPI document structure
public class OpenApiBuilder
{
public OpenApiDocument Build(TypeScanResult scanResult)
{
var doc = new OpenApiDocument
{
Info = new OpenApiInfo
{
Title = "LBS Foundry API",
Version = "v1",
Description = "CQRS API for LBS Foundry Platform"
}
};
AddSchemas(doc, scanResult);
AddReadModelEndpoint(doc, scanResult);
AddCommandEndpoint(doc, scanResult);
AddSecuritySchemes(doc);
return doc;
}
}
2. SchemaGenerator - Converts ScannedType → OpenAPI schema
public class SchemaGenerator
{
public OpenApiSchema GenerateSchema(ScannedType type)
{
var schema = new OpenApiSchema
{
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>()
};
// Add discriminator property
schema.Properties["queryType"] = new OpenApiSchema
{
Type = "string",
Enum = new List<IOpenApiAny> { new OpenApiString(type.Name) }
};
// Add all properties
foreach (var prop in type.Properties)
{
schema.Properties[ToCamelCase(prop.Name)] = MapToOpenApiType(prop);
}
schema.Required = GetRequiredProperties(type);
return schema;
}
}
3. PathItemGenerator - Generates endpoint definitions with discriminator support
Generated Output¶
{
"openapi": "3.0.1",
"info": {
"title": "LBS Foundry API",
"version": "v1"
},
"paths": {
"/api/readmodel": {
"post": {
"tags": ["Queries"],
"summary": "Execute a read model query",
"requestBody": {
"content": {
"application/json": {
"schema": {
"oneOf": [
{ "$ref": "#/components/schemas/GetGroups" },
{ "$ref": "#/components/schemas/PlayerSearch" }
],
"discriminator": {
"propertyName": "queryType",
"mapping": {
"GetGroups": "#/components/schemas/GetGroups",
"PlayerSearch": "#/components/schemas/PlayerSearch"
}
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"GetGroups": {
"type": "object",
"required": ["queryType", "sport"],
"properties": {
"queryType": { "type": "string", "enum": ["GetGroups"] },
"sport": { "type": "string" },
"series": { "type": "string", "nullable": true },
"season": { "type": "integer", "nullable": true }
},
"example": {
"queryType": "GetGroups",
"sport": "NRL",
"series": "premiership",
"season": 2025
}
}
},
"securitySchemes": {
"BearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
"description": "Clerk JWT token"
},
"BasicAuth": {
"type": "http",
"scheme": "basic",
"description": "Service account credentials"
}
}
}
}
Consequences¶
Positive¶
- Interactive Documentation - Swagger UI for exploring and testing the API
- Multi-Language Support - Generate clients for 50+ languages using OpenAPI Generator
- Testing Tools Integration - Import into Postman, Insomnia, or generate mock servers
- Contract Validation - Validate requests and catch breaking changes in CI
- Industry Standards - Using OpenAPI 3.0, widely recognized and supported
- Reuses Infrastructure - Leverages existing TypeScanner from ADR-007
- Complements SDKs - Keeps optimized custom SDKs while adding standard docs
Negative¶
- Additional Maintenance - One more output to maintain (though auto-generated)
- Generic Clients - OpenAPI-generated clients are larger and less optimized than custom SDKs
- Development Effort - Requires 4-6 days to implement fully
- CI/CD Changes - Need to add OpenAPI generation to build pipeline
- Learning Curve - Team needs to understand OpenAPI spec format
Risks¶
- Breaking Changes - Need clear versioning strategy for OpenAPI spec
- Discriminator Complexity - Polymorphic endpoints with
oneOfcan be harder to document - Maintenance Drift - OpenAPI spec could drift from actual API if not properly tested
Implementation¶
Phase 1: Core Generator (2-3 days)¶
File Structure:
src/Tools/LBS.Tools.OpenApiGenerator/
├── Program.cs # CLI entry point
├── OpenApiBuilder.cs # Builds OpenAPI document
├── SchemaGenerator.cs # Converts ScannedType → OpenAPI schema
├── PathItemGenerator.cs # Generates path definitions
└── LBS.Tools.OpenApiGenerator.csproj
Phase 2: Enhanced Features (1-2 days)¶
- Add authentication/authorization documentation
- Add role-based security requirements
- Add examples for each schema
- Link queries to their result types
Phase 3: CI/CD Integration (0.5 days)¶
Add to .github/workflows/Build-Container-Apps.yml:
- name: Generate OpenAPI Specification
run: |
dotnet run --project src/Tools/LBS.Tools.OpenApiGenerator/LBS.Tools.OpenApiGenerator.csproj -- \
"src/Apps/Ballr.WebApi/bin/Release/net10.0" \
"/usr/share/dotnet/packs/Microsoft.NETCore.App.Ref/10.0.0/ref/net10.0" \
"docs/openapi.json" \
--format json
- name: Upload OpenAPI Spec
uses: actions/upload-artifact@v4
with:
name: openapi-spec
path: docs/openapi.json
Phase 4: Documentation (0.5 days)¶
- Document usage in developer guide
- Add examples for client generation
- Document Swagger UI integration
Total Effort: 4-6 days
Usage¶
Generate OpenAPI Spec:
# Generate JSON
dotnet run --project src/Tools/LBS.Tools.OpenApiGenerator -- \
"src/Apps/Ballr.WebApi/bin/Release/net10.0" \
"/usr/share/dotnet/packs/Microsoft.NETCore.App.Ref/10.0.0/ref/net10.0" \
"docs/openapi.json" \
--format json
# Generate YAML
dotnet run --project src/Tools/LBS.Tools.OpenApiGenerator -- \
"src/Apps/Ballr.WebApi/bin/Release/net10.0" \
"/usr/share/dotnet/packs/Microsoft.NETCore.App.Ref/10.0.0/ref/net10.0" \
"docs/openapi.yaml" \
--format yaml
Generate Clients:
# Generate TypeScript client (alternative to custom SDK)
npx @openapitools/openapi-generator-cli generate \
-i docs/openapi.json \
-g typescript-axios \
-o clients/typescript
# Generate Python client (alternative to custom SDK)
openapi-generator-cli generate \
-i docs/openapi.json \
-g python \
-o clients/python
# Generate C# client (new capability)
openapi-generator-cli generate \
-i docs/openapi.json \
-g csharp-netcore \
-o clients/csharp
Comparison Table¶
| Feature | Custom SDK (Current) | OpenAPI + Generated Client |
|---|---|---|
| Type Safety | Excellent | Excellent |
| Maintenance | Low (auto-generated) | Low (auto-generated) |
| Language Support | 2 (TS, Python) | 50+ languages |
| Documentation | SDK README | Interactive API docs |
| Testing Tools | Manual | Postman, Swagger UI |
| Standards | Custom | OpenAPI 3.0 standard |
| Bundle Size | Optimized | Larger (generic client) |
| Builder Pattern | Custom helpers | Generic request builders |
References¶
Internal Documentation¶
- ADR-007: TypeScript SDK Auto-Generation - Custom SDK implementation
- Developer Guide - Development patterns
- Common Tasks - Query/command implementation
External Resources¶
- OpenAPI 3.0 Specification
- OpenAPI Generator - Multi-language client generation
- Swagger UI - Interactive API documentation
- Microsoft.OpenApi - .NET OpenAPI library
Related Tools¶
- Postman - API testing with OpenAPI import
- Insomnia - REST client with OpenAPI support
- Pact - Contract testing framework
Priority: Lower - Consider after current SDK work (ADR-007) is stable and mature.
Next Steps (when ready):
1. Create LBS.Tools.OpenApiGenerator project
2. Reuse TypeScanner from SDK generator
3. Build OpenApiBuilder and SchemaGenerator
4. Test with existing queries/commands
5. Add to CI/CD pipeline
6. Publish to docs/openapi.json