🌊

サービスクラス設計指南

に公開

サービスクラス設計指南

基本原則

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. このサービスを一言で説明できますか?
  2. 変更理由は1つですか?
  3. 「〜も〜も」という説明になっていませんか?

Discussion