--- name: service-layer-patterns description: Service layer organization and patterns for GoodGo microservices. Use when implementing business logic, organizing service classes, using dependency injection, composing services, or separating concerns between controllers and repositories. --- # Service Layer Patterns ## When to Use This Skill Use this skill when: - Implementing business logic in services - Organizing service layer code - Using dependency injection patterns - Composing multiple services together - Separating concerns between controllers, services, and repositories - Handling business rules and validations - Implementing service composition patterns ## Core Concepts ### Service Layer Architecture The service layer follows a three-tier architecture pattern, separating concerns between HTTP handling, business logic, and data access: ```mermaid graph TB subgraph "HTTP Layer" Controller["Controller
- HTTP request/response
- Input validation
- Status codes"] end subgraph "Business Layer" Service["Service
- Business logic
- Business rules
- Orchestration
- Caching
- Logging"] end subgraph "Data Layer" Repository["Repository
- Data access
- CRUD operations
- Database queries"] Database[("Database
Prisma")] end Controller -->|"Calls"| Service Service -->|"Uses"| Repository Repository -->|"Queries"| Database style Controller fill:#e1f5ff style Service fill:#fff4e1 style Repository fill:#e8f5e9 style Database fill:#f3e5f5 ``` ### Service Layer Responsibilities The service layer: - Contains business logic - Orchestrates repository calls - Validates business rules - Handles cross-cutting concerns (caching, logging) - Coordinates multiple repositories - Independent of HTTP transport layer ### Dependency Injection Services use constructor injection for dependencies. The dependency injection flow shows how components are wired together: ```mermaid graph LR subgraph "Module Initialization" Prisma[("Prisma Client")] Cache[("Cache Service")] end subgraph "Dependency Creation" Repo["UserRepository
(prisma)"] Service["UserService
(repository, cache)"] Controller["UserController
(service)"] end subgraph "Router Setup" Router["Router
(controller methods)"] end Prisma -->|"Injected"| Repo Cache -->|"Injected"| Service Repo -->|"Injected"| Service Service -->|"Injected"| Controller Controller -->|"Bound to"| Router style Prisma fill:#f3e5f5 style Cache fill:#f3e5f5 style Repo fill:#e8f5e9 style Service fill:#fff4e1 style Controller fill:#e1f5ff style Router fill:#e1f5ff ``` **Example Implementation:** ```typescript export class UserService { constructor( private userRepository: UserRepository, private cacheService: CacheService ) {} } ``` ## Patterns ### Request Flow Through Layers The following sequence diagram illustrates how a request flows through the Controller → Service → Repository layers: ```mermaid sequenceDiagram participant Client participant Controller participant Service participant Cache participant Repository participant Database Client->>Controller: HTTP Request (GET /users/:id) Controller->>Controller: Validate input Controller->>Service: getUserById(id) Service->>Cache: Check cache alt Cache Hit Cache-->>Service: Return cached user Service-->>Controller: Return user else Cache Miss Service->>Repository: findById(id) Repository->>Database: Query user Database-->>Repository: User data Repository-->>Service: User entity alt User Not Found Service-->>Controller: Throw NotFoundError Controller-->>Client: 404 Not Found else User Found Service->>Cache: Set cache (TTL: 5min) Service-->>Controller: Return user Controller-->>Client: 200 OK + User data end end ``` ### Basic Service Pattern ```typescript import { logger } from '@goodgo/logger'; import { userRepository } from '../repositories/user.repository'; import { NotFoundError } from '../errors/http-error'; export class UserService { async getUserById(id: string) { logger.info('Fetching user by ID', { id }); const user = await userRepository.findById(id); if (!user) { throw new NotFoundError('User', { id }); } return user; } } ``` ### Service with Caching ```typescript export class UserService { constructor( private repository: UserRepository, private cacheService: CacheService ) {} async getUserById(id: string) { const cacheKey = cacheService.keys.user(id); // Try cache first const cached = await this.cacheService.get(cacheKey); if (cached) return cached; // Cache miss - fetch from repository const user = await this.repository.findById(id); if (!user) { throw new NotFoundError('User'); } // Cache for 5 minutes await this.cacheService.set(cacheKey, user, 300); return user; } } ``` ### Service Composition Services can depend on other services to compose complex operations. The following diagram shows service composition: ```mermaid graph TB subgraph "AccessRequestService" ARService["AccessRequestService"] ARRepo["AccessRequestRepository"] end subgraph "Dependencies" UserService["UserService"] RBACService["RBACService"] end subgraph "Supporting Services" UserRepo["UserRepository"] RBACRepo["RBACRepository"] end ARService -->|"Uses"| UserService ARService -->|"Uses"| RBACService ARService -->|"Uses"| ARRepo UserService -->|"Uses"| UserRepo RBACService -->|"Uses"| RBACRepo style ARService fill:#fff4e1 style UserService fill:#fff4e1 style RBACService fill:#fff4e1 style ARRepo fill:#e8f5e9 style UserRepo fill:#e8f5e9 style RBACRepo fill:#e8f5e9 ``` **Implementation Example:** Services can depend on other services: ```typescript export class AccessRequestService { constructor( private accessRequestRepository: AccessRequestRepository, private userService: UserService, private rbacService: RBACService ) {} async createRequest(userId: string, data: CreateRequestDto) { // Use other services const user = await this.userService.getUserById(userId); const hasPermission = await this.rbacService.checkPermission(userId, 'CREATE_REQUEST'); if (!hasPermission) { throw new ForbiddenError('Insufficient permissions'); } return await this.accessRequestRepository.create({ ...data, userId }); } } ``` ### Business Logic Validation Services validate business rules: ```typescript export class UserService { async createUser(data: CreateUserInput) { // Business rule: Check if email exists const existing = await this.repository.findByEmail(data.email); if (existing) { throw new ConflictError('User with this email already exists'); } // Business rule: Validate organization if (data.organizationId) { const org = await this.orgRepository.findById(data.organizationId); if (!org) { throw new NotFoundError('Organization'); } } return await this.repository.create(data); } } ``` ### Service Module Pattern Organize services into modules: ```typescript export class FeatureModule { private controller: FeatureController; private service: FeatureService; private router: Router; constructor() { const repository = new FeatureRepository(prisma); this.service = new FeatureService(repository); this.controller = new FeatureController(this.service); this.router = this.createRouter(); } getRouter(): Router { return this.router; } private createRouter(): Router { const router = Router(); router.get('/', asyncHandler(this.controller.findAll.bind(this.controller))); router.post('/', asyncHandler(this.controller.create.bind(this.controller))); return router; } } ``` ## Best Practices 1. **Single Responsibility**: Each service handles one domain area 2. **Dependency Injection**: Use constructor injection for testability 3. **Business Logic Only**: Keep HTTP concerns in controllers 4. **Use Repositories**: Don't access database directly 5. **Error Handling**: Throw appropriate domain errors 6. **Logging**: Log important operations and errors 7. **Caching**: Implement caching in services, not repositories 8. **Composition**: Compose services for complex operations ## Common Mistakes 1. **HTTP in Services**: Using `req`/`res` in services 2. **Direct Database Access**: Accessing Prisma directly instead of repositories 3. **Too Many Responsibilities**: Service doing too many things 4. **No Error Handling**: Not throwing appropriate errors 5. **Business Logic in Controllers**: Moving business logic to controllers ## Resources - [Feature Service](../../services/iam-service/src/modules/feature/feature.service.ts) - Example service implementation - [Repository Pattern](../repository-pattern/SKILL.md) - Repository patterns - [Caching Patterns](../caching-patterns/SKILL.md) - Caching in services - [Error Handling](../error-handling-patterns/SKILL.md) - Error handling patterns