Todoアプリの構築
このチュートリアルでは、MBC CQRS Serverlessを使用して完全なTodoアプリケーションを構築する方法を説明します。CQRSパターン、イベントハンドリング、段階的な機能追加を学びます。
このチュートリアルは、段階的なステップで構成されたサンプルコードに従っています。
構築するもの
以下の機能を持つ完全に機能するTodoアプリケーション:
- TodoのCRUD操作
- コマンド/クエリ分離によるCQRSパターン
- RDSへのイベント駆動データ同期
- オプション:Todoのシーケンス番号
- オプション:非同期タスク処理
前提条件
- クイックスタートチュートリアルを完了していること
- NestJSの基本的な理解
- ローカル開発用にDockerが実行中であ ること
サンプルの実行
各ステップには完全に動作するサンプルがあります。サンプルを実行するには:
# ステップディレクトリに移動
cd step-02-create # または他のステップ
# 依存関係のインストール
npm install
# ターミナル1:Dockerサービスを起動
npm run offline:docker
# ターミナル2:データベースマイグレーションを実行
npm run migrate
# ターミナル3:serverless offlineサーバーを起動
npm run offline:sls
Part 1:基本的なCQRS実装(step-02-create)
ステップ1:ヘルパー関数の作成
まず、パーティションキーとソートキーを管理するヘルパー関数を作成します(src/helpers/id.ts):
import { KEY_SEPARATOR } from '@mbc-cqrs-serverless/core'
import { ulid } from 'ulid'
export const TODO_PK_PREFIX = 'TODO'
export function generateTodoPk(tenantCode: string): string {
return `${TODO_PK_PREFIX}${KEY_SEPARATOR}${tenantCode}`
}
export function generateTodoSk(): string {
return ulid() // ULID provides time-ordered unique identifiers (ULIDは時間順の一意識別子を提供)
}
export function parsePk(pk: string): { type: string; tenantCode: string } {
if (pk.split(KEY_SEPARATOR).length !== 2) {
throw new Error('Invalid PK')
}
const [type, tenantCode] = pk.split(KEY_SEPARATOR)
return { type, tenantCode }
}
ステップ2:DTOの定義
Todo属性DTOを作成(dto/todo-attributes.dto.ts):
import { ApiProperty } from '@nestjs/swagger'
import { IsDateString, IsEnum, IsOptional, IsString } from 'class-validator'
// TodoStatus enum (will be synced with Prisma in step-03) (TodoStatusのenum、step-03でPrismaと同期)
export enum TodoStatus {
PENDING = 'PENDING',
IN_PROGRESS = 'IN_PROGRESS',
COMPLETED = 'COMPLETED',
CANCELED = 'CANCELED',
}
export class TodoAttributes {
@IsOptional()
@IsString()
description?: string
@IsOptional()
@ApiProperty({ enum: TodoStatus })
@IsEnum(TodoStatus)
status?: TodoStatus
@IsOptional()
@IsDateString()
dueDate?: string
}
入力DTOを作成(dto/create-todo.dto.ts):
import { Type } from 'class-transformer'
import { IsOptional, IsString, ValidateNested } from 'class-validator'
import { TodoAttributes } from './todo-attributes.dto'
export class CreateTodoDto {
@IsString()
name: string // The name field is required by CommandEntity (nameフィールドはCommandEntityで必須)
@Type(() => TodoAttributes)
@ValidateNested()
@IsOptional()
attributes?: TodoAttributes
constructor(partial: Partial<CreateTodoDto>) {
Object.assign(this, partial)
}
}