リポジトリパターン (GoF)
データアクセスロジックをカプセル化し、ビジネスロジックからデータストアの詳細を隠蔽するパターン
設計パターンDDD
リポジトリパターンとは
リポジトリパターンは、データアクセスロジックをカプセル化し、ビジネスロジックからデータストアの詳細を隠蔽するパターンである。DDD の基本構成要素で、テスト容易性と DB の差し替え可能性を提供する。詳細は「リポジトリパターン実装」を参照。
なぜ必要か
// ❌ ビジネスロジックに DynamoDB の詳細が混在
async function getOrder(id: string) {
const result = await db.send(new GetItemCommand({
TableName: 'orders',
Key: { PK: { S: `ORDER#${id}` }, SK: { S: 'METADATA' } },
}));
return unmarshall(result.Item!);
}
// ✅ リポジトリで隠蔽
async function getOrder(id: string) {
return orderRepository.findById(id);
}
TypeScript での実装
interface OrderRepository {
findById(id: string): Promise<Order | null>;
save(order: Order): Promise<void>;
findByUserId(userId: string): Promise<Order[]>;
}
class DynamoDBOrderRepository implements OrderRepository {
constructor(private db: DynamoDBClient, private tableName: string) {}
async findById(id: string): Promise<Order | null> {
const result = await this.db.send(new GetItemCommand({
TableName: this.tableName,
Key: { PK: { S: `ORDER#${id}` }, SK: { S: 'META' } },
}));
return result.Item ? toOrder(unmarshall(result.Item)) : null;
}
async save(order: Order): Promise<void> {
await this.db.send(new PutItemCommand({
TableName: this.tableName,
Item: marshall(toItem(order)),
}));
}
async findByUserId(userId: string): Promise<Order[]> {
const result = await this.db.send(new QueryCommand({
TableName: this.tableName,
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk',
ExpressionAttributeValues: { ':pk': { S: `USER#${userId}` } },
}));
return (result.Items ?? []).map(i => toOrder(unmarshall(i)));
}
}
テスト用のインメモリ実装
class InMemoryOrderRepository implements OrderRepository {
private store = new Map<string, Order>();
async findById(id: string) { return this.store.get(id) ?? null; }
async save(order: Order) { this.store.set(order.id, order); }
async findByUserId(userId: string) {
return [...this.store.values()].filter(o => o.userId === userId);
}
}
リポジトリのメリット
テスト時に InMemory 実装に差し替えるだけで DB なしにテストでき、DynamoDB から Aurora への DB 変更もリポジトリの実装を差し替えるだけで済む。ビジネスロジックが DB の詳細を知らない状態を保てるため、データアクセスの責務が明確に分離される。
全体像を把握するには関連書籍も有用。