Service Layer Architecture
Core Principles
Repository Abstraction Chain
The service layer operates on a chain of repository abstractions that provide increasingly specialized functionality:
BaseRepository<T>
└─> AdministrationBaseRepository
└─> FirestoreAdministrationRepository
Each level in this chain serves a specific purpose:
- BaseRepository: Defines fundamental CRUD operations
- Specialized Repositories: Add domain-specific operations
- Concrete Implementations: Provide actual database interactions
class AdministrationService {
constructor(
private adminRepo: AdministrationBaseRepository,
private orgRepo: OrgBaseRepository,
private userClaimRepo: UserClaimBaseRepository
) {}
async getAdministration(id: string): Promise<Result<Administration>> {
return this.adminRepo.get({ id });
}
}
interface Result<T> {
id: string;
data: T;
error?: Error;
metadata?: Record<string, unknown>;
}
Benefits
Testability
- Easy to mock repository dependencies
- Isolated business logic testing
- Clear contract boundaries
Maintainability
- Separation of concerns
- Implementation independence
- Clear dependency hierarchy
Scalability
- New repositories can be added without service changes
- Multiple database support
- Consistent patterns across the system
Service Layer Pattern Overview
The service layer in ROAR's repository pattern is implemented through factory functions that create service instances with their required repository dependencies. This design provides several key benefits:
Factory Function Pattern
- Dependency Injection: Repository dependencies are injected at creation time
- Encapsulation: Implementation details are hidden behind interfaces
- Type Safety: TypeScript ensures correct repository implementations
- Testability: Easy to mock dependencies for unit testing
Service Creation Example
const adminService = createAdministrationService({
administrationRepository, // AdministrationBaseRepository implementation
orgRepository, // OrgBaseRepository implementation
userClaimRepository // UserClaimBaseRepository implementation
});
const idpService = createIdentityProviderService({
identityProviderRepository, // IdentityProviderBaseRepository implementation
userClaimRepository, // UserClaimBaseRepository implementation
userRepository // UserBaseRepository implementation
});
Key Benefits
- Separation of Concerns: Services handle business logic while repositories manage data access
- Loose Coupling: Services depend on repository interfaces, not implementations
- Single Responsibility: Each service handles specific domain operations
- Maintainability: Easy to modify service implementation without affecting other layers
- Scalability: New services can be added following the same pattern
This service layer implementation completes the repository pattern by providing a clean separation between business logic and data access while maintaining type safety and testability.