Interactive

Application層

複数のドメインオブジェクトや外部サービスを調整することで、ClaudeCodeはビジネスユースケースを実現する処理フローを生成できます。弊社ではトランザクション境界とエラーハンドリングをApplication層に集約し、処理の流れを明確にします。

弊社の推奨ルール

  • executeメソッドで統一 - すべてのServiceクラスはexecuteメソッドを持つ
  • Props型で引数定義 - 複数の引数はPropsオブジェクトにまとめる
  • 依存性注入の活用 - コンストラクタのデフォルト引数で依存関係を管理
  • 単一責任の原則 - 1つのServiceは1つのユースケースのみを扱う
  • エラーのユーザーフレンドリー化 - Infrastructure層のエラーをユーザー向けメッセージに変換

ClaudeCodeでの利用

基本的なServiceの作成依頼

ビジネスロジックの調整が必要な場合

ユーザー登録のServiceクラスを作成して。メール送信とDB登録を含む

複数ドメインの連携依頼

複数のドメインオブジェクトを調整する場合

注文処理のServiceを実装して。在庫確認、決済、配送手配を順番に実行

外部サービス連携の依頼

外部APIとドメインロジックを組み合わせる場合

決済ServiceでStripeと連携。決済後にDBとメール通知を更新

バッチ処理の実装依頼

定期的な処理や大量データ処理が必要な場合

月次請求のバッチServiceを作成。未払いユーザーに請求書を送信

単純な処理ではなくServiceパターンを使う

弊社では、2つ以上の処理を組み合わせる場合や、3つ以上のオブジェクトを調整する場合にService層を導入します。単一の処理は直接実装で十分です。

直接実装で十分な場合

単純なCRUD操作や単一の外部API呼び出しは、Service層を経由せず直接実装します。

// Interface層で直接処理
app.get('/users/:id', async (c) => {
  const user = await prisma.user.findUnique({
    where: { id: c.req.param('id') }
  })
  return c.json(user)
})

// 単純な外部API呼び出し
app.get('/weather', async (c) => {
  const response = await fetch('https://api.weather.com/current')
  return c.json(await response.json())
})

Service層が必要な場合

複数の処理を調整する必要がある場合、Service層でユースケースをカプセル化します。

// Application層: 複数の処理を調整
type Props = {
  email: string
  name: string
  planId: string
}

export class UserRegistrationService {
  constructor(
    private readonly c: Context,
    private readonly deps = {
      db: prisma,
      email: new EmailService(),
      stripe: new StripeAdapter({ apiKey: c.env.stripeApiKey })
    }
  ) {}

  async execute(props: Props) {
    // 1. メール重複チェック
    const existing = await this.deps.db.user.findUnique({
      where: { email: props.email }
    })
    if (existing) {
      throw new BusinessError(
        'このメールアドレスは既に使用されています',
        'EMAIL_ALREADY_EXISTS',
        400
      )
    }

    try {
      // 2. Stripeで顧客作成
      const customerId = await this.deps.stripe.createCustomer({
        email: props.email,
        name: props.name
      })

      // 3. DBにユーザー作成
      const user = await this.deps.db.user.create({
        data: {
          email: props.email,
          name: props.name,
          stripeCustomerId: customerId,
          planId: props.planId
        }
      })

      // 4. ウェルカムメール送信
      await this.deps.email.sendWelcome(user.email)

      return user
    } catch (error) {
      // Infrastructure層のエラーをユーザーフレンドリーに変換
      if (error.code === 'card_declined') {
        throw new BusinessError(
          'カードが承認されませんでした',
          'PAYMENT_DECLINED',
          400
        )
      }
      if (error.message?.includes('email')) {
        throw new BusinessError(
          'メール送信に失敗しました',
          'EMAIL_SEND_FAILED',
          500
        )
      }
      // 予期しないエラーはそのまま投げる
      throw error
    }
  }
}

エラーハンドリング

カスタムエラーの定義

ビジネスエラーを定義し、Infrastructure層の技術的エラーをユーザーフレンドリーなメッセージに変換します。

// ビジネスエラー
export class BusinessError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly statusCode: number = 400
  ) {
    super(message)
    this.name = 'BusinessError'
  }
}

// 使用例
export class OrderService {
  async execute(props: Props) {
    const product = await this.deps.db.product.findUnique({
      where: { id: props.productId }
    })

    if (!product) {
      throw new BusinessError(
        '商品が見つかりません',
        'PRODUCT_NOT_FOUND',
        404
      )
    }

    if (product.stock < props.quantity) {
      throw new BusinessError(
        '在庫が不足しています',
        'INSUFFICIENT_STOCK',
        400
      )
    }

    try {
      // 決済処理
      await this.deps.payment.processPayment({
        amount: product.price * props.quantity,
        customerId: props.customerId
      })
    } catch (error) {
      // Infrastructure層のエラーをユーザー向けに変換
      if (error.type === 'StripeCardError') {
        throw new BusinessError(
          '決済処理に失敗しました。カード情報を確認してください',
          'PAYMENT_FAILED',
          400
        )
      }
      throw error
    }
  }
}

よくある問題

Serviceクラスが肥大化する

単一責任の原則に従い、1つのServiceは1つのユースケースのみを扱います。複雑な処理は複数のServiceに分割します。

依存関係の管理が複雑

コンストラクタのデフォルト引数パターンで依存性注入を実現し、テスト時はモックを注入します。

Infrastructure層のエラー処理

Infrastructure層から返された技術的エラーをApplication層でキャッチし、ユーザーフレンドリーなメッセージに変換してから再スローします。