The SDK follows a layered architecture with clear separation of concerns:
Application Layer
↓
Feed / Snapshot / Customers Modules
↓
Transport Layer (RabbitMQ / HTTP)
↓
Transformation Layer (Mapper / Validator)
↓
Entity Layer (Domain Models)
↓
Error Handling Layer
Logger Adapters
- Interface-based logging (
ILogger) - Multiple implementations: Console, Pino, Winston, Bunyan
- Allows swapping loggers without code changes
HTTP Service Adapters
- Abstract HTTP service interface
- Axios implementation
- Enables testing and alternative implementations
API Factory
- Creates pre-configured API client instances
- Handles dependency injection
- Simplifies client instantiation
Retry Strategies
- Configurable retry logic with exponential backoff
- Different strategies for different error types
- Pluggable retry implementations
Class Transformer Decorators
@Exposefor property mapping@Typefor nested object transformation- Enables automatic DTO ↔ Entity conversion
Entity Handlers
- Register handlers for entity types
- Event-driven message processing
- Loose coupling between feed and application logic
Request DTOs
- Fluent interface for building requests
- Optional parameters with sensible defaults
- Type-safe request construction
Feed (Interface)
↓
RabbitMQFeed (Implementation)
↓
MessageConsumer
↓
Entity Handlers (Registered)
API Client (Interface)
↓
Implementation (Snapshot/Customers)
↓
BaseHttpClient
↓
AxiosService
API Response / RabbitMQ Message
↓
Plain Object (JSON)
↓
class-transformer (plainToInstance)
↓
Typed Entity
Decision: Use TypeScript as the primary language Rationale:
- Type safety prevents runtime errors
- Better IDE support and developer experience
- Self-documenting code through types
Decision: Use class-transformer for object mapping
Rationale:
- Declarative property mapping
- Handles nested objects and arrays
- Support for custom transformations
Decision: Use interfaces for logger, HTTP service, etc. Rationale:
- Enables testing with mocks
- Allows alternative implementations
- Reduces coupling
Decision: Use string IDs instead of numbers for entities Rationale:
- JavaScript number precision limits
- JSON serialization compatibility
- Frontend interoperability
Decision: Custom error classes extending base errors Rationale:
- Type-safe error handling
- Context preservation
- Better debugging experience
- Transport: RabbitMQ/HTTP communication
- Transformation: Data mapping and validation
- Domain: Business entities and logic
- Application: High-level SDK APIs
- Each module has one clear purpose
- Classes have focused responsibilities
- Functions do one thing well
- Dependencies passed to constructors
- Enables testing and flexibility
- Reduces tight coupling
- Validate inputs early
- Clear error messages
- Prevent invalid state propagation
- Receive: RabbitMQ delivers raw message
- Parse: JSON parsing with ID safety
- Validate: Zod schema validation
- Transform: Convert to typed entity
- Dispatch: Call registered handler
- Handle Errors: Wrap and log errors
- Create DTO: Build request with typed DTO
- Validate: Request validation
- Transform: Map DTO to API format
- Send: HTTP request via service
- Parse: Parse JSON response
- Transform: Convert to typed entity
- Handle Errors: Wrap HTTP errors
- Test individual components in isolation
- Mock dependencies
- Use Jest with ts-jest
- Test API client interactions
- Mock HTTP responses
- Test transformation logic