- Updated skill documentation files to include structured metadata for better organization. - Enhanced bilingual descriptions and guidelines for clarity in both English and Vietnamese. - Refined sections on usage, best practices, and related skills to ensure consistency across all documentation. - Improved formatting and removed outdated references to streamline the documentation experience. - Added best practices checklists to relevant skills for better usability and adherence to standards.
203 lines
5.5 KiB
Markdown
203 lines
5.5 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 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
|
|
|
|
## 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
|