Client Library Architecture
ECM Protocol client libraries abstract protocol details, providing idiomatic interfaces for developers. This guide covers implementing a compliant client from the ground up.
Core Components
Transport Layer
Implement pluggable transport:
interface Transport {
async request(method: string, path: string, body?: any): Promise<Response>;
async stream(path: string): AsyncIterator<Message>;
close(): void;
}
class HTTPTransport implements Transport {
constructor(baseUrl: string, options: HTTPOptions) {...}
// Implementation...
}
class GRPCTransport implements Transport {
constructor(endpoint: string, options: GRPCOptions) {...}
// Implementation...
}
Serialization
Handle ECM message serialization:
class ECMSerializer {
serialize(context: Context): string {
return JSON.stringify({
ecm_version: "1.0",
payload: context,
timestamp: new Date().toISOString()
});
}
deserialize(data: string): Context {
const envelope = JSON.parse(data);
this.validateVersion(envelope.ecm_version);
return envelope.payload;
}
}
Context Operations
CRUD Implementation
class ContextClient {
constructor(transport: Transport) {
this.transport = transport;
}
async put(context: Context): Promise<ContextRef> {
const response = await this.transport.request(
'POST', '/contexts', context
);
return response.body as ContextRef;
}
async get(id: string): Promise<Context | null> {
try {
const response = await this.transport.request(
'GET', \`/contexts/\${id}\`
);
return response.body as Context;
} catch (error) {
if (error.status === 404) return null;
throw error;
}
}
async query(filter: ContextFilter): Promise<Context[]> {
const response = await this.transport.request(
'POST', '/contexts/query', filter
);
return response.body.contexts;
}
}
Concurrency Control
ETag Handling
async update(id: string, context: Context, etag?: string): Promise<ContextRef> {
const headers = etag ? { 'If-Match': etag } : {};
try {
const response = await this.transport.request(
'PUT', \`/contexts/\${id}\`, context, { headers }
);
return response.body;
} catch (error) {
if (error.status === 409) {
throw new ConcurrencyError('Context modified since read');
}
throw error;
}
}
Subscription Support
Real-Time Updates
subscribe(filter: ContextFilter, handler: (event: ContextEvent) => void): Subscription {
const stream = this.transport.stream(\`/contexts/subscribe?\${encode(filter)}\`);
const subscription = {
cancel: () => stream.close()
};
(async () => {
for await (const message of stream) {
handler(this.serializer.deserialize(message));
}
})();
return subscription;
}
Error Handling
Error Classification
Map protocol errors to client exceptions: 404 to ContextNotFoundError, 409 to ConcurrencyError, 400 to ValidationError, 401/403 to AuthenticationError/AuthorizationError, 429 to RateLimitError.
Testing
Test client comprehensively:
- Unit tests: With mocked transport
- Integration tests: Against test server
- Protocol compliance: Verify against spec
- Error handling: All error scenarios
Conclusion
A well-implemented ECM Protocol client provides clean abstraction over protocol details. Focus on transport pluggability, proper error handling, and comprehensive testing for a production-ready library.