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は集約ルートごとに作成し、メソッドはfindOne、findMany、write、deleteの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のインターフェースは変わりません。