UNPKG

@mbc-cqrs-serverless/core

Version:
257 lines (200 loc) 9.45 kB
![MBC CQRS serverless framework](https://mbc-cqrs-serverless.mbc-net.com/img/mbc-cqrs-serverless.png) # @mbc-cqrs-serverless/core [![npm version](https://badge.fury.io/js/@mbc-cqrs-serverless%2Fcore.svg)](https://www.npmjs.com/package/@mbc-cqrs-serverless/core) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) The core package of the MBC CQRS Serverless framework, providing a complete implementation of CQRS (Command Query Responsibility Segregation) and Event Sourcing patterns for AWS serverless architectures. ![Quick Start Demo](https://mbc-cqrs-serverless.mbc-net.com/demo/quick-start.gif) ## Features - **CQRS Pattern**: Separate command (write) and query (read) operations for better scalability - **Event Sourcing**: Full audit trail with versioned commands and optimistic locking - **AWS Integration**: Built-in support for DynamoDB, Step Functions, SNS, SQS, S3, and Cognito - **Multi-tenancy**: Tenant isolation with automatic context management - **NestJS Framework**: Leverage dependency injection, decorators, and modular architecture - **TypeScript First**: Full type safety and excellent IDE support ## Installation ```bash npm install @mbc-cqrs-serverless/core ``` ## Quick Start ### 1. Configure the Module ```typescript import { Module } from '@nestjs/common'; import { CommandModule, DataService, CommandService } from '@mbc-cqrs-serverless/core'; @Module({ imports: [ CommandModule.register({ tableName: 'todo', }), ], }) export class TodoModule {} ``` ### 2. Inject Services ```typescript import { Injectable } from '@nestjs/common'; import { CommandService, DataService, generateId, getUserContext, VERSION_FIRST, IInvoke, } from '@mbc-cqrs-serverless/core'; @Injectable() export class TodoService { constructor( private readonly commandService: CommandService, private readonly dataService: DataService, ) {} async create(dto: CreateTodoDto, opts: { invokeContext: IInvoke }) { const { tenantCode } = getUserContext(opts.invokeContext); const pk = `TODO#${tenantCode}`; const sk = `TODO#${Date.now()}`; const command = { pk, sk, id: generateId(pk, sk), tenantCode, code: sk, type: 'TODO', version: VERSION_FIRST, name: dto.name, attributes: dto.attributes, }; return await this.commandService.publishAsync(command, opts); } async findOne(pk: string, sk: string) { return await this.dataService.getItem({ pk, sk }); } } ``` ## Key Concepts ### CQRS Architecture ``` ┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Client │────▶│ CommandService │────▶│ DynamoDB │ │ (Write) │ │ (publishAsync) │ │ (Command Table) │ └─────────────┘ └─────────────────┘ └────────┬────────┘ │ ▼ DynamoDB Streams ┌─────────────────┐ │ Step Functions │ │ (Data Sync) │ └────────┬────────┘ │ ┌─────────────┐ ┌─────────────────┐ ┌────────▼────────┐ │ Client │────▶│ DataService │────▶│ DynamoDB │ │ (Read) │ │ (getItem) │ │ (Data Table) │ └─────────────┘ └─────────────────┘ └─────────────────┘ ``` ### Command vs Data Tables | Aspect | Command Table | Data Table | |--------|---------------|------------| | Purpose | Event log / Audit trail | Current state | | Versioning | All versions stored | Latest version only | | Sort Key | Includes version (sk#v001) | No version suffix | | Use Case | Write operations | Read operations | ### Version Control Every command includes a version number for optimistic locking: ```typescript // VERSION_FIRST = 0 for new items const command = { pk, sk, version: VERSION_FIRST, ... }; await commandService.publishAsync(command, opts); // Updates require the current version const updateCommand = { pk, sk, version: currentVersion, ... }; await commandService.publishPartialUpdateAsync(updateCommand, opts); ``` ## API Reference ### CommandService The primary service for write operations. | Method | Description | |--------|-------------| | `publishAsync(input, options)` | Create or update an item asynchronously via Step Functions | | `publishSync(input, options)` | Create or update an item synchronously (bypasses Step Functions) | | `publishPartialUpdateAsync(input, options)` | Partial update with field merging (async) | | `publishPartialUpdateSync(input, options)` | Partial update with field merging (sync) | | `getItem(key)` | Get a specific command version | | `getLatestItem(key)` | Get the latest command version | | `duplicate(key, options)` | Duplicate an existing command | | `reSyncData()` | Re-synchronize all data to sync handlers | ### DataService The primary service for read operations. | Method | Description | |--------|-------------| | `getItem(key)` | Retrieve a single item by pk and sk | | `listItemsByPk(pk, options)` | List items by partition key with filtering and pagination | ### Helper Functions ```typescript import { generateId, // Generate unique ID from pk and sk getUserContext, // Extract user info from Lambda context addSortKeyVersion, // Add version suffix to sort key removeSortKeyVersion, // Remove version suffix from sort key getTenantCode, // Extract tenant code from partition key } from '@mbc-cqrs-serverless/core'; ``` ### Constants ```typescript import { VERSION_FIRST, // Initial version (0) VERSION_LATEST, // Marker for latest version (-1) VER_SEPARATOR, // Version separator in sort key ('#') } from '@mbc-cqrs-serverless/core'; ``` ## Data Sync Handlers Extend data synchronization to custom destinations (e.g., RDS, Elasticsearch): ```typescript import { Injectable } from '@nestjs/common'; import { IDataSyncHandler, DataSyncHandler, CommandModel, } from '@mbc-cqrs-serverless/core'; import { PrismaService } from '../prisma/prisma.service'; @DataSyncHandler({ tableName: 'todo' }) @Injectable() export class TodoDataSyncHandler implements IDataSyncHandler { type = 'rds'; constructor(private readonly prisma: PrismaService) {} async up(cmd: CommandModel): Promise<void> { await this.prisma.todo.upsert({ where: { pk_sk: { pk: cmd.pk, sk: cmd.sk } }, create: { /* ... */ }, update: { /* ... */ }, }); } async down(cmd: CommandModel): Promise<void> { // Optional: handle rollback } } ``` ## Environment Variables | Variable | Description | Default | |----------|-------------|---------| | `DYNAMODB_ENDPOINT` | DynamoDB endpoint URL | AWS default | | `DYNAMODB_REGION` | DynamoDB region | AWS default | | `SFN_ENDPOINT` | Step Functions endpoint | AWS default | | `SFN_REGION` | Step Functions region | AWS default | | `SNS_ENDPOINT` | SNS endpoint | AWS default | | `SQS_ENDPOINT` | SQS endpoint | AWS default | | `COGNITO_ENDPOINT` | Cognito endpoint | AWS default | | `COGNITO_REGION` | Cognito region | AWS default | ## Related Packages | Package | Description | |---------|-------------| | [@mbc-cqrs-serverless/cli](https://www.npmjs.com/package/@mbc-cqrs-serverless/cli) | CLI for project scaffolding | | [@mbc-cqrs-serverless/sequence](https://www.npmjs.com/package/@mbc-cqrs-serverless/sequence) | Sequence number generation | | [@mbc-cqrs-serverless/task](https://www.npmjs.com/package/@mbc-cqrs-serverless/task) | Async task processing | | [@mbc-cqrs-serverless/master](https://www.npmjs.com/package/@mbc-cqrs-serverless/master) | Master data management | | [@mbc-cqrs-serverless/tenant](https://www.npmjs.com/package/@mbc-cqrs-serverless/tenant) | Multi-tenancy support | | [@mbc-cqrs-serverless/import](https://www.npmjs.com/package/@mbc-cqrs-serverless/import) | Data import utilities | | [@mbc-cqrs-serverless/ui-setting](https://www.npmjs.com/package/@mbc-cqrs-serverless/ui-setting) | UI configuration | ## Documentation Full documentation is available at [https://mbc-cqrs-serverless.mbc-net.com/](https://mbc-cqrs-serverless.mbc-net.com/) - [Getting Started](https://mbc-cqrs-serverless.mbc-net.com/docs/introduction) - [Build a Todo App Tutorial](https://mbc-cqrs-serverless.mbc-net.com/docs/build-todo-app) - [Architecture Guide](https://mbc-cqrs-serverless.mbc-net.com/docs/architecture-overview) - [API Reference](https://mbc-cqrs-serverless.mbc-net.com/docs/command-service) ## License Copyright © 2024-2025, Murakami Business Consulting, Inc. [https://www.mbc-net.com/](https://www.mbc-net.com/) This project is under the [MIT License](../../LICENSE.txt).