Files
pos-system/docs/en/skills/service-layer-patterns.md
Ho Ngoc Hai 2640b351c3 Enhance documentation with detailed diagrams and structured flows
- Added request/response flow diagrams to api-design and api-gateway-advanced skills for better visualization of processes.
- Introduced configuration loading flow in configuration-management skill to clarify the configuration process.
- Included error propagation flow in error-handling-patterns skill to illustrate error handling across layers.
- Enhanced various skills with additional diagrams to improve understanding of complex concepts.

These updates aim to provide clearer guidance and improve the overall documentation experience for developers.
2026-01-01 23:22:54 +07:00

339 lines
9.3 KiB
Markdown

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