Skip to main content

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:

  1. Define all technical indicators in OpenAPI schema
  2. Generate TypeScript types and Python models
  3. Use runtime validation in both services
  4. 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.