Interactive

Infrastructure層

外部サービスやデータベースへの接続を担当する層です。弊社ではAdapterパターンで外部依存を隔離します。

Adapterパターン

外部サービスのAPIをAdapterクラスで包み、ビジネスロジックに必要なメソッドだけを公開します。AdapterはContextをコンストラクタで受け取り、c.envから環境変数を参照します。戻り値はT | Errorとし、技術的な原因をエラーにします。

export class StripeAdapter {
  constructor(private readonly c: Context) {}

  async createPayment(props: {
    amount: number
    customerId: string
  }): Promise<{ id: string; secret: string } | Error> {
    try {
      const stripe = new Stripe(this.c.env.stripeApiKey)
      const intent = await stripe.paymentIntents.create({
        amount: props.amount,
        currency: 'jpy',
        customer: props.customerId
      })
      return { id: intent.id, secret: intent.client_secret! }
    } catch (error) {
      // 技術的な原因をカスタムエラーにする
      return new NetworkError('Stripe API通信に失敗しました')
    }
  }
}

Repositoryの実装

Repositoryは集約ルートごとに作成し、メソッドはfindOnefindManywritedeleteの4つに限定します。Contextからc.var.dbでORMを参照し、データベースから取得した関連データはフラットなEntityに変換します。

export class UserProfileRepository {
  constructor(private readonly c: Context) {}

  async findOne(
    where: { id?: string }
  ): Promise<UserProfileEntity | null> {
    const data = await this.c.var.db.user.findFirst({
      where,
      include: { profile: true }
    })
    if (!data) return null

    // JOINしたデータをフラットなEntityに変換
    return new UserProfileEntity({
      id: data.id,
      name: data.name,
      bio: data.profile?.bio || '',
      avatar: data.profile?.avatar || ''
    })
  }
}

Drizzleを使用する場合も同じ構造です。ORMが異なってもRepositoryのインターフェースは変わりません。