Top 3 Approaches to Keep API Schemas in Sync
· 2 min read
When building distributed systems with multiple services, keeping API schemas in sync between services can be challenging. Here are three effective approaches to tackle this problem:
1. Schema-First Development with Code Generation
Overview
- Define API schema in a language-agnostic format (OpenAPI/Swagger, Protocol Buffers, GraphQL)
- Use code generators to create types, clients, and server stubs
- Single source of truth for API contracts
Benefits
- Enforces API contract consistency
- Reduces manual type definitions
- Early detection of breaking changes
- IDE support and type safety
Implementation
// Generated from OpenAPI schema
interface TechnicalAnalysis {
rsi?: {
rsi: number;
overbought: boolean;
oversold: boolean;
};
macd?: {
macd: number;
macd_signal: number;
macd_hist: number;
macd_trend_confirm: boolean;
};
// ... other indicators
}
2. Shared Type Libraries
Overview
- Create a shared package containing common types
- Publish as an internal npm/pip package
- Version the package following semver
Benefits
- Direct type reuse across services
- Simplified dependency management
- Version control for breaking changes
Implementation
// @mercury/shared-types
export interface RSIResponse {
rsi: number;
overbought: boolean;
oversold: boolean;
}
// Service A
import { RSIResponse } from '@mercury/shared-types';
class RSIService {
compute(): RSIResponse {
// Implementation
}
}
// Service B
import { RSIResponse } from '@mercury/shared-types';
class RSIClient {
async getRSI(): Promise<RSIResponse> {
// Implementation
}
}
3. Runtime Schema Validation
Overview
- Use runtime validators (Zod, Joi, Pydantic)
- Validate requests/responses at runtime
- Generate types from validators
Benefits
- Runtime type safety
- Self-documenting schemas
- Automatic error handling
- Type inference
Implementation
import { z } from 'zod';
const RSISchema = z.object({
rsi: z.number(),
overbought: z.boolean(),
oversold: z.boolean(),
});
// Types are inferred
type RSIResponse = z.infer<typeof RSISchema>;
// Runtime validation
const validateResponse = (data: unknown): RSIResponse => {
return RSISchema.parse(data);
};
Recommendation
For the Mercury project, we recommend adopting the Schema-First approach using OpenAPI:
- Define all technical indicators in OpenAPI schema
- Generate TypeScript types and Python models
- Use runtime validation in both services
- Automate schema sync in CI/CD pipeline
This ensures:
- Single source of truth for API contracts
- Type safety across services
- Early detection of breaking changes
- Reduced maintenance overhead
Remember: The goal is to make schema inconsistencies impossible rather than catching them in tests.