メインコンテンツまでスキップ

DataService

概要

DataServiceはCQRSパターンのクエリ側であり、DynamoDBに保存されたデータに対する効率的な読み取り操作を提供します。クエリに最適化されたデータテーブル(読み取りモデル)からのすべての読み取り操作を処理します。

DataServiceを使用する前に、CommandServiceセクションで説明されているようにCommandModuleをセットアップする必要があります。

メソッド

async getItem(key: DetailKey): Promise<DataModel>

getItemメソッドは、指定された詳細キー/主キーを持つアイテムの属性セットを返します。一致するアイテムがない場合、getItemundefinedを返します。

例:

import { DataService, DataModel } from '@mbc-cqrs-serverless/core';
import { Injectable, NotFoundException } from '@nestjs/common';

@Injectable()
export class CatService {
constructor(private readonly dataService: DataService) {}

async getCat(pk: string, sk: string): Promise<CatDataEntity> {
const item = await this.dataService.getItem({ pk, sk });

if (!item) {
throw new NotFoundException('Cat not found');
}
return new CatDataEntity(item as CatDataEntity);
}
}

async listItemsByPk(pk: string, opts?: ListItemsOptions): Promise<DataListEntity>

listItemsByPkメソッドは、パーティションキーに一致する1つ以上のアイテムを返します。フィルタリング、ページネーション、ソートをサポートしています。

基本的な使い方

主キー(pk)でアイテムを一覧取得:

const res = await this.dataService.listItemsByPk(pk);
return new CatListEntity(res);

ソートキーフィルター付き

主キー(pk)でアイテムを一覧取得し、ソートキー(sk)にフィルター式を使用します。例えば、ソートキーがCAT#で始まるアイテムを取得し、100件に制限:

import { KEY_SEPARATOR } from '@mbc-cqrs-serverless/core';

const query = {
sk: {
skExpression: 'begins_with(sk, :typeCode)',
skAttributeValues: {
':typeCode': `CAT${KEY_SEPARATOR}`,
},
},
limit: 100,
};
const res = await this.dataService.listItemsByPk(pk, query);
return new CatDataListEntity(res);

ページネーション

startFromSklimitを使用してページネーションを実装:

async listCatsWithPagination(
tenantCode: string,
pageSize: number,
lastSk?: string
): Promise<{ items: CatDataEntity[]; lastSk?: string }> {
const pk = `${tenantCode}#CAT`;

const result = await this.dataService.listItemsByPk(pk, {
limit: pageSize,
startFromSk: lastSk,
});

return {
items: result.items.map(item => new CatDataEntity(item)),
lastSk: result.lastSk,
};
}

ソートキー演算子

以下のソートキー式がサポートされています:

演算子説明
等しいsk = :value完全一致
前方一致begins_with(sk, :prefix)プレフィックス一致
範囲sk BETWEEN :start AND :end範囲クエリ
より小さいsk < :valueより小さい比較
より大きいsk > :valueより大きい比較

範囲クエリの例:

const query = {
sk: {
skExpression: 'sk BETWEEN :start AND :end',
skAttributeValues: {
':start': 'ORDER#2024-01-01',
':end': 'ORDER#2024-12-31',
},
},
};
const res = await this.dataService.listItemsByPk(pk, query);

async publish(cmd: CommandModel): Promise<DataModel>

publishメソッドはコマンドデータをデータテーブルに発行します。これは通常、フレームワークのデータ同期ハンドラーによって内部的に呼び出されますが、カスタム同期ロジックを実装する際に直接使用することもできます。

注記

このメソッドは主にフレームワーク内部で使用されます。ほとんどの場合、データ同期を自動的に処理するCommandService.publishSync()またはCommandService.publishAsync()を使用してください。

import {
CommandModel,
DataModel,
DataService,
DataSyncHandler,
IDataSyncHandler,
} from "@mbc-cqrs-serverless/core";
import { Injectable } from "@nestjs/common";

// Custom data sync handler example (カスタムデータ同期ハンドラーの例)
@DataSyncHandler()
@Injectable()
export class CustomDataSyncHandler implements IDataSyncHandler {
constructor(private readonly dataService: DataService) {}

async up(cmd: CommandModel): Promise<DataModel> {
// Publish command to data table (コマンドをデータテーブルに発行)
const dataModel = await this.dataService.publish(cmd);

// Additional synchronization to external systems (外部システムへの追加同期)
await this.syncToExternalSystem(dataModel);

return dataModel;
}

async down(cmd: CommandModel): Promise<void> {
// Handle rollback if needed (必要に応じてロールバックを処理)
}

private async syncToExternalSystem(data: DataModel): Promise<void> {
// Custom sync logic (カスタム同期ロジック)
}
}

このメソッドは:

  • CommandModelをDataModel形式に変換
  • ソートキーからバージョンサフィックスを削除
  • 元の作成メタデータを保持(createdAt、createdBy、createdIp)
  • 更新メタデータを更新(updatedAt、updatedBy、updatedIp)
  • データをデータテーブルに保存

共通パターン

コードで検索

テナント内でユニークなコードでアイテムを検索:

async findByCode(tenantCode: string, code: string): Promise<CatDataEntity | null> {
const pk = `${tenantCode}#CAT`;
const sk = `CAT#${code}`;

const item = await this.dataService.getItem({ pk, sk });
return item ? new CatDataEntity(item) : null;
}

タイプフィルター付き一覧

タイプでフィルターされたアイテムを一覧取得:

async listByType(tenantCode: string, type: string): Promise<CatDataEntity[]> {
const pk = `${tenantCode}#CAT`;

const result = await this.dataService.listItemsByPk(pk, {
sk: {
skExpression: 'begins_with(sk, :type)',
skAttributeValues: {
':type': `${type}#`,
},
},
});

return result.items.map(item => new CatDataEntity(item));
}

エラーハンドリング

一般的なクエリエラーを適切に処理:

import {
InternalServerErrorException,
Logger,
NotFoundException,
} from '@nestjs/common';

async getItemSafely(pk: string, sk: string): Promise<CatDataEntity> {
const logger = new Logger('CatService');

try {
const item = await this.dataService.getItem({ pk, sk });

if (!item) {
throw new NotFoundException(`Item not found: ${pk}/${sk}`);
}

return new CatDataEntity(item);
} catch (error) {
if (error instanceof NotFoundException) {
throw error;
}
// Log and rethrow unexpected errors (予期しないエラーをログに記録して再スロー)
logger.error('Unexpected error querying item:', error);
throw new InternalServerErrorException('Failed to retrieve item');
}
}

型定義

DetailKey

interface DetailKey {
pk: string; // Partition key
sk: string; // Sort key
}

ListItemsOptions

ListItemsOptionsはメソッドシグネチャ内のインライン型定義であり、個別にエクスポートされるインターフェースではありません。型構造は以下の通りです:

{
sk?: {
skExpression: string;
skAttributeValues: Record<string, string>;
skAttributeNames?: Record<string, string>;
};
startFromSk?: string;
limit?: number;
order?: 'asc' | 'desc';
}

DataListEntity

DataListEntityはリストクエリ結果をラップするクラス(インターフェースではありません)です。簡単にインスタンス化するためのコンストラクタを提供します:

class DataListEntity {
items: DataEntity[]; // Array of data entities (データエンティティの配列)
lastSk?: string; // Sort key for pagination cursor (ページネーションカーソル用のソートキー)
total?: number; // Total count (if available) (利用可能な場合の合計数)

constructor(data: Partial<DataListEntity>);
}

コンストラクタは部分オブジェクトを受け取り、クエリ結果からインスタンスを作成できます:

const result = await this.dataService.listItemsByPk(pk);
const listEntity = new DataListEntity(result);

ベストプラクティス

  1. プロジェクション式を使用: データ転送を削減するために必要な属性のみを取得
  2. ページネーションを実装: メモリ問題を避けるため、大きな結果セットは常にページネーション
  3. 頻繁にアクセスするデータをキャッシュ: 静的または変更頻度の低いデータのキャッシュを検討
  4. 適切なキー設計を使用: クエリパターンを効率的にサポートするようにキーを設計
  5. 見つからない場合の処理: 使用する前に常にアイテムが存在するか確認