🌊
サービスクラス設計指南
サービスクラス設計指南
基本原則
1つのサービスクラス = 1つの責務
命名規則
[動詞] + [名詞] + Service
推奨: Create, Update, Delete, Send, Calculate, Validate
避ける: Manager, Handler, Processor
❌ 悪い例:複数責務の違反
// 複数の責務を持つサービス(違反)
class UserService {
async call(userData: any) {
// 責務1: ユーザー作成
const user = await User.create(userData);
// 責務2: メール送信
await this.sendEmail(user.email, 'Welcome!');
// 責務3: ポイント付与
await Points.create({ userId: user.id, amount: 100 });
// 責務4: ログ出力
console.log(`User ${user.id} created`);
return user;
}
}
問題点:
- メール送信が失敗したらユーザー作成も失敗する
- ポイント機能を変更するとユーザー作成も影響を受ける
- テストが困難(全ての機能をモックする必要)
- 一言で説明できない「ユーザーを作成して、メールを送って、ポイントも...」
✅ 良い例:単一責務に分離
// 責務1: ユーザー作成のみ
class CreateUserService {
async call(userData: UserData): Promise<User> {
return await User.create(userData);
}
}
// 責務2: メール送信のみ
class SendWelcomeEmailService {
async call(email: string): Promise<void> {
await this.sendEmail(email, 'Welcome!');
}
}
// 責務3: ポイント付与のみ
class GrantWelcomePointsService {
async call(userId: number): Promise<void> {
await Points.create({ userId, amount: 100 });
}
}
// 責務4: 全体制御のみ(組み合わせ)
class RegisterUserWorkflowService {
async call(userData: UserData): Promise<User> {
// メイン処理
const user = await new CreateUserService().call(userData);
// 副次的処理(失敗しても登録は成功)
try {
await new SendWelcomeEmailService().call(user.email);
await new GrantWelcomePointsService().call(user.id);
} catch (error) {
console.error('Post-registration failed:', error);
}
return user;
}
}
良い点:
- 各サービスが一言で説明できる
- 独立してテストできる
- メール機能の変更がユーザー作成に影響しない
- 再利用しやすい
判断基準
❌ 責務違反のサイン
// 「〜も〜も」の説明になる
class ProcessOrderService {
async call(order: Order) {
await this.validateOrder(order); // 検証も
await this.calculatePrice(order); // 計算も
await this.processPayment(order); // 決済も
await this.sendConfirmation(order); // 通知も
await this.updateInventory(order); // 在庫更新も
}
}
✅ 責務が明確
// 一言で説明できる
class ValidateOrderService {
async call(order: Order): Promise<boolean> {
// 注文を検証する
}
}
class CalculateOrderPriceService {
async call(order: Order): Promise<number> {
// 注文価格を計算する
}
}
class ProcessOrderPaymentService {
async call(order: Order): Promise<Payment> {
// 注文決済を処理する
}
}
実装パターン
interface ServiceResult<T> {
success: boolean;
data?: T;
errors?: string[];
}
class CreateUserService {
async call(userData: UserData): Promise<ServiceResult<User>> {
try {
const user = await User.create(userData);
return { success: true, data: user };
} catch (error) {
return { success: false, errors: [error.message] };
}
}
}
チェックリスト
- 一言で機能を説明できる
- 「〜も〜も」という説明にならない
- 変更理由が1つ
- 独立してテストできる
- 「動詞 + 名詞 + Service」で命名
まとめ
責務を1つに絞ることで得られるもの:
- 🧠 理解しやすさ - 一目で何をするかわかる
- 🔧 保守性 - 変更の影響範囲が限定される
- 🧪 テストしやすさ - 独立してテストできる
- 🔄 再利用性 - 他の場所でも使える
覚えておくべき3つの質問:
- このサービスを一言で説明できますか?
- 変更理由は1つですか?
- 「〜も〜も」という説明になっていませんか?
Discussion