SDK Architecture
This document provides a detailed look at CVT's SDK design patterns, adapter architecture, and cross-language consistency strategies.
Overview
CVT SDKs provide:
- gRPC communication: Protocol handling and connection management
- Configuration: Server address, TLS, authentication
- Validation API: Register schemas, validate interactions
- HTTP Adapters: Automatic validation of HTTP client calls
- Mock Adapters: Schema-compliant response generation
SDKs do NOT provide:
- Test frameworks (use Jest, Pytest, JUnit, etc.)
- HTTP clients (use Axios, requests, http.Client, etc.)
- Schema management (handled by server)
Core Abstraction
Validator Client
All SDKs implement a common ContractValidator interface:
┌─────────────────────────────────────────────────────────────┐
│ ContractValidator │
├─────────────────────────────────────────────────────────────┤
│ │
│ Constructor │
│ ├─ address: string (server address) │
│ ├─ tls?: TLSConfig (optional TLS settings) │
│ └─ apiKey?: string (optional authentication) │
│ │
│ Methods │
│ ├─ registerSchema(id, content, options?) │
│ ├─ validate(request, response) │
│ ├─ validateProducerResponse(method, path, response) │
│ ├─ compareSchemas(id, oldVersion, newVersion) │
│ ├─ generateFixture(id, method, path) │
│ ├─ registerConsumer(config) │
│ ├─ listConsumers(schemaId, environment) │
│ ├─ canIDeploy(schemaId, version, environment) │
│ └─ close() │
│ │
└─────────────────────────────────────────────────────────────┘
Consistent API Across Languages
// Node.js
const validator = new ContractValidator("localhost:9550");
await validator.registerSchema("my-api", "./openapi.json");
const result = await validator.validate(request, response);
# Python
validator = ContractValidator("localhost:9550")
validator.register_schema("my-api", "./openapi.json")
result = validator.validate(request, response)
// Go
validator, _ := cvt.NewContractValidator("localhost:9550")
validator.RegisterSchema(ctx, "my-api", schemaContent)
result, _ := validator.Validate(ctx, request, response)
// Java
ContractValidator validator = new ContractValidator("localhost:9550");
validator.registerSchema("my-api", schemaContent);
ValidationResult result = validator.validate(request, response);
Consumer Validation Patterns
Direct Validation
Explicit validation in tests:
┌──────────────────────────────────────────────────────────────┐
│ Test Code │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. Make HTTP call │
│ ┌────────────┐ HTTP ┌────────────┐ │
│ │ Your Code │ ────────────► │ Real API │ │
│ └────────────┘ └────────────┘ │
│ │ │ │
│ │ request │ response │
│ ▼ ▼ │
│ 2. Validate with CVT │
│ ┌──────────────────────────────────────────┐ │
│ │ validator.validate(request, response) │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. Assert result │
│ expect(result.valid).toBe(true) │
│ │
└──────────────────────────────────────────────────────────────┘
When to use:
- Unit tests with explicit assertions
- Testing specific edge cases
- Full control over what gets validated
HTTP Adapters
Automatic validation of all HTTP calls:
┌─────────────────────────────────────────────────────────────┐
│ HTTP Client with Adapter │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ │
│ │ Your Code │ │
│ └─────┬──────┘ │
│ │ api.get('/users/123') │
│ ▼ │
│ ┌────────────┐ │
│ │ HTTP │ │
│ │ Client │ │
│ │ (Axios) │ │
│ └─────┬──────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ CVT Adapter │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ 1. Intercept request │ │ │
│ │ │ 2. Make actual HTTP call │──┼──► Real API │
│ │ │ 3. Intercept response │◄─┼── │
│ │ │ 4. Validate both with CVT │ │ │
│ │ │ 5. Return response (or throw) │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Response (or validation error) │
│ │
└─────────────────────────────────────────────────────────────┘
When to use:
- Integration tests
- Zero-touch validation
- Catch all contract violations
Mock Adapters
Schema-compliant response generation:
┌──────────────────────────────────────────────────────────────┐
│ Mock Adapter │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────┐ │
│ │ Your Code │ │
│ └─────┬──────┘ │
│ │ mock.get('/users/123') │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ Mock Adapter │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ 1. Parse request │ │ │
│ │ │ 2. Find matching operation │ │ │
│ │ │ 3. Generate response from schema │ │ No network │
│ │ │ 4. Validate generated response │ │ call! │
│ │ │ 5. Return mock response │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Generated Response │
│ { id: 123, name: "string", email: "user@example.com" } │
│ │
└──────────────────────────────────────────────────────────────┘
When to use:
- API not yet available
- Testing without network access
- Fast, deterministic tests
Producer Validation Patterns
Middleware (Runtime)
Validate requests/responses at runtime:
┌──────────────────────────────────────────────────────────────┐
│ API Server with Middleware │
├──────────────────────────────────────────────────────────────┤
│ │
│ Client Request │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ CVT Middleware │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ 1. Validate incoming request │ │ │
│ │ │ │ │ │
│ │ │ mode: strict → reject 400 │ │ │
│ │ │ mode: warn → log, continue │ │ │
│ │ │ mode: shadow → metrics only │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ Your Handler │ │
│ │ return { id: 123, name: "Alice" } │ │
│ └────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────┐ │
│ │ CVT Middleware │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ 2. Validate outgoing response │ │ │
│ │ │ (log violations, metrics) │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Client Response │
│ │
└──────────────────────────────────────────────────────────────┘
Validation modes:
| Mode | Request Violation | Response Violation | Use Case |
|---|---|---|---|
strict | Reject (400) | Log error | Production enforcement |
warn | Log, continue | Log warning | Gradual rollout |
shadow | Metrics only | Metrics only | Initial deployment |
ProducerTestKit (Test-time)
Validate handler output in tests:
┌──────────────────────────────────────────────────────────────┐
│ Producer Test │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. Call handler directly │
│ ┌──────────────────────────────────────────┐ │
│ │ const response = await myHandler(request)│ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. Validate with ProducerTestKit │
│ ┌──────────────────────────────────────────┐ │
│ │ testKit.validateResponse({ │ │
│ │ method: "GET", │ │
│ │ path: "/users/123", │ │
│ │ statusCode: 200, │ │
│ │ body: response │ │
│ │ }) │ │
│ └──────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. Assert compliance │
│ expect(result.valid).toBe(true) │
│ │
└──────────────────────────────────────────────────────────────┘
When to use:
- Unit testing handlers
- Pre-deployment validation
- Faster than middleware approach
Adapter Pattern
All SDK adapters follow a consistent pattern:
┌─────────────────────────────────────────────────────────────┐
│ Adapter Interface │
├─────────────────────────────────────────────────────────────┤
│ │
│ Configuration │
│ ├─ validator: ContractValidator │
│ ├─ schemaId: string │
│ ├─ onViolation: "throw" | "log" | "ignore" │
│ └─ recordUsedEndpoints: boolean │
│ │
│ Lifecycle │
│ ├─ intercept(request) → modifiedRequest │
│ ├─ intercept(response) → validatedResponse │
│ └─ dispose() │
│ │
└─────────────────────────────────────────────────────────────┘
Language-Specific Implementations
Node.js (Axios Adapter)
const adapter = createAxiosAdapter({
axios: axiosInstance,
validator,
schemaId: "user-api",
onViolation: "throw", // or "log", "ignore"
});
// All calls through this instance are validated
const response = await axiosInstance.get("/users/123");
Implementation: Uses Axios interceptors for request/response interception.
Python (Session Adapter)
session = ContractValidatingSession(
validator=validator,
schema_id="user-api",
on_violation="throw",
)
# All calls through this session are validated
response = session.get("http://api/users/123")
Implementation: Wraps requests.Session with validation hooks.
Go (RoundTripper)
rt := adapters.NewValidatingRoundTripper(adapters.RoundTripperConfig{
Validator: validator,
SchemaID: "user-api",
OnViolation: adapters.OnViolationThrow,
})
client := &http.Client{Transport: rt}
// All calls through this client are validated
resp, _ := client.Get("http://api/users/123")
Implementation: Implements http.RoundTripper interface for interception.
Java (Interceptor)
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new CVTInterceptor(validator, "user-api"))
.build();
// All calls through this client are validated
Response response = client.newCall(request).execute();
Implementation: Uses OkHttp Interceptor for request/response interception.
Error Handling
Validation Result Structure
All SDKs return a consistent validation result:
interface ValidationResult {
valid: boolean;
errors?: string[];
validatedAgainstVersion?: string;
validatedAgainstHash?: string;
}
Error Handling Strategies
| Strategy | Behavior | Use Case |
|---|---|---|
throw | Throw exception on violation | Strict testing |
log | Log warning, return result | Observability |
ignore | Silent, return result | Gradual rollout |
// Throw on violation
const adapter = createAxiosAdapter({
onViolation: "throw",
});
try {
await api.get("/users/123");
} catch (error) {
// ContractValidationError
console.log(error.violations);
}
// Log on violation
const adapter = createAxiosAdapter({
onViolation: "log",
logger: console,
});
// Violations logged, response returned
const response = await api.get("/users/123");
Language-Specific Notes
Node.js
- Proto loading: Dynamic loading via
@grpc/proto-loader - Connection management: gRPC channel reuse
- Async/await: All methods return Promises
- Types: Full TypeScript support
Python
- Package manager: uv (recommended) or pip
- Proto generation: Pre-generated with
grpcio-tools - Async support: Both sync and async APIs
- Type hints: Full typing support
Go
- Proto generation: Pre-generated with protoc-gen-go
- Context: All methods accept
context.Context - Error handling: Go-style
(result, error)returns - HTTP integration: Native
http.RoundTripper
Java
- Build system: Maven with protobuf plugin
- Proto generation: Maven
protobuf-maven-plugin - Async support: CompletableFuture API available
- Spring integration: Auto-configuration support
Implementation Notes
Key implementation files:
| SDK | Main Files |
|---|---|
| Node.js | sdks/node/src/index.ts, sdks/node/src/adapters/ |
| Python | sdks/python/cvt_sdk/__init__.py, sdks/python/cvt_sdk/adapters/ |
| Go | sdks/go/cvt/validator.go, sdks/go/cvt/adapters/ |
| Java | sdks/java/src/main/java/io/github/sahina/sdk/, sdks/java/src/main/java/io/github/sahina/sdk/adapters/ |
In Roadmap
The following SDK features are planned but not yet implemented:
- Automatic endpoint tracking: Auto-detect used endpoints for consumer registration
- Batch validation: Validate multiple interactions in single RPC
- Connection pooling: Improved connection management for high throughput
- Metrics export: SDK-side metrics for client-side monitoring
Related Documentation
- Architecture Overview - System architecture
- SDK Documentation - Language-specific SDK guides
- Consumer Testing Guide - Consumer validation patterns
- Producer Testing Guide - Producer validation patterns