CodebaseSrc
Adapters
Implementations of domain ports, connecting the application to external services and infrastructure. Adapters translate between domain interfaces and specific t
Adapters Layer (adapters/)
Implementations of domain ports, connecting the application to external services and infrastructure. Adapters translate between domain interfaces and specific technologies.
Purpose
Adapters implement the port interfaces defined in domain/ports/, providing concrete integrations with:
- External APIs (NJ Geocoding Service)
- Caching systems (in-memory LRU/LFU cache)
- HTTP clients (fetch-based with retry logic)
Structure
cache/- Cache service implementations (in-memory, factory for multiple types)nj-api/- NJ Office of GIS geocoding API clienthttp/- HTTP client with timeout and retry capabilities
Adapter Implementations
Cache Adapters (cache/)
in-memory-cache.ts- High-performance in-memory cache with hybrid LRU/LFU evictioncache-factory.ts- Factory for creating cache instances (in-memory, Redis placeholder, no-op)
API Adapters (nj-api/)
geocoding-client.ts- ImplementsGeocodingServicePortfor NJ geocoding APIsuggestions-client.ts- ImplementsSuggestionsServicePortfor address autocompleteendpoint-builder.ts- Constructs API URLs with query parametersapi-config.ts- API endpoints and configuration constants
HTTP Adapters (http/)
fetch-client.ts- ImplementsHttpClientPortwith exponential backoff retry logic
Key Principles
Adapter Pattern
Adapters translate between domain concepts and external APIs:
// Domain port interface
interface GeocodingServicePort {
geocodeAddress(address: AddressInput): Promise<GeocodingResult>;
}
// Adapter implements port, calls external API
export class NJGeocodingClient implements GeocodingServicePort {
async geocodeAddress(address: AddressInput): Promise<GeocodingResult> {
const apiResponse = await this.httpClient.get<NJApiResponse>(url);
return this.transformToGeocodingResult(apiResponse.data); // ← Translation
}
}
Dependency Injection
Adapters receive their own dependencies:
// Adapter receives HTTP client (another adapter)
export const createNJGeocodingClient = (
httpClient: HttpClientPort, // ← Injected dependency
): GeocodingServicePort => {
// Implementation
};
Error Transformation
Adapters catch external errors and throw domain errors:
try {
const response = await fetch(url);
} catch (error) {
// Transform infrastructure error → domain error
throw new ApiTimeoutError({ url, timeoutMs });
}
Configuration
Adapters are configured through lib/config.ts:
const config = {
njApi: {
baseUrl: process.env.NJ_API_BASE_URL,
timeoutMs: 10000,
retryEnabled: true,
maxRetries: 3,
},
cache: {
maxEntries: 16384,
ttlDays: 7,
},
};
Testing
Adapters are tested at multiple levels:
- Unit Tests: Isolated with mocked dependencies
- Integration Tests: Real HTTP calls to external APIs (marked with
.integration.test.ts) - Contract Tests: Verify adapters satisfy port interfaces
Adding New Adapters
- Create adapter module in appropriate subdirectory
- Implement one or more domain port interfaces
- Add configuration to
lib/config.ts - Write unit tests with mocked dependencies
- Write integration tests for real external calls
- Document adapter in this README