一键导入
cqrs
CQRS (Command Query Responsibility Segregation) 패턴 가이드를 실행합니다.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
CQRS (Command Query Responsibility Segregation) 패턴 가이드를 실행합니다.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Interactive requirements discovery through Socratic dialogue and systematic exploration
사용자 계획을 기존 도메인 모델에 대해 stress-test하는 인터뷰 세션. 용어를 날카롭게 다듬고, 결정이 굳어질 때마다 CONTEXT.md(도메인 어휘 사전)와 ADR을 인라인으로 갱신한다. 새 기능 요구사항 탐색은 `/brainstorm`을, 기존 도메인 모델·용어와의 정합성 점검은 이 스킬을 사용한다.
Evaluate design from a UX perspective, assessing visual hierarchy, information architecture, emotional resonance, cognitive load, and overall quality with quantitative scoring, persona-based testing, automated anti-pattern detection, and actionable feedback. Use when the user asks to review, critique, evaluate, or give feedback on a design or component.
Run technical quality checks across accessibility, performance, theming, responsive design, and anti-patterns. Generates a scored report with P0-P3 severity ratings and actionable plan. Use when the user wants an accessibility check, performance audit, or technical quality review. (Impeccable design audit — distinct from /audit which validates project rules.)
Create distinctive, production-grade frontend interfaces with high design quality. Generates creative, polished code that avoids generic AI aesthetics. Use when the user asks to build web components, pages, artifacts, posters, or applications, or when any design skill requires project context. Call with 'craft' for shape-then-build, 'teach' for design context setup, or 'extract' to pull reusable components and tokens into the design system.
Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks
| name | cqrs |
| description | CQRS (Command Query Responsibility Segregation) 패턴 가이드를 실행합니다. |
| user-invocable | true |
CQRS (Command Query Responsibility Segregation) 패턴 가이드를 실행합니다.
┌────────────────────────────────────────────────────────────┐
│ Client │
└────────────────────────┬───────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Commands │ │ Queries │
│ (Write Model) │ │ (Read Model) │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Command Bus │ │ Query Bus │
│ Validation │ │ Caching │
└────────┬────────┘ └────────┬────────┘
│ │
▼ ▼
┌─────────────────┐ Events ┌─────────────────┐
│ Write Database │───────────►│ Read Database │
│ (PostgreSQL) │ │ (Redis/Elastic) │
└─────────────────┘ └─────────────────┘
src/
├── domain/
│ ├── aggregates/
│ │ └── Order.ts
│ └── events/
│ └── OrderEvents.ts
├── application/
│ ├── commands/
│ │ ├── CreateOrderCommand.ts
│ │ └── handlers/
│ │ └── CreateOrderHandler.ts
│ ├── queries/
│ │ ├── GetOrderQuery.ts
│ │ └── handlers/
│ │ └── GetOrderHandler.ts
│ └── projections/
│ └── OrderProjection.ts
├── infrastructure/
│ ├── bus/
│ │ ├── CommandBus.ts
│ │ └── QueryBus.ts
│ ├── persistence/
│ │ ├── WriteRepository.ts
│ │ └── ReadRepository.ts
│ └── projectors/
│ └── OrderProjector.ts
└── api/
└── controllers/
└── OrderController.ts
// application/commands/CreateOrderCommand.ts
export interface Command {
readonly type: string;
}
export class CreateOrderCommand implements Command {
readonly type = 'CreateOrder';
constructor(
public readonly customerId: string,
public readonly items: Array<{
productId: string;
quantity: number;
unitPrice: number;
}>,
public readonly shippingAddress: Address
) {}
}
// application/commands/CancelOrderCommand.ts
export class CancelOrderCommand implements Command {
readonly type = 'CancelOrder';
constructor(
public readonly orderId: string,
public readonly reason: string
) {}
}
// application/commands/handlers/CreateOrderHandler.ts
export interface CommandHandler<T extends Command> {
handle(command: T): Promise<void>;
}
export class CreateOrderHandler implements CommandHandler<CreateOrderCommand> {
constructor(
private readonly orderRepository: OrderRepository,
private readonly eventPublisher: EventPublisher
) {}
async handle(command: CreateOrderCommand): Promise<void> {
// 비즈니스 규칙 검증
this.validateOrder(command);
// Aggregate 생성
const order = Order.create({
customerId: command.customerId,
items: command.items.map(item => OrderItem.create(item)),
shippingAddress: command.shippingAddress,
});
// 저장
await this.orderRepository.save(order);
// 이벤트 발행 (읽기 모델 업데이트 트리거)
const events = order.getUncommittedEvents();
for (const event of events) {
await this.eventPublisher.publish(event);
}
order.clearUncommittedEvents();
}
private validateOrder(command: CreateOrderCommand): void {
if (command.items.length === 0) {
throw new ValidationError('Order must have at least one item');
}
// 추가 검증...
}
}
// infrastructure/bus/CommandBus.ts
export interface CommandBus {
dispatch<T extends Command>(command: T): Promise<void>;
register<T extends Command>(
commandType: string,
handler: CommandHandler<T>
): void;
}
export class InMemoryCommandBus implements CommandBus {
private handlers = new Map<string, CommandHandler<any>>();
register<T extends Command>(
commandType: string,
handler: CommandHandler<T>
): void {
this.handlers.set(commandType, handler);
}
async dispatch<T extends Command>(command: T): Promise<void> {
const handler = this.handlers.get(command.type);
if (!handler) {
throw new Error(`No handler registered for command: ${command.type}`);
}
await handler.handle(command);
}
}
// 미들웨어 지원 버전
export class CommandBusWithMiddleware implements CommandBus {
private handlers = new Map<string, CommandHandler<any>>();
private middlewares: Middleware[] = [];
use(middleware: Middleware): void {
this.middlewares.push(middleware);
}
async dispatch<T extends Command>(command: T): Promise<void> {
const handler = this.handlers.get(command.type);
if (!handler) {
throw new Error(`No handler registered for command: ${command.type}`);
}
// 미들웨어 체인 실행
const chain = this.middlewares.reduceRight(
(next, middleware) => () => middleware.execute(command, next),
() => handler.handle(command)
);
await chain();
}
}
// 미들웨어 예시
export class LoggingMiddleware implements Middleware {
async execute(command: Command, next: () => Promise<void>): Promise<void> {
console.log(`Executing command: ${command.type}`);
const start = Date.now();
await next();
console.log(`Command ${command.type} executed in ${Date.now() - start}ms`);
}
}
export class ValidationMiddleware implements Middleware {
async execute(command: Command, next: () => Promise<void>): Promise<void> {
await this.validate(command);
await next();
}
}
// application/queries/GetOrderQuery.ts
export interface Query<TResult> {
readonly type: string;
}
export class GetOrderQuery implements Query<OrderView> {
readonly type = 'GetOrder';
constructor(public readonly orderId: string) {}
}
export class GetOrdersQuery implements Query<OrderView[]> {
readonly type = 'GetOrders';
constructor(
public readonly customerId?: string,
public readonly status?: string,
public readonly page: number = 1,
public readonly limit: number = 20
) {}
}
// Read Model (View)
export interface OrderView {
id: string;
customerId: string;
customerName: string;
items: Array<{
productId: string;
productName: string;
quantity: number;
unitPrice: number;
subtotal: number;
}>;
totalAmount: number;
status: string;
createdAt: Date;
updatedAt: Date;
}
// application/queries/handlers/GetOrderHandler.ts
export interface QueryHandler<T extends Query<TResult>, TResult> {
handle(query: T): Promise<TResult>;
}
export class GetOrderHandler implements QueryHandler<GetOrderQuery, OrderView> {
constructor(
private readonly readRepository: OrderReadRepository,
private readonly cache: Cache
) {}
async handle(query: GetOrderQuery): Promise<OrderView> {
// 캐시 확인
const cacheKey = `order:${query.orderId}`;
const cached = await this.cache.get<OrderView>(cacheKey);
if (cached) {
return cached;
}
// 읽기 DB에서 조회
const order = await this.readRepository.findById(query.orderId);
if (!order) {
throw new NotFoundError(`Order ${query.orderId} not found`);
}
// 캐시 저장
await this.cache.set(cacheKey, order, { ttl: 300 });
return order;
}
}
export class GetOrdersHandler implements QueryHandler<GetOrdersQuery, OrderView[]> {
constructor(private readonly readRepository: OrderReadRepository) {}
async handle(query: GetOrdersQuery): Promise<OrderView[]> {
return this.readRepository.findAll({
customerId: query.customerId,
status: query.status,
skip: (query.page - 1) * query.limit,
take: query.limit,
});
}
}
// infrastructure/bus/QueryBus.ts
export interface QueryBus {
execute<TResult>(query: Query<TResult>): Promise<TResult>;
register<T extends Query<TResult>, TResult>(
queryType: string,
handler: QueryHandler<T, TResult>
): void;
}
export class InMemoryQueryBus implements QueryBus {
private handlers = new Map<string, QueryHandler<any, any>>();
register<T extends Query<TResult>, TResult>(
queryType: string,
handler: QueryHandler<T, TResult>
): void {
this.handlers.set(queryType, handler);
}
async execute<TResult>(query: Query<TResult>): Promise<TResult> {
const handler = this.handlers.get(query.type);
if (!handler) {
throw new Error(`No handler registered for query: ${query.type}`);
}
return handler.handle(query);
}
}
// application/projections/OrderProjection.ts
export class OrderProjection {
constructor(private readonly readDb: ReadDatabase) {}
// Event Handler로 호출됨
async apply(event: DomainEvent): Promise<void> {
switch (event.type) {
case 'OrderCreated':
await this.handleOrderCreated(event as OrderCreatedEvent);
break;
case 'OrderItemAdded':
await this.handleOrderItemAdded(event as OrderItemAddedEvent);
break;
case 'OrderStatusChanged':
await this.handleOrderStatusChanged(event as OrderStatusChangedEvent);
break;
}
}
private async handleOrderCreated(event: OrderCreatedEvent): Promise<void> {
await this.readDb.orders.insert({
id: event.orderId,
customerId: event.customerId,
customerName: await this.getCustomerName(event.customerId),
items: [],
totalAmount: 0,
status: 'PENDING',
createdAt: event.occurredAt,
updatedAt: event.occurredAt,
});
}
private async handleOrderItemAdded(event: OrderItemAddedEvent): Promise<void> {
const product = await this.getProduct(event.productId);
await this.readDb.orders.update(
{ id: event.orderId },
{
$push: {
items: {
productId: event.productId,
productName: product.name,
quantity: event.quantity,
unitPrice: event.unitPrice,
subtotal: event.quantity * event.unitPrice,
},
},
$inc: { totalAmount: event.quantity * event.unitPrice },
$set: { updatedAt: event.occurredAt },
}
);
}
private async handleOrderStatusChanged(event: OrderStatusChangedEvent): Promise<void> {
await this.readDb.orders.update(
{ id: event.orderId },
{
$set: {
status: event.newStatus,
updatedAt: event.occurredAt,
},
}
);
// 캐시 무효화
await this.cache.delete(`order:${event.orderId}`);
}
}
// api/controllers/OrderController.ts
export class OrderController {
constructor(
private readonly commandBus: CommandBus,
private readonly queryBus: QueryBus
) {}
// Command (POST/PUT/DELETE)
async createOrder(req: Request, res: Response): Promise<void> {
const command = new CreateOrderCommand(
req.body.customerId,
req.body.items,
req.body.shippingAddress
);
await this.commandBus.dispatch(command);
res.status(202).json({ message: 'Order creation accepted' });
}
async cancelOrder(req: Request, res: Response): Promise<void> {
const command = new CancelOrderCommand(
req.params.orderId,
req.body.reason
);
await this.commandBus.dispatch(command);
res.status(202).json({ message: 'Order cancellation accepted' });
}
// Query (GET)
async getOrder(req: Request, res: Response): Promise<void> {
const query = new GetOrderQuery(req.params.orderId);
const order = await this.queryBus.execute(query);
res.json(order);
}
async getOrders(req: Request, res: Response): Promise<void> {
const query = new GetOrdersQuery(
req.query.customerId as string,
req.query.status as string,
Number(req.query.page) || 1,
Number(req.query.limit) || 20
);
const orders = await this.queryBus.execute(query);
res.json(orders);
}
}
// infrastructure/persistence/WriteRepository.ts
export class OrderWriteRepository {
constructor(private readonly db: PostgresClient) {}
async save(order: Order): Promise<void> {
await this.db.transaction(async (trx) => {
await trx.query(
`INSERT INTO orders (id, customer_id, status, created_at)
VALUES ($1, $2, $3, $4)
ON CONFLICT (id) DO UPDATE SET status = $3`,
[order.id, order.customerId, order.status, order.createdAt]
);
for (const item of order.items) {
await trx.query(
`INSERT INTO order_items (order_id, product_id, quantity, unit_price)
VALUES ($1, $2, $3, $4)`,
[order.id, item.productId, item.quantity, item.unitPrice]
);
}
});
}
async findById(id: string): Promise<Order | null> {
const result = await this.db.query(
`SELECT * FROM orders WHERE id = $1`,
[id]
);
if (result.rows.length === 0) return null;
return this.mapToAggregate(result.rows[0]);
}
}
// infrastructure/persistence/ReadRepository.ts
export class OrderReadRepository {
constructor(private readonly db: MongoClient) {}
async findById(id: string): Promise<OrderView | null> {
return this.db.collection('order_views').findOne({ id });
}
async findAll(filter: OrderFilter): Promise<OrderView[]> {
const query: any = {};
if (filter.customerId) query.customerId = filter.customerId;
if (filter.status) query.status = filter.status;
return this.db
.collection('order_views')
.find(query)
.skip(filter.skip)
.limit(filter.take)
.toArray();
}
}
// infrastructure/projectors/EventSynchronizer.ts
export class EventSynchronizer {
constructor(
private readonly eventConsumer: EventConsumer,
private readonly projections: Projection[]
) {}
async start(): Promise<void> {
await this.eventConsumer.subscribe(
['orders', 'customers', 'products'],
async (event: DomainEvent) => {
for (const projection of this.projections) {
try {
await projection.apply(event);
} catch (error) {
console.error(`Projection error: ${error.message}`);
// Retry 또는 DLQ
}
}
}
);
}
}
## CQRS Architecture
### Commands
| Command | Handler | Result |
|---------|---------|--------|
| CreateOrder | CreateOrderHandler | Async |
| CancelOrder | CancelOrderHandler | Async |
### Queries
| Query | Handler | Cache |
|-------|---------|-------|
| GetOrder | GetOrderHandler | 5min |
| GetOrders | GetOrdersHandler | No |
### Data Flow
Command → CommandBus → Handler → WriteDB → Event → Projection → ReadDB Query → QueryBus → Handler → Cache/ReadDB → Response
요청에 맞는 CQRS 아키텍처를 설계하세요.