å ç¢ã§ã¹ã±ãŒã©ãã«ãªããŒã¿ããŒã¹ã¢ãŒããã¯ãã£ãæ§ç¯ããããã®äœç³»çã¬ã€ã
ã¯ããã«
æ¬çªã·ã¹ãã ãã¬ãã¥ãŒããéãç§ã¯ããŒã¿æ°žç¶åå±€ãã¢ããªã±ãŒã·ã§ã³ã¢ãŒããã¯ãã£ã®åºç€ã§ãããåæã«æœåšçãªããã«ããã¯ã§ãããããšãäžè²«ããŠèгå¯ããŠããŸããé©åã«èšèšãããããŒã¿å±€ãšæ¥ããããã§æ§ç¯ãããããŒã¿å±€ã®éãã¯ãè² è·ãããã£ããšããã¹ããŒããé²åãããšãããŸãã¯åå2æã«ãã©ã³ã¶ã¯ã·ã§ã³ã®ç°åžžããããã°ãããšãã«æããã«ãªããŸãã
ãã®ã¬ã€ãã§ã¯ããã¡ã€ã³ã¢ãã«ãæ¬çªç°å¢å¯Ÿå¿ã®ãªããžããªå®è£ ã«å€æããããã®å®èšŒæžã¿ã®ãã¿ãŒã³ãããã¥ã¡ã³ãåããŸããRepository ãã¿ãŒã³ãããŒã¿ããŒã¹ã¢ãŒããã¯ãã£ãžã® CQRS ã®é©çšãORM ãããã³ã°æŠç¥ããã€ã°ã¬ãŒã·ã§ã³ã¯ãŒã¯ãããŒããã©ã³ã¶ã¯ã·ã§ã³åŠçãããã³ã³ãã¯ã·ã§ã³ããŒã«èšå®ã«ã€ããŠæ€èšŒããŸãããã¹ãŠå ¬åŒããã¥ã¡ã³ããšå®æŠã§ãã¹ããããå®è·µã«åºã¥ããŠããŸãã
Repository ãã¿ãŒã³: ãã¡ã€ã³ãšããŒã¿ã®éã仲ä»ãã
ãã¿ãŒã³ã®å®çŸ©ãšç®ç
Martin Fowler ã® Patterns of Enterprise Application Architecture ã«ãããæ£èŠã®å®çŸ©ã«ããã°ãRepository ã¯ããã¡ã€ã³ãªããžã§ã¯ãã«ã¢ã¯ã»ã¹ããããã®ã³ã¬ã¯ã·ã§ã³ã®ãããªã€ã³ã¿ãŒãã§ãŒã¹ã䜿çšããŠããã¡ã€ã³ãšããŒã¿ãããã³ã°å±€ã®éã仲ä»ããããã®ã§ãã ãã®æœè±¡åã¯3ã€ã®éèŠãªç®çãæãããŸãïŒ
- åé¢ : ãã¡ã€ã³ããžãã¯ã¯æ°žç¶åã¡ã«ããºã ãèªèããŸãã
- ãã¹ã¿ããªã㣠: Repository ã€ã³ã¿ãŒãã§ãŒã¹ã¯ç°¡åã«ã¢ãã¯åã§ããŸã
- æè»æ§ : å®è£ ã®è©³çŽ°ã¯æ¶è²»è ã«åœ±é¿ãäžããããšãªãé²åã§ããŸã
Repository ãã¿ãŒã³ã¯ ORM ã®çŽæ¥äœ¿çšãšã¯æ ¹æ¬çã«ç°ãªããŸããORM ããšã³ãã£ãã£ã¬ãã«ã® CRUD æäœãæäŸããã®ã«å¯ŸããRepository ã¯ããžãã¹æå³ã衚çŸãããã¡ã€ã³äžå¿ã®ã¯ãšãªã¡ãœãããæäŸããŸãã
TypeORM Repository ã®å®è£
TypeORM 㯠Active Record ãš Data Mapper ã®äž¡æ¹ã®ãã¿ãŒã³ããµããŒãããŠããããªããžããªã¯èªç¶ã« Data Mapper ã¢ãããŒãã«æŽåããŸãã åãšã³ãã£ãã£ã¯ç¬èªã®ãªããžããªãåãåãããã®ãšã³ãã£ãã£ã¿ã€ãã«åºæã®æäœãåŠçããŸãã
åºæ¬ç㪠Repository æ§é
// src/domain/entities/User.ts
import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
@Entity('users')
@Index(['email'], { unique: true })
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 255 })
email: string;
@Column({ type: 'varchar', length: 255 })
name: string;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
@Column({ type: 'timestamp', nullable: true })
lastLoginAt: Date | null;
@Column({ type: 'boolean', default: true })
isActive: boolean;
}
// src/infrastructure/repositories/UserRepository.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../../domain/entities/User';
@Injectable()
export class UserRepository {
constructor(
@InjectRepository(User)
private readonly repository: Repository<User>,
) {}
async findByEmail(email: string): Promise<User | null> {
return this.repository.findOne({
where: { email }
});
}
async findActiveUsers(): Promise<User[]> {
return this.repository.find({
where: { isActive: true },
order: { createdAt: 'DESC' },
});
}
async updateLastLogin(userId: string): Promise<void> {
await this.repository.update(
{ id: userId },
{ lastLoginAt: new Date() }
);
}
async save(user: User): Promise<User> {
return this.repository.save(user);
}
async countActiveUsers(): Promise<number> {
return this.repository.count({
where: { isActive: true },
});
}
}
ãã®å®è£ ã¯ããã€ãã®éèŠãªååã瀺ããŠããŸãïŒ
-
ãã¡ã€ã³åºæã®ã¡ãœãã :
findActiveUsers()ãšupdateLastLogin()ã¯ããžãã¹æäœã衚çŸããŸã - åå®å šæ§ : TypeScript ã¯ãšã³ãã£ãã£ããããã£ã®ã³ã³ãã€ã«ææ€èšŒãä¿èšŒããŸã
- é¢å¿ã®åé¢ : ãªããžããªã¯ã¯ãšãªããžãã¯ããã¡ã€ã³ãšã³ãã£ãã£ããåé¢ããŠã«ãã»ã«åããŸã
TypeORM ã®ãªããžããªã¯åºç€çãªã¡ãœããïŒfindãsaveãupdateãdeleteïŒãæäŸããã«ã¹ã¿ã ãªããžããªã¯ã©ã¹ã¯ãã¡ã€ã³åºæã®ã¯ãšãªã¡ãœããã远å ããŸãã ãã®äºå±€ã¢ãããŒãã¯æè»æ§ãšå©äŸ¿æ§ã®ãã©ã³ã¹ãåããŸãã
CQRS: èªã¿åããšæžã蟌ã¿ã®è²¬ä»»ãåé¢ãã
ãã¿ãŒã³ã®æŠèŠãšé©çšæ§
Command Query Responsibility Segregation (CQRS) ã¯ãç°ãªãã¢ãã«ã䜿çšããŠèªã¿åãæäœãšæžãèŸŒã¿æäœãåé¢ããŸãã ãã®åé¢ã«ãããåã¯ãŒã¯ããŒãã®ç¬ç«ããæé©åãå¯èœã«ãªããŸããããã¯ãé察称ãªèªã¿åã/æžã蟌ã¿ãã¿ãŒã³ãæã€ã·ã¹ãã ã«ãããŠç¹ã«äŸ¡å€ã®ããç¹æ§ã§ãã
Martin Fowler ããã®éèŠãªã¬ã€ãã³ã¹ : ãCQRS ã¯ã·ã¹ãã å šäœã§ã¯ãªããã·ã¹ãã ã®ç¹å®ã®éšåïŒDDD çšèªã§ã¯ BoundedContextïŒã«ã®ã¿äœ¿çšãã¹ãã§ããç¹ã«ãCQRS ããœãããŠã§ã¢ã·ã¹ãã ãæ·±å»ãªå°é£ã«é¥ããã±ãŒã¹ã«ééããããšããããŸããã
ããŒã¿ããŒã¹ã¬ãã«ã® CQRS å®è£
Microsoft Azure ã®ã¢ãŒããã¯ãã£ããã¥ã¡ã³ãã¯ãCQRS ããŒã¿ããŒã¹åé¢ã®ããã€ãã®ã¢ãããŒããæŠèª¬ããŠããŸãïŒ
- èªã¿åãã¬ããªã«ãæã€åäžããŒã¿ããŒã¹ : PostgreSQL èªã¿åãã¬ããªã«ãã¯ãšãªãåŠçãããã©ã€ããªãã³ãã³ããåŠçããŸã
- åå¥ã®è«çããŒã¿ããŒã¹ : èªã¿åãã¯ãŒã¯ããŒããšæžã蟌ã¿ã¯ãŒã¯ããŒãã«å¯Ÿããç°ãªãã¹ããŒãæé©å
- ç°çš®ã¹ã㢠: æžã蟌ã¿çšã®ãªã¬ãŒã·ã§ãã«ããŒã¿ããŒã¹ãèªã¿åãçšã®ããã¥ã¡ã³ãã¹ãã¢
èªã¿åããã¿ãŒã³ãæžã蟌ã¿ãã¿ãŒã³ãšå€§ããç°ãªãå Žåã3çªç®ã®ã¢ãããŒãã¯ç¹ã«å¹æçã§ããããšã蚌æãããŠããŸããe ã³ããŒã¹ã·ã¹ãã ãèããŠã¿ãŸãããïŒ
- æžã蟌ã¿ã¢ãã« : åç §æŽåæ§ãä¿èšŒããæ£èŠåããã PostgreSQL ã¹ããŒã
- èªã¿åãã¢ãã« : 補åã«ã¿ãã°ã¯ãšãªçšã«æé©åããã鿣èŠå MongoDB ããã¥ã¡ã³ã
åææŠç¥
AWS Prescriptive Guidance ã¯2ã€ã®äž»èŠãªåæã¢ãããŒããç¹å®ããŠããŸãïŒ
åæïŒåŒ·ãæŽåæ§ïŒ :
- ããŒã¿ããŒã¹ã¬ãã«ã®ã¬ããªã±ãŒã·ã§ã³ïŒPostgreSQL ã¹ããªãŒãã³ã°ã¬ããªã±ãŒã·ã§ã³ïŒ
- 忣ãã©ã³ã¶ã¯ã·ã§ã³å ã®äºéæžã蟌ã¿
- ãã¬ãŒããªã: å¯çšæ§ã®äœäžãæžã蟌ã¿ã¬ã€ãã³ã·ã®å¢å
éåæïŒçµææŽåæ§ïŒ :
- ã¡ãã»ãŒãžãã¥ãŒçµç±ã®ã€ãã³ãé§ååæ
- Debezium ãªã©ã®ããŒã«ã䜿çšãã Change Data Capture (CDC)
- ãã¬ãŒããªã: äžæçãªäžæŽåãŠã£ã³ããŠãè€éæ§ã®å¢å
ã»ãšãã©ã®ã¢ããªã±ãŒã·ã§ã³ã§ã¯ãéåæåæã«ããçµææŽåæ§ãæé©ãªãã©ã³ã¹ãæäŸããŸããäž»èŠãªå®è£
èŠä»¶ã¯ãæžã蟌ã¿ã¢ãã«ããã®å
ç¢ãªã€ãã³ãçºè¡ã§ãã
// src/application/commands/CreateOrderCommand.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { EventBus } from '../events/EventBus';
import { Order } from '../../domain/entities/Order';
import { OrderCreatedEvent } from '../events/OrderCreatedEvent';
@Injectable()
export class CreateOrderCommandHandler {
constructor(
@InjectRepository(Order)
private readonly orderRepository: Repository<Order>,
private readonly eventBus: EventBus,
) {}
async execute(command: CreateOrderCommand): Promise<void> {
// æ£èŠåãããã³ãã³ãããŒã¿ããŒã¹ã«æžã蟌ã
const order = this.orderRepository.create({
userId: command.userId,
items: command.items,
totalAmount: command.totalAmount,
status: 'pending',
});
await this.orderRepository.save(order);
// èªã¿åãã¢ãã«åæã®ããã«ã€ãã³ããçºè¡
await this.eventBus.publish(
new OrderCreatedEvent(order.id, order.userId, order.totalAmount)
);
}
}
EventBus ã¯èªã¿åãã¢ãã«æŽæ°ãã³ãã©ãŒãžã®éåæé ä¿¡ãåŠçããã¯ãšãªããŒã¿ããŒã¹ã泚æããŒã¿ã®éæ£èŠåãã¥ãŒãç¶æã§ããããã«ããŸãã
ORM ãããã³ã°æŠç¥: ç¶æ¿ãããŒãã«ã«å€æãã
3ã€ã®äž»èŠãªæŠç¥
ãã¡ã€ã³ã¢ãã«ãç¶æ¿ãå©çšããå ŽåãORM ã¯ã¯ã©ã¹éå±€ããªã¬ãŒã·ã§ãã«ã¹ããŒãã«ãããã³ã°ããå¿ èŠããããŸããHibernateãDoctrineãããã³ SQLAlchemy ã®å ¬åŒããã¥ã¡ã³ãã¯ãã¹ãŠã3ã€ã®åºæ¬çãªæŠç¥ã説æããŠããŸãïŒ
1. Single Table Inheritance (STI)
éå±€å ã®ãã¹ãŠã®ã¯ã©ã¹ããå ·äœçãªåã瀺ãèå¥ååãæã€1ã€ã®ããŒãã«ã«ãããã³ã°ãããŸãã
å©ç¹ :
- åªããã¯ãšãªããã©ãŒãã³ã¹ãæã€ã·ã³ãã«ãªã¹ããŒã
- ããªã¢ãŒãã£ãã¯ã¯ãšãªã«çµåãäžèŠ
- å®è£ ãšçè§£ãç°¡å
æ¬ ç¹ :
- ãµãã¯ã©ã¹åºæã®ããããã£ã®ããã®ã¹ããŒã¹åïŒNULL å€ïŒ
- ããŒãã«ã®å¹ ã¯éå±€ã®è€éããšãšãã«å¢å
- ããŒã¿æŽåæ§ã®åé¡ã®å¯èœæ§
2. Joined Table Inheritance (JTI)
åºåºã¯ã©ã¹ãšåãµãã¯ã©ã¹ãåå¥ã®ããŒãã«ãåãåããŸãããµãã¯ã©ã¹ããŒãã«ã¯åºåºããŒãã«ãžã®å€éšããŒåç §ãæã¡ãŸãã
å©ç¹ :
- æ£èŠåãããã¹ããŒãã§åé·æ§ãæå°å
- åºåºããããã£ãšãµãã¯ã©ã¹ããããã£ã®æç¢ºãªåé¢
- åå®å šãªã¹ããŒã匷å¶
æ¬ ç¹ :
- ãµãã¯ã©ã¹ã¯ãšãªã«çµåãå¿ èŠïŒããã©ãŒãã³ã¹ãžã®åœ±é¿ïŒ
- ä¿å®ãããè€éãªã¹ããŒã
- æ¿å ¥æäœãè€æ°ã®ããŒãã«ã«ãŸããã
3. Table-Per-Concrete-Class (TPC)
åå ·è±¡ã¯ã©ã¹ããç¶æ¿ããããã®ãå«ããã¹ãŠã®ããããã£ãå«ãç¬èªã®ããŒãã«ãåãåããŸãã
å©ç¹ :
- å ·è±¡åã¯ãšãªã«çµåãäžèŠ
- åããŒãã«ããšã³ãã£ãã£ãå®å šã«èšè¿°
- åäžåã¯ãšãªã®è¯å¥œãªããã©ãŒãã³ã¹
æ¬ ç¹ :
- 鿣èŠåã¹ããŒããç¶æ¿ãããåãè€è£œ
- ããªã¢ãŒãã£ãã¯ã¯ãšãªã« UNION æäœãå¿ èŠ
- åºåºã¯ã©ã¹ãžã®ã¹ããŒã倿Žããã¹ãŠã®ããŒãã«ã«æ³¢å
TypeORM å®è£ äŸ
TypeORM 㯠Single Table ãš Joined Table æŠç¥ããµããŒãããŠããŸãã以äžã¯ Joined Table ã®å®è£
ã§ãïŒ
// src/domain/entities/Content.ts
import { Entity, PrimaryGeneratedColumn, Column, TableInheritance } from 'typeorm';
@Entity()
@TableInheritance({ column: { type: 'varchar', name: 'type' } })
export abstract class Content {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 500 })
title: string;
@Column({ type: 'text' })
description: string;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;
}
@Entity()
export class Article extends Content {
@Column({ type: 'text' })
body: string;
@Column({ type: 'varchar', length: 255 })
author: string;
@Column({ type: 'int', default: 0 })
readCount: number;
}
@Entity()
export class Video extends Content {
@Column({ type: 'varchar', length: 500 })
videoUrl: string;
@Column({ type: 'int' })
durationSeconds: number;
@Column({ type: 'varchar', length: 100, nullable: true })
resolution: string | null;
}
ãã® Joined Table ã¢ãããŒãã¯3ã€ã®ããŒãã«ãäœæããŸãïŒ
-
content: åºåºããããã£ïŒidãtitleãdescriptionãcreatedAtãtypeïŒ -
article: ãµãã¯ã©ã¹ããããã£ïŒbodyãauthorãreadCountïŒãš content ãžã® FK -
video: ãµãã¯ã©ã¹ããããã£ïŒvideoUrlãdurationSecondsãresolutionïŒãš content ãžã® FK
èå¥åå 'type' ã¯ãæ£èŠåãããã¹ããŒããç¶æããªããããªã¢ãŒãã£ãã¯ã¯ãšãªãå¯èœã«ããŸãã
ãã€ã°ã¬ãŒã·ã§ã³ã®ãã¹ããã©ã¯ãã£ã¹: ããŒãžã§ã³ç®¡çäžã§ã®ã¹ããŒãã®é²å
ãªãåæããããã€ã°ã¬ãŒã·ã§ã³ãªã®ã
TypeORM ã® synchronize: true ãªãã·ã§ã³ã¯ããšã³ãã£ãã£å®çŸ©ãšããŒã¿ããŒã¹ã¹ããŒããèªåçã«æŽåãããŸããããã¯éçºã«äŸ¿å©ãªæ©èœã§ããããããå
¬åŒ TypeORM ããã¥ã¡ã³ããè¿°ã¹ãŠããããã«ïŒãããŒã¿ããŒã¹ã«ããŒã¿ãå
¥ã£ãåŸãæ¬çªç°å¢ã§ã¹ããŒãåæã« synchronize: true ã䜿çšããããšã¯å®å
šã§ã¯ãããŸãããã
ãã€ã°ã¬ãŒã·ã§ã³ã¯ãããŒã«ããã¯æ©èœãåãããããŒãžã§ã³ç®¡çãããç£æ»å¯èœãªã¹ããŒã倿ŽãæäŸããŸããããã¯æ¬çªã·ã¹ãã ã«ãšã£ãŠäžå¯æ¬ ãªç¹æ§ã§ãã
ãã€ã°ã¬ãŒã·ã§ã³ã¯ãŒã¯ãããŒ
2025幎㮠NestJS ãš TypeORM ãã€ã°ã¬ãŒã·ã§ã³ã¬ã€ãã¯ããã®äœç³»çãªã¯ãŒã¯ãããŒãããã¥ã¡ã³ãåããŠããŸãïŒ
- ãšã³ãã£ãã£å®çŸ© : TypeORM ãšã³ãã£ãã£ãå®çŸ©ãŸãã¯å€æŽ
-
ãã€ã°ã¬ãŒã·ã§ã³çæ :
npm run migration:generate -- src/migrations/AddUserLastLoginAtãå®è¡ - çæããã SQL ã®ã¬ãã¥ãŒ : UP ãš DOWN ãã€ã°ã¬ãŒã·ã§ã³ã¡ãœãããæ€èšŒ
- ããŒãžã§ã³ç®¡ç : ãšã³ãã£ãã£å€æŽãšäžç·ã«ãã€ã°ã¬ãŒã·ã§ã³ãã¡ã€ã«ãã³ããã
- ãããã€ã¡ã³ã : æ°ããã³ãŒãããããã€ããåã«ãã€ã°ã¬ãŒã·ã§ã³ãå®è¡
çæããããã€ã°ã¬ãŒã·ã§ã³ã®äŸ
// src/migrations/1696875432123-AddUserLastLoginAt.ts
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddUserLastLoginAt1696875432123 implements MigrationInterface {
name = 'AddUserLastLoginAt1696875432123';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "users"
ADD "last_login_at" TIMESTAMP
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "users"
DROP COLUMN "last_login_at"
`);
}
}
ãã€ã°ã¬ãŒã·ã§ã³ã§ã®ãã©ã³ã¶ã¯ã·ã§ã³å¶åŸ¡
TypeORM ã¯ãã€ã°ã¬ãŒã·ã§ã³çšã«3ã€ã®ãã©ã³ã¶ã¯ã·ã§ã³ã¢ãŒããæäŸããŸãïŒ
- ããã©ã«ã : ãã¹ãŠã®ãã€ã°ã¬ãŒã·ã§ã³ãåäžã®ãã©ã³ã¶ã¯ã·ã§ã³ã§å®è¡ãããŸãïŒå šãç¡ãã®ãããã€ã¡ã³ãïŒ
-
--transaction each: åãã€ã°ã¬ãŒã·ã§ã³ãç¬èªã®ãã©ã³ã¶ã¯ã·ã§ã³ã§å®è¡ãããŸãïŒéšåçãªããŒã«ããã¯ãå¯èœïŒ -
--transaction none: ãã©ã³ã¶ã¯ã·ã§ã³ã©ããã³ã°ãªãïŒCREATE INDEX CONCURRENTLY ãªã©ã®æäœçšïŒ
PostgreSQL ã® CREATE INDEX CONCURRENTLY æäœã¯ãã©ã³ã¶ã¯ã·ã§ã³ãããã¯å
ã§ã¯å®è¡ã§ããªãããããã®ãããªãã€ã°ã¬ãŒã·ã§ã³ã«ã¯ --transaction none ãã©ã°ãå¿
èŠã§ãã
ãã€ã°ã¬ãŒã·ã§ã³ã®è¿œè·¡ãšç¶æ 管ç
TypeORM ã¯ãå®è¡ããããã€ã°ã¬ãŒã·ã§ã³ãèšé²ãã migrations ããŒãã«ãããŒã¿ããŒã¹ã«ç¶æããŸãã ãã®ããŒãã«ã¯ä»¥äžãä¿èšŒããŸãïŒ
- åªçæ§ : ãã€ã°ã¬ãŒã·ã§ã³ã¯æ£ç¢ºã«1åå®è¡ãããŸã
- é åº : ãã€ã°ã¬ãŒã·ã§ã³ã¯æç³»åé ã«å®è¡ãããŸã
- æŽåæ§ : ãã¹ãŠã®ç°å¢ãåäžã®ã¹ããŒãã«åæããŸã
ãã€ã°ã¬ãŒã·ã§ã³ããŒãã«ã¢ãããŒãã¯ãFlywayãLiquibaseãããã³ã»ãšãã©ã®ãã€ã°ã¬ãŒã·ã§ã³ãã¬ãŒã ã¯ãŒã¯ã§äœ¿çšãããŠãããç°å¢å šäœã§ä¿¡é Œæ§ã®é«ãç¶æ 远跡ãæäŸããŸãã
ãã©ã³ã¶ã¯ã·ã§ã³åé¢ãš ACID ä¿èšŒ
PostgreSQL ã® ACID å®è£
PostgreSQL 㯠ACID æºæ ã§ããããã¹ãŠã®ãã©ã³ã¶ã¯ã·ã§ã³ã«å¯Ÿã㊠AtomicityïŒååæ§ïŒãConsistencyïŒäžè²«æ§ïŒãIsolationïŒå颿§ïŒãããã³ DurabilityïŒæ°žç¶æ§ïŒã®ä¿èšŒãæäŸããŸãã ãããã®ããããã£ãçè§£ããããšã§ãæ£ãããã©ã³ã¶ã¯ã·ã§ã³ã®äœ¿çšãå°ãããŸãïŒ
- AtomicityïŒååæ§ïŒ : ãã©ã³ã¶ã¯ã·ã§ã³ã¯å šãç¡ãã®äœæ¥åäœã§ã
- ConsistencyïŒäžè²«æ§ïŒ : ããŒã¿ããŒã¹å¶çŽã¯ãã©ã³ã¶ã¯ã·ã§ã³å¢çãè¶ ããŠåŒ·å¶ãããŸã
- IsolationïŒå颿§ïŒ : 䞊è¡ãã©ã³ã¶ã¯ã·ã§ã³ã¯å¹²æžããŸããïŒèšå®å¯èœãªã¬ãã«ïŒ
- DurabilityïŒæ°žç¶æ§ïŒ : ã³ããããããããŒã¿ã¯ã·ã¹ãã é害ãéããŠæ°žç¶ããŸãïŒWAL çµç±ïŒ
PostgreSQL 㯠Write-Ahead Logging (WAL) ãéããŠæ°žç¶æ§ãå®è£ ããŠãããã³ããã確èªãè¿ãåã«ãã©ã³ã¶ã¯ã·ã§ã³èšé²ããã£ã¹ã¯ã«å°éããŸãã
åé¢ã¬ãã«ãšãã®ãã¬ãŒããªã
PostgreSQL å ¬åŒããã¥ã¡ã³ãã¯4ã€ã®åé¢ã¬ãã«ãå®çŸ©ããŠããŸãããPostgreSQL ã¯3ã€ãå®è£ ããŠããŸãïŒ
Read CommittedïŒããã©ã«ãïŒ
ã¯ãšãªã¯ã¯ãšãªãéå§ãããåã«ã³ããããããããŒã¿ã®ã¿ãåç §ããŸãããã®ã¬ãã«ã¯ããŒãã£ãªãŒããé²ããŸãããå埩äžå¯èœãªèªã¿åããšãã¡ã³ãã ãªãŒããèš±å¯ããŸãã
ãŠãŒã¹ã±ãŒã¹ : ã»ãšãã©ã®ã¢ããªã±ãŒã·ã§ã³ãã©ã³ã¶ã¯ã·ã§ã³ã®æ±çšåé¢
Repeatable Read
ã¯ãšãªã¯ãã©ã³ã¶ã¯ã·ã§ã³éå§æããã®äžè²«ããã¹ãããã·ã§ãããåç §ããŸãããã®ã¬ãã«ã¯ããŒãã£ãªãŒããšå埩äžå¯èœãªèªã¿åããé²ããŸãããçè«çã«ã¯ãã¡ã³ãã ãªãŒããèš±å¯ããŸãïŒãã ããPostgreSQL ã®å®è£ ã¯ãã¡ã³ãã ãé²ããŸãïŒã
ãŠãŒã¹ã±ãŒã¹ : è€æ°ã®ã¯ãšãªã«ããã£ãŠäžè²«ããããŒã¿ãå¿ èŠãšããã¬ããŒã
Serializable
æãå³å¯ãªåé¢ã§ããã©ã³ã¶ã¯ã·ã§ã³ã®é£ç¶å®è¡ããšãã¥ã¬ãŒãããŸãããã¹ãŠã®ç°åžžãé²ããŸãããå詊è¡ããžãã¯ãå¿ èŠãšããçŽåå倱æãåŒãèµ·ããå¯èœæ§ããããŸãã
ãŠãŒã¹ã±ãŒã¹ : 絶察çãªæŽåæ§ãå¿ èŠãšããéèãã©ã³ã¶ã¯ã·ã§ã³
TypeORM ã§ã®å®çšçãªãã©ã³ã¶ã¯ã·ã§ã³åŠç
// src/infrastructure/services/AccountService.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { Account } from '../../domain/entities/Account';
@Injectable()
export class AccountService {
constructor(
@InjectRepository(Account)
private readonly accountRepository: Repository<Account>,
private readonly dataSource: DataSource,
) {}
async transferFunds(
fromAccountId: string,
toAccountId: string,
amount: number
): Promise<void> {
await this.dataSource.transaction(
'SERIALIZABLE', // éèãã©ã³ã¶ã¯ã·ã§ã³çšã®åé¢ã¬ãã«
async (transactionalEntityManager) => {
// SELECT FOR UPDATE ã§èªã¿åã£ãŠããã¯ãååŸ
const fromAccount = await transactionalEntityManager.findOne(Account, {
where: { id: fromAccountId },
lock: { mode: 'pessimistic_write' },
});
const toAccount = await transactionalEntityManager.findOne(Account, {
where: { id: toAccountId },
lock: { mode: 'pessimistic_write' },
});
if (!fromAccount || !toAccount) {
throw new Error('Account not found');
}
if (fromAccount.balance < amount) {
throw new Error('Insufficient funds');
}
// æ®é«æŽæ°ãå®è¡
fromAccount.balance -= amount;
toAccount.balance += amount;
await transactionalEntityManager.save(fromAccount);
await transactionalEntityManager.save(toAccount);
}
);
}
}
ãã®å®è£ ã¯éèŠãªãã©ã³ã¶ã¯ã·ã§ã³ãã¿ãŒã³ã瀺ããŠããŸãïŒ
- æç€ºçãªåé¢ã¬ãã« : SERIALIZABLE ã¯äžŠè¡è»¢éã®ç°åžžãé²ããŸã
- æ²èгçãã㯠: SELECT FOR UPDATE ã¯æŽæ°åªå€±ãé²ããŸã
- ã¢ãããã¯æäœ : ãã¹ãŠã®å€æŽãäžç·ã«ã³ããããŸãã¯ããŒã«ããã¯ãããŸã
- ããžãã¹æ€èšŒ : æ®é«äžè¶³ãã§ãã¯ããã©ã³ã¶ã¯ã·ã§ã³å ã§çºçããŸã
PostgreSQL ã® MVCCïŒMulti-Version Concurrency ControlïŒã·ã¹ãã ã«ãããã»ãšãã©ã®å Žåãèªã¿åãåŽãšæžã蟌ã¿åŽã®ããããã³ã°ãªãã§ãããã®åé¢ã¬ãã«ãå¯èœã«ãªããŸãã
ã³ãã¯ã·ã§ã³ããŒãªã³ã°: ããŒã¿ããŒã¹ã¢ã¯ã»ã¹ã®ã¹ã±ãŒãªã³ã°
ãªãã³ãã¯ã·ã§ã³ããŒãªã³ã°ãéèŠãªã®ã
PostgreSQL ã®ã¢ãŒããã¯ãã£ã¯ã忥ç¶ã«å¯ŸããŠæ°ããããã»ã¹ããã©ãŒã¯ããŸããããã¯çããã©ã³ã¶ã¯ã·ã§ã³ã«ãšã£ãŠé«ã³ã¹ããªæäœã§ããã³ãã¯ã·ã§ã³ããŒãªã³ã°ã¯ã確ç«ãããæ¥ç¶ãåå©çšããããšã§ãã®ã³ã¹ããååŽããŸãã
Stack Overflow ã®ãšã³ãžãã¢ãªã³ã°ããã°ã¯æ¬¡ã®ããã«è¿°ã¹ãŠããŸãïŒãã³ãã¯ã·ã§ã³ããŒãªã³ã°ã¯ããã¹ãŠã®ã¯ãšãªã«å¯ŸããŠæ°ããæ¥ç¶ã確ç«ãããªãŒããŒããããåæžããããŒã¿ããŒã¹æ¥ç¶ãåå©çšããããã«äœ¿çšãããæè¡ã§ããã
ããŒã«ãµã€ãžã³ã°: æ°åŠçã¢ãããŒã
PostgreSQL ã³ãã¯ã·ã§ã³ããŒã«ã®ãµã€ãžã³ã°ã«é¢ããæš©åšããå ¬åŒã¯ãPostgreSQL ã³ãã¥ããã£ããæ¥ãŠããŸãïŒ
connections = ((core_count à 2) + effective_spindle_count)
1ã€ã® SSD ãæã€4ã³ã¢ã®ããŒã¿ããŒã¹ãµãŒããŒã®å ŽåïŒ
- (4 Ã 2) + 1 = 9 connections
ãã®å ¬åŒã¯ãCPU 䜿çšçãšãã£ã¹ã¯ I/O 容éã®ãã©ã³ã¹ãåããŸããããŒã«ã倧ããèšå®ãããããšã³ã³ããã¹ãã¹ã€ããã³ã°ã®ãªãŒããŒããããçºçããå°ãããããšãã¥ãŒã€ã³ã°é å»¶ãçºçããŸãã
PgBouncer: æ¬çªã°ã¬ãŒãã®ã³ãã¯ã·ã§ã³ããŒãªã³ã°
PgBouncer 㯠PostgreSQL ã®æ¥çæšæºã³ãã¯ã·ã§ã³ããŒã©ãŒãšããŠæ©èœãã3ã€ã®ããŒãªã³ã°ã¢ãŒããæäŸããŸãïŒ
Transaction ModeïŒæšå¥šïŒ :
- ãã©ã³ã¶ã¯ã·ã§ã³æéäžã«æ¥ç¶ãå²ãåœãŠ
- COMMIT/ROLLBACK åŸã«ããŒã«ã«æ¥ç¶ãè¿ã
- çããã©ã³ã¶ã¯ã·ã§ã³ã®é«ãæ¥ç¶åå©çšãå¯èœã«ããŸã
Session Mode :
- ã¯ã©ã€ã¢ã³ãã»ãã·ã§ã³æéäžã«æ¥ç¶ãå²ãåœãŠ
- ã¢ããã€ã¶ãªããã¯ãšããªãã¢ãã¹ããŒãã¡ã³ãã«å¿ èŠ
- æ¥ç¶åå©çšãäœããããŒã¿ããŒã¹è² è·ãé«ã
Statement Mode :
- ã¹ããŒãã¡ã³ãããšã«æ¥ç¶ãå²ãåœãŠ
- è€æ°ã¹ããŒãã¡ã³ããã©ã³ã¶ã¯ã·ã§ã³ãšäºææ§ããªã
- æé«ã®åå©çšãæãå€ãã®å¶é
PgBouncer èšå®äŸ
# /etc/pgbouncer/pgbouncer.ini
[databases]
production_db = host=localhost port=5432 dbname=production_db
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
# 4ã³ã¢ããŒã¿ããŒã¹ãµãŒããŒã«åºã¥ãããŒã«ãµã€ãžã³ã°
default_pool_size = 9
max_client_conn = 100
reserve_pool_size = 3
reserve_pool_timeout = 5
# æé©ãªåå©çšã®ããã®ãã©ã³ã¶ã¯ã·ã§ã³ã¬ãã«ããŒãªã³ã°
pool_mode = transaction
# æ¥ç¶ã¿ã€ã ã¢ãŠã
server_idle_timeout = 600
server_lifetime = 3600
server_connect_timeout = 15
# ãã®ã³ã°
log_connections = 1
log_disconnections = 1
log_pooler_errors = 1
äž»èŠãªãã©ã¡ãŒã¿ã®èª¬æ :
-
default_pool_size = 9: ãŠãŒã¶ãŒ/ããŒã¿ããŒã¹ãã¢ããšã®æå€§ãµãŒããŒæ¥ç¶ïŒå ¬åŒã«åºã¥ãïŒ -
max_client_conn = 100: æå€§ã¯ã©ã€ã¢ã³ãæ¥ç¶ïŒãã¥ãŒã€ã³ã°ãæå¹åïŒ -
reserve_pool_size = 3: ãªã¶ãŒãããŒã«çšã®è¿œå æ¥ç¶ -
pool_mode = transaction: ãã©ã³ã¶ã¯ã·ã§ã³å®äºåŸã«æ¥ç¶ãè§£æŸ
åäž PgBouncer ãè¶ ããã¹ã±ãŒãªã³ã°
PgBouncer ã¯ã·ã³ã°ã«ã¹ã¬ããããã»ã¹ãšããŠå®è¡ããã1ã€ã® CPU ã³ã¢ã®ã¿ã䜿çšããŸããé«ã¹ã«ãŒãããã·ã¹ãã ã®å ŽåãCrunchy Data ã¯è€æ°ã® PgBouncer ã€ã³ã¹ã¿ã³ã¹ã®å®è¡ãããã¥ã¡ã³ãåããŠããŸãïŒ
- ããŒããã©ã³ãµãŒã®èåŸã«ããè€æ°ã® PgBouncer ããã»ã¹
- å PgBouncer ã€ã³ã¹ã¿ã³ã¹ãç¬èªã®ããŒã«ãæã€
- éåçãªããŒã«ãµã€ãºã¯äŸç¶ãšããŠã³ã¢ã«ãŠã³ãå ¬åŒã«åŸã
è€æ°ã® PgBouncer ã€ã³ã¹ã¿ã³ã¹ãå¿ èŠãªå åïŒ
- PostgreSQL ãååã«å©çšãããŠããªãéã« PgBouncer ã® CPU ã 100% ã«ãªã
- ããŒã¿ããŒã¹ã«äœè£ãããã«ããããããã¢ããªã±ãŒã·ã§ã³ã¯ãšãªã¬ã€ãã³ã·ãå¢å ãã
çµ±å: æ¬çªå¯Ÿå¿ããŒã¿å±€ã®æ§ç¯
éå±€åã¢ãŒããã¯ãã£ãã¿ãŒã³
ãããã®ãã¿ãŒã³ãçµã¿åããããšãéå±€åãããã¢ãŒããã¯ãã£ãçãŸããŸãïŒ
- ãã¡ã€ã³å±€ : çŽç²ãªããžãã¹ãšã³ãã£ãã£ãšã€ã³ã¿ãŒãã§ãŒã¹
- ãªããžããªå±€ : ãã¡ã€ã³äžå¿ã®ããŒã¿ã¢ã¯ã»ã¹æœè±¡å
- ORM å±€ : TypeORM ãšã³ãã£ãã£ãšãã€ã°ã¬ãŒã·ã§ã³
- æ¥ç¶å±€ : PgBouncer ããŒã«ãšããŒã¿ããŒã¹ã¯ã©ã¹ã¿ãŒ
åå±€ã¯äžã®å±€ã«ã®ã¿äŸåããç¬ç«ãããã¹ããšé²åãå¯èœã«ããŸãã
èšå®ç®¡ç
æ¬çªã·ã¹ãã ã«ã¯ç°å¢åºæã®èšå®ãå¿
èŠã§ãïŒ
// src/config/database.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { DataSourceOptions } from 'typeorm';
export const getDatabaseConfig = (): TypeOrmModuleOptions => {
const isProduction = process.env.NODE_ENV === 'production';
return {
type: 'postgres',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432', 10),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
// ãšã³ãã£ãã£ãšãã€ã°ã¬ãŒã·ã§ã³ã®ãã¹
entities: ['dist/**/*.entity.js'],
migrations: ['dist/migrations/*.js'],
// æ¬çªç°å¢åºæã®èšå®
synchronize: false, // æ¬çªç°å¢ã§ã¯çµ¶å¯Ÿã«äœ¿çšããªã
migrationsRun: false, // CLI çµç±ã§ãã€ã°ã¬ãŒã·ã§ã³ãæç€ºçã«å®è¡
logging: isProduction ? ['error', 'warn'] : true,
// ã³ãã¯ã·ã§ã³ããŒã«èšå®ïŒã¢ããªã±ãŒã·ã§ã³ã¬ãã«ïŒ
extra: {
max: 20, // ã¢ããªã±ãŒã·ã§ã³ããŒã«ãµã€ãº
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 10000,
},
// æ¬çªç°å¢çš SSL
ssl: isProduction ? { rejectUnauthorized: false } : false,
};
};
ãã®èšå®ã¯å€å±€é²åŸ¡ã瀺ããŠããŸãïŒ
- æç€ºçãªãã€ã°ã¬ãŒã·ã§ã³å¶åŸ¡ : èªåã¹ããŒãåæãªã
- ã³ãã¯ã·ã§ã³ããŒãªã³ã° : PgBouncer ã®åã®ã¢ããªã±ãŒã·ã§ã³ã¬ãã«ããŒã«
- ç°å¢åºæã®ãã®ã³ã° : éçºã§ã¯è©³çŽ°ãæ¬çªã§ã¯ãšã©ãŒ
- SSL åŒ·å¶ : æ¬çªç°å¢ã§ã®æå·åæ¥ç¶
ã¢ãã¿ãªã³ã°ãšå¯èŠ³æž¬æ§
æ¬çªããŒã¿å±€ã«ã¯è€æ°ã®ã¬ãã«ã§ã®ã¢ãã¿ãªã³ã°ãå¿ èŠã§ãïŒ
ããŒã¿ããŒã¹ã¬ãã« :
- ã¯ãšãªããã©ãŒãã³ã¹:
pg_stat_statementsæ¡åŒµ - æ¥ç¶æ°:
pg_stat_activityãã¥ãŒ - ã¬ããªã±ãŒã·ã§ã³ã©ã°:
pg_stat_replicationãã¥ãŒ
ã³ãã¯ã·ã§ã³ããŒã«ã¬ãã« :
- ããŒã«äœ¿çšç: PgBouncer ã® SHOW POOLS ã³ãã³ã
- ãã¥ãŒæ·±åºŠ: SHOW CLIENTS åºå
- æ¥ç¶åŸ æ©æé: ã¢ããªã±ãŒã·ã§ã³ã¬ãã«ã®ã¡ããªã¯ã¹
ã¢ããªã±ãŒã·ã§ã³ã¬ãã« :
- Repository ã¡ãœããã®ã¬ã€ãã³ã·
- ãã©ã³ã¶ã¯ã·ã§ã³æéã®ãã¹ãã°ã©ã
- çŽååå€±ææ°ïŒSERIALIZABLE åé¢ã®å ŽåïŒ
PostgreSQL ãš PgBouncer çšã® Prometheus ãšã¯ã¹ããŒã¿ãŒãååšããGrafana ã§ã®å æ¬çãªããã·ã¥ããŒããå¯èœã«ããŸãã
çµè«: äœç³»çãªããŒã¿ã¢ãŒããã¯ãã£
æ¬çªå¯Ÿå¿ã®ããŒã¿ããŒã¹ã¢ãŒããã¯ãã£ãæ§ç¯ããã«ã¯ãããã¥ã¡ã³ãåããããã¿ãŒã³ã®äœç³»çãªé©çšãå¿ èŠã§ããRepository ãã¿ãŒã³ã¯ãã¡ã€ã³ããžãã¯ãæ°žç¶åã®æžå¿µããåé¢ããŸããCQRS ã¯ã¯ãŒã¯ããŒãç¹æ§ãç°ãªãå Žåã«ç¬ç«ããèªã¿åã/æžã蟌ã¿ã®æé©åãå¯èœã«ããŸããORM ãããã³ã°æŠç¥ã¯ãçè§£ããããã¬ãŒããªããæã€ãªããžã§ã¯ãéå±€ããªã¬ãŒã·ã§ãã«ã¹ããŒãã«å€æããŸãããã€ã°ã¬ãŒã·ã§ã³ã¯ããŒãžã§ã³ç®¡çãããã¹ããŒãé²åãæäŸããŸãããã©ã³ã¶ã¯ã·ã§ã³åé¢ã¬ãã«ã¯æŽåæ§ä¿èšŒãšäžŠè¡æ§ã®ãã©ã³ã¹ãåããŸããã³ãã¯ã·ã§ã³ããŒãªã³ã°ã¯ãªãœãŒã¹æ¯æžãªãã§ããŒã¿ããŒã¹ã¢ã¯ã»ã¹ãã¹ã±ãŒã«ããŸãã
åãã¿ãŒã³ã¯ç¹å®ã®ã¢ãŒããã¯ãã£äžã®æžå¿µã«å¯ŸåŠããŸããå ¬åŒããã¥ã¡ã³ããšæ¥çã®ãã¹ããã©ã¯ãã£ã¹ã«å°ãããçµã¿åããã¯ãè² è·äžã§æŽåæ§ãç¶æããèŠä»¶ãšãšãã«ã¯ãªãŒã³ã«é²åããå®çšçãªéçšã¡ããªã¯ã¹ã衚é¢åããå ç¢ãªããŒã¿å±€ããããããŸãã
ç§ã¯ãTypeORM ã«æ¯ããããã·ã³ãã«ãª Repository å®è£ ããå§ããèªã¿åã/æžã蟌ã¿ãã¿ãŒã³ãå€§å¹ ã«ç°ãªãå Žåã«ã®ã¿ CQRS ã远å ããã¯ãšãªãã¿ãŒã³ã«åºã¥ããŠãããã³ã°æŠç¥ãéžæãããããžã§ã¯ãéå§ãããã€ã°ã¬ãŒã·ã§ã³é§åã®ã¹ããŒã倿Žã匷å¶ããæŽåæ§èŠä»¶ã«äžèŽããåé¢ã¬ãã«ãéžæããããŒã¿ããŒã¹ãµãŒããŒãªãœãŒã¹ã«åŸã£ãŠã³ãã¯ã·ã§ã³ããŒã«ããµã€ãžã³ã°ããããšãæšå¥šããŸãã
ãããã®ãã¿ãŒã³ã¯ããã¥ã¡ã³ãåããããã¹ããããå®èšŒãããŠããŸããäœç³»çã«å®è£ ããŠãã ããã
ã¢ãŒããã¯ãã£ã®èŠèŠå
ãããã®æŠå¿µã匷åããããã«ãæ¬çªã·ã¹ãã ã§ãããã®ãã¿ãŒã³ãã©ã®ããã«æ¥ç¶ããããã瀺ããŸãïŒ
éå±€åã¢ãŒããã¯ãã£: ãã¡ã€ã³ããããŒã¿ããŒã¹ãŸã§
å±€éã®ã¯ãªãŒã³ãªåé¢ã¯ä¿å®æ§ãä¿èšŒããŸãïŒ
âââââââââââââââââââââââââââââââââââ
â Domain Layer (Business Logic) â
â - Entities with behavior â
â - Value Objects â
â - Domain Services â
ââââââââââââââ¬âââââââââââââââââââââ
â Repository Interface
ââââââââââââââŒâââââââââââââââââââââ
â Data Adapter Layer â
â - TypeORM Repositories â
â - ORM Models (.model.ts) â
â - Mapping Logic â
ââââââââââââââ¬âââââââââââââââââââââ
â TypeORM Connection
ââââââââââââââŒâââââââââââââââââââââ
â PostgreSQL Database â
â - Tables & Indexes â
â - Constraints â
â - Connection Pool â
âââââââââââââââââââââââââââââââââââ
åå±€ã«ã¯æç¢ºãªè²¬ä»»ããããäŸåé¢ä¿ã¯äžæ¹åã«æµããŸãã
CQRS ããŒã¿ãããŒ
CQRS ãå®è£ ããå Žåãã³ãã³ããšã¯ãšãªã¯åå¥ã®ãã¹ããã©ããŸãïŒ
ã³ãã³ããã¹ ïŒæžã蟌ã¿ïŒ: ãŠãŒã¶ãŒãªã¯ãšã¹ã â ã³ãã³ããã³ãã©ãŒ â æžã蟌ã¿ãªããžã㪠â ãã¹ã¿ãŒ DB â ã€ãã³ãçºè¡
ã¯ãšãªãã¹ ïŒèªã¿åãïŒ: ãŠãŒã¶ãŒãªã¯ãšã¹ã â ã¯ãšãªãã³ãã©ãŒ â èªã¿åããªããžã㪠â èªã¿åãã¬ããªã« â ã¬ã¹ãã³ã¹
ãã®åé¢ã«ãããèªã¿åããšæžãèŸŒã¿æäœã®ç¬ç«ããæé©åãå¯èœã«ãªããæžã蟌ã¿ã®æŽåæ§ãç¶æããªããèªã¿åãã¬ããªã«ãæ°Žå¹³ã«ã¹ã±ãŒã«ã§ããŸãã
åèæç®
: [1] Fowler, M. (2002). "Repository." Patterns of Enterprise Application Architecture. Retrieved from https://martinfowler.com/eaaCatalog/repository.html
: [2] TypeORM. (2024). "Working with Repository." TypeORM Documentation. Retrieved from https://typeorm.io/docs/working-with-entity-manager/working-with-repository/
: [3] TypeORM. (2024). "Repository APIs." TypeORM Documentation. Retrieved from https://typeorm.io/docs/working-with-entity-manager/repository-api/
: [4] Microsoft. (2024). "CQRS Pattern." Azure Architecture Center. Retrieved from https://learn.microsoft.com/en-us/azure/architecture/patterns/cqrs
: [5] Fowler, M. (2011). "CQRS." Martin Fowler's Blog. Retrieved from https://martinfowler.com/bliki/CQRS.html
: [6] AWS. (2024). "CQRS Pattern." AWS Prescriptive Guidance. Retrieved from https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-data-persistence/cqrs-pattern.html
: [7] Doctrine Project. (2024). "Inheritance Mapping." Doctrine ORM Documentation. Retrieved from https://www.doctrine-project.org/projects/doctrine-orm/en/3.5/reference/inheritance-mapping.html
: [8] SQLAlchemy. (2024). "Mapping Class Inheritance Hierarchies." SQLAlchemy 2.0 Documentation. Retrieved from https://docs.sqlalchemy.org/en/20/orm/inheritance.html
: [9] TypeORM. (2024). "Migrations." TypeORM Documentation. Retrieved from https://typeorm.io/docs/advanced-topics/migrations/
: [10] Gunawardena, B. (2025). "NestJS & TypeORM Migrations in 2025." JavaScript in Plain English. Retrieved from https://javascript.plainenglish.io/nestjs-typeorm-migrations-in-2025-50214275ec8d
: [11] Aviator. (2024). "ACID Transactions and Implementation in a PostgreSQL Database." Retrieved from https://www.aviator.co/blog/acid-transactions-postgresql-database/
: [12] PostgreSQL Global Development Group. (2024). "Transaction Isolation." PostgreSQL 18 Documentation. Retrieved from https://www.postgresql.org/docs/current/transaction-iso.html
: [13] ScaleGrid. (2024). "PostgreSQL Connection Pooling: Part 1 - Pros & Cons." Retrieved from https://scalegrid.io/blog/postgresql-connection-pooling-part-1-pros-and-cons/
: [14] Stack Overflow. (2020). "Improve Database Performance with Connection Pooling." Stack Overflow Blog. Retrieved from https://stackoverflow.blog/2020/10/14/improve-database-performance-with-connection-pooling/
: [15] ScaleGrid. (2024). "PostgreSQL Connection Pooling: Part 2 - PgBouncer." Retrieved from https://scalegrid.io/blog/postgresql-connection-pooling-part-2-pgbouncer/
: [16] Crunchy Data. (2024). "Postgres at Scale: Running Multiple PgBouncers." Crunchy Data Blog. Retrieved from https://www.crunchydata.com/blog/postgres-at-scale-running-multiple-pgbouncers
Originally published at kanaeru.ai





Top comments (0)