---
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<br/>- HTTP request/response<br/>- Input validation<br/>- Status codes"]
    end
    
    subgraph "Business Layer"
        Service["Service<br/>- Business logic<br/>- Business rules<br/>- Orchestration<br/>- Caching<br/>- Logging"]
    end
    
    subgraph "Data Layer"
        Repository["Repository<br/>- Data access<br/>- CRUD operations<br/>- Database queries"]
        Database[("Database<br/>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<br/>(prisma)"]
        Service["UserService<br/>(repository, cache)"]
        Controller["UserController<br/>(service)"]
    end
    
    subgraph "Router Setup"
        Router["Router<br/>(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<User>(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
