This document describes the repository abstraction pattern implemented in the Creditra backend to prepare for database integration while maintaining loose coupling.
The repository pattern provides a clean abstraction layer between the business logic (services) and data access, making it easy to swap between different data storage implementations without changing the core application logic.
Routes → Services → Repositories → Data Sources
Define the core data structures and types:
CreditLine.ts- Credit line entities and related typesRiskEvaluation.ts- Risk evaluation entities and factorsTransaction.ts- Transaction entities and status types
Define contracts for data access operations:
CreditLineRepository.ts- CRUD operations for credit linesRiskEvaluationRepository.ts- Risk evaluation storage and retrievalTransactionRepository.ts- Transaction logging and querying
Current in-memory implementations:
InMemoryCreditLineRepository.ts- Memory-based credit line storageInMemoryRiskEvaluationRepository.ts- Memory-based risk evaluation storageInMemoryTransactionRepository.ts- Memory-based transaction storage
Business logic layer that uses repositories:
CreditLineService.ts- Credit line management and validationRiskEvaluationService.ts- Risk assessment and caching logic
Container.ts- Manages repository and service instances
The application logic doesn't depend on specific database implementations. You can easily switch from in-memory storage to PostgreSQL, MongoDB, or any other database.
Services can be tested with mock repositories, and repositories can be tested independently with their own test suites.
- Routes handle HTTP concerns (request/response)
- Services handle business logic and validation
- Repositories handle data persistence
When ready to integrate a real database:
- Create new repository implementations (e.g.,
PostgresCreditLineRepository) - Update the container to use the new implementations
- No changes needed in services or routes
Each repository interface follows consistent patterns:
create()- Create new entitiesfindById()- Find by primary keyfindAll()- List with paginationupdate()- Update existing entitiesdelete()- Remove entitiescount()- Get total count
findByWalletAddress()- Find entities by walletfindLatestByWalletAddress()- Get most recent evaluationdeleteExpired()- Cleanup expired dataisValid()- Check data validity
const container = Container.getInstance();
const creditLine = await container.creditLineService.createCreditLine({
walletAddress: 'wallet123',
creditLimit: '1000.00',
interestRateBps: 500
});const result = await container.riskEvaluationService.evaluateRisk({
walletAddress: 'wallet123',
forceRefresh: false
});To integrate PostgreSQL (or any other database):
- Create Database Repository Implementations
export class PostgresCreditLineRepository implements CreditLineRepository {
constructor(private db: Pool) {}
async create(request: CreateCreditLineRequest): Promise<CreditLine> {
const query = 'INSERT INTO credit_lines (wallet_address, credit_limit, interest_rate_bps) VALUES ($1, $2, $3) RETURNING *';
const result = await this.db.query(query, [request.walletAddress, request.creditLimit, request.interestRateBps]);
return mapRowToCreditLine(result.rows[0]);
}
// ... other methods
}- Update Container Configuration
// In Container.ts constructor
if (process.env.DATABASE_URL) {
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
this._creditLineRepository = new PostgresCreditLineRepository(pool);
} else {
this._creditLineRepository = new InMemoryCreditLineRepository();
}- No Changes Required
- Services continue to work unchanged
- Routes continue to work unchanged
- Tests continue to work with mock repositories
- Test each repository implementation independently
- Verify all interface methods work correctly
- Test edge cases and error conditions
- Use mock repositories to isolate business logic
- Test validation rules and business constraints
- Test error handling and edge cases
- Test complete request/response cycles
- Verify data persistence across requests
- Test API contracts and error responses
- Fast for development and testing
- Data lost on restart
- Memory usage grows with data
- Persistent storage
- Better performance for large datasets
- Support for complex queries and indexing
- Transaction support for data consistency
- All user inputs validated at service layer
- Type safety enforced through TypeScript interfaces
- Sanitization of wallet addresses and amounts
- Repository interfaces don't expose internal implementation details
- Services control access patterns and business rules
- Easy to add authorization checks at service layer
The repository pattern makes it easy to add:
- Query performance monitoring
- Data access logging
- Metrics collection
- Error tracking
Future database implementations can include:
- Connection pool monitoring
- Query execution time tracking
- Database health checks
- Automated failover support