---
name: service-layer-patterns
description: Service layer patterns for GoodGo microservices. Use for business logic, dependency injection, service composition, or controller-repository separation.
compatibility: "typescript>=5"
---

# 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 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:

```typescript
export class UserService {
  constructor(
    private userRepository: UserRepository,
    private cacheService: CacheService
  ) {}
}
```

## Patterns

### 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:

```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

## Quick Reference

**Service Responsibilities:**
| Layer | Responsibility | Example |
|-------|----------------|---------|
| **Controller** | HTTP handling, validation | Parse request, send response |
| **Service** | Business logic | Validate rules, orchestrate |
| **Repository** | Data access | CRUD operations |

**Service Template:**
```typescript
export class EntityService {
  constructor(
    private repository: EntityRepository,
    private cache?: CacheService
  ) {}

  async findById(id: string): Promise<Entity> {
    const entity = await this.repository.findById(id);
    if (!entity) throw new NotFoundError('Entity');
    return entity;
  }

  async create(data: CreateDto): Promise<Entity> {
    // Business validation
    await this.validateBusinessRules(data);
    return this.repository.create(data);
  }
}
```

**Dependency Injection Pattern:**
```typescript
// Module setup
const repository = new EntityRepository(prisma);
const service = new EntityService(repository, cacheService);
const controller = new EntityController(service);
```

**Common Patterns:**
| Pattern | When to Use |
|---------|-------------|
| **Caching** | Frequently accessed data |
| **Composition** | Complex operations across domains |
| **Validation** | Business rule enforcement |
| **Logging** | Audit and debugging |

## 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
