@mbc-cqrs-serverless/core 1.0.16 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -140
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# @mbc-cqrs-serverless/core
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@mbc-cqrs-serverless/core)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
7
|
-
The
|
|
8
|
+
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.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **CQRS Pattern**: Separate command (write) and query (read) operations for better scalability
|
|
13
|
+
- **Event Sourcing**: Full audit trail with versioned commands and optimistic locking
|
|
14
|
+
- **AWS Integration**: Built-in support for DynamoDB, Step Functions, SNS, SQS, S3, and Cognito
|
|
15
|
+
- **Multi-tenancy**: Tenant isolation with automatic context management
|
|
16
|
+
- **NestJS Framework**: Leverage dependency injection, decorators, and modular architecture
|
|
17
|
+
- **TypeScript First**: Full type safety and excellent IDE support
|
|
15
18
|
|
|
16
19
|
## Installation
|
|
17
20
|
|
|
@@ -19,190 +22,233 @@ The Core package provides the foundational functionality for the MBC CQRS Server
|
|
|
19
22
|
npm install @mbc-cqrs-serverless/core
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
##
|
|
25
|
+
## Quick Start
|
|
23
26
|
|
|
24
|
-
###
|
|
27
|
+
### 1. Configure the Module
|
|
25
28
|
|
|
26
|
-
1. Import and configure the core module:
|
|
27
29
|
```typescript
|
|
28
|
-
import { CoreModule } from '@mbc-cqrs-serverless/core';
|
|
29
30
|
import { Module } from '@nestjs/common';
|
|
31
|
+
import { CommandModule, DataService, CommandService } from '@mbc-cqrs-serverless/core';
|
|
30
32
|
|
|
31
33
|
@Module({
|
|
32
34
|
imports: [
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
stage: 'dev',
|
|
35
|
+
CommandModule.register({
|
|
36
|
+
tableName: 'todo',
|
|
36
37
|
}),
|
|
37
38
|
],
|
|
38
39
|
})
|
|
39
|
-
export class
|
|
40
|
+
export class TodoModule {}
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
###
|
|
43
|
+
### 2. Inject Services
|
|
43
44
|
|
|
44
|
-
1. Create a command:
|
|
45
45
|
```typescript
|
|
46
|
-
import {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
import { Injectable } from '@nestjs/common';
|
|
47
|
+
import {
|
|
48
|
+
CommandService,
|
|
49
|
+
DataService,
|
|
50
|
+
generateId,
|
|
51
|
+
getUserContext,
|
|
52
|
+
VERSION_FIRST,
|
|
53
|
+
IInvoke,
|
|
54
|
+
} from '@mbc-cqrs-serverless/core';
|
|
55
|
+
|
|
56
|
+
@Injectable()
|
|
57
|
+
export class TodoService {
|
|
58
|
+
constructor(
|
|
59
|
+
private readonly commandService: CommandService,
|
|
60
|
+
private readonly dataService: DataService,
|
|
61
|
+
) {}
|
|
62
|
+
|
|
63
|
+
async create(dto: CreateTodoDto, opts: { invokeContext: IInvoke }) {
|
|
64
|
+
const { tenantCode } = getUserContext(opts.invokeContext);
|
|
65
|
+
const pk = `TODO#${tenantCode}`;
|
|
66
|
+
const sk = `TODO#${Date.now()}`;
|
|
67
|
+
|
|
68
|
+
const command = {
|
|
69
|
+
pk,
|
|
70
|
+
sk,
|
|
71
|
+
id: generateId(pk, sk),
|
|
72
|
+
tenantCode,
|
|
73
|
+
code: sk,
|
|
74
|
+
type: 'TODO',
|
|
75
|
+
version: VERSION_FIRST,
|
|
76
|
+
name: dto.name,
|
|
77
|
+
attributes: dto.attributes,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return await this.commandService.publishAsync(command, opts);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async findOne(pk: string, sk: string) {
|
|
84
|
+
return await this.dataService.getItem({ pk, sk });
|
|
57
85
|
}
|
|
58
86
|
}
|
|
59
87
|
```
|
|
60
88
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
## Key Concepts
|
|
90
|
+
|
|
91
|
+
### CQRS Architecture
|
|
64
92
|
|
|
65
|
-
@CommandHandler(CreateUserCommand)
|
|
66
|
-
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
|
|
67
|
-
async execute(command: CreateUserCommand): Promise<void> {
|
|
68
|
-
// Implementation
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
93
|
```
|
|
94
|
+
┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
95
|
+
│ Client │────▶│ CommandService │────▶│ DynamoDB │
|
|
96
|
+
│ (Write) │ │ (publishAsync) │ │ (Command Table) │
|
|
97
|
+
└─────────────┘ └─────────────────┘ └────────┬────────┘
|
|
98
|
+
│
|
|
99
|
+
▼ DynamoDB Streams
|
|
100
|
+
┌─────────────────┐
|
|
101
|
+
│ Step Functions │
|
|
102
|
+
│ (Data Sync) │
|
|
103
|
+
└────────┬────────┘
|
|
104
|
+
│
|
|
105
|
+
┌─────────────┐ ┌─────────────────┐ ┌────────▼────────┐
|
|
106
|
+
│ Client │────▶│ DataService │────▶│ DynamoDB │
|
|
107
|
+
│ (Read) │ │ (getItem) │ │ (Data Table) │
|
|
108
|
+
└─────────────┘ └─────────────────┘ └─────────────────┘
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Command vs Data Tables
|
|
112
|
+
|
|
113
|
+
| Aspect | Command Table | Data Table |
|
|
114
|
+
|--------|---------------|------------|
|
|
115
|
+
| Purpose | Event log / Audit trail | Current state |
|
|
116
|
+
| Versioning | All versions stored | Latest version only |
|
|
117
|
+
| Sort Key | Includes version (sk#v001) | No version suffix |
|
|
118
|
+
| Use Case | Write operations | Read operations |
|
|
119
|
+
|
|
120
|
+
### Version Control
|
|
72
121
|
|
|
73
|
-
|
|
122
|
+
Every command includes a version number for optimistic locking:
|
|
74
123
|
|
|
75
|
-
1. Create an event handler:
|
|
76
124
|
```typescript
|
|
77
|
-
|
|
78
|
-
|
|
125
|
+
// VERSION_FIRST = 0 for new items
|
|
126
|
+
const command = { pk, sk, version: VERSION_FIRST, ... };
|
|
127
|
+
await commandService.publishAsync(command, opts);
|
|
79
128
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// Implementation
|
|
84
|
-
}
|
|
85
|
-
}
|
|
129
|
+
// Updates require the current version
|
|
130
|
+
const updateCommand = { pk, sk, version: currentVersion, ... };
|
|
131
|
+
await commandService.publishPartialUpdateAsync(updateCommand, opts);
|
|
86
132
|
```
|
|
87
133
|
|
|
88
|
-
|
|
134
|
+
## API Reference
|
|
89
135
|
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
import { DataService, InjectDataService } from '@mbc-cqrs-serverless/core';
|
|
136
|
+
### CommandService
|
|
93
137
|
|
|
94
|
-
|
|
95
|
-
export class UserService {
|
|
96
|
-
constructor(
|
|
97
|
-
@InjectDataService() private readonly dataService: DataService
|
|
98
|
-
) {}
|
|
138
|
+
The primary service for write operations.
|
|
99
139
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
140
|
+
| Method | Description |
|
|
141
|
+
|--------|-------------|
|
|
142
|
+
| `publishAsync(input, options)` | Create or update an item asynchronously via Step Functions |
|
|
143
|
+
| `publishSync(input, options)` | Create or update an item synchronously (bypasses Step Functions) |
|
|
144
|
+
| `publishPartialUpdateAsync(input, options)` | Partial update with field merging (async) |
|
|
145
|
+
| `publishPartialUpdateSync(input, options)` | Partial update with field merging (sync) |
|
|
146
|
+
| `getItem(key)` | Get a specific command version |
|
|
147
|
+
| `getLatestItem(key)` | Get the latest command version |
|
|
148
|
+
| `duplicate(key, options)` | Duplicate an existing command |
|
|
149
|
+
| `reSyncData()` | Re-synchronize all data to sync handlers |
|
|
150
|
+
|
|
151
|
+
### DataService
|
|
152
|
+
|
|
153
|
+
The primary service for read operations.
|
|
154
|
+
|
|
155
|
+
| Method | Description |
|
|
156
|
+
|--------|-------------|
|
|
157
|
+
| `getItem(key)` | Retrieve a single item by pk and sk |
|
|
158
|
+
| `listItemsByPk(pk, options)` | List items by partition key with filtering and pagination |
|
|
108
159
|
|
|
109
|
-
###
|
|
160
|
+
### Helper Functions
|
|
110
161
|
|
|
111
|
-
1. Implement role-based access:
|
|
112
162
|
```typescript
|
|
113
|
-
import {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
async createUser(@Body() userData: CreateUserDto): Promise<void> {
|
|
121
|
-
// Implementation
|
|
122
|
-
}
|
|
123
|
-
}
|
|
163
|
+
import {
|
|
164
|
+
generateId, // Generate unique ID from pk and sk
|
|
165
|
+
getUserContext, // Extract user info from Lambda context
|
|
166
|
+
addSortKeyVersion, // Add version suffix to sort key
|
|
167
|
+
removeSortKeyVersion, // Remove version suffix from sort key
|
|
168
|
+
getTenantCode, // Extract tenant code from partition key
|
|
169
|
+
} from '@mbc-cqrs-serverless/core';
|
|
124
170
|
```
|
|
125
171
|
|
|
126
|
-
###
|
|
172
|
+
### Constants
|
|
127
173
|
|
|
128
|
-
1. Configure tenant isolation:
|
|
129
174
|
```typescript
|
|
130
|
-
import {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
async getUsersForTenant(
|
|
136
|
-
@TenantContext() tenantId: string
|
|
137
|
-
): Promise<User[]> {
|
|
138
|
-
// Implementation with automatic tenant isolation
|
|
139
|
-
}
|
|
140
|
-
}
|
|
175
|
+
import {
|
|
176
|
+
VERSION_FIRST, // Initial version (0)
|
|
177
|
+
VERSION_LATEST, // Marker for latest version (-1)
|
|
178
|
+
VER_SEPARATOR, // Version separator in sort key ('#')
|
|
179
|
+
} from '@mbc-cqrs-serverless/core';
|
|
141
180
|
```
|
|
142
181
|
|
|
143
|
-
|
|
182
|
+
## Data Sync Handlers
|
|
183
|
+
|
|
184
|
+
Extend data synchronization to custom destinations (e.g., RDS, Elasticsearch):
|
|
144
185
|
|
|
145
|
-
1. Use AWS services:
|
|
146
186
|
```typescript
|
|
147
|
-
import {
|
|
148
|
-
|
|
149
|
-
|
|
187
|
+
import { Injectable } from '@nestjs/common';
|
|
188
|
+
import {
|
|
189
|
+
IDataSyncHandler,
|
|
190
|
+
DataSyncHandler,
|
|
191
|
+
CommandModel,
|
|
150
192
|
} from '@mbc-cqrs-serverless/core';
|
|
193
|
+
import { PrismaService } from '../prisma/prisma.service';
|
|
151
194
|
|
|
195
|
+
@DataSyncHandler({ tableName: 'todo' })
|
|
152
196
|
@Injectable()
|
|
153
|
-
export class
|
|
154
|
-
|
|
155
|
-
private readonly stepFunctions: StepFunctionService,
|
|
156
|
-
private readonly notifications: NotificationService
|
|
157
|
-
) {}
|
|
197
|
+
export class TodoDataSyncHandler implements IDataSyncHandler {
|
|
198
|
+
type = 'rds';
|
|
158
199
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
200
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
201
|
+
|
|
202
|
+
async up(cmd: CommandModel): Promise<void> {
|
|
203
|
+
await this.prisma.todo.upsert({
|
|
204
|
+
where: { pk_sk: { pk: cmd.pk, sk: cmd.sk } },
|
|
205
|
+
create: { /* ... */ },
|
|
206
|
+
update: { /* ... */ },
|
|
163
207
|
});
|
|
164
208
|
}
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## Error Handling
|
|
169
|
-
|
|
170
|
-
The core package provides standardized error handling:
|
|
171
209
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
CommandError,
|
|
175
|
-
ValidationError,
|
|
176
|
-
NotFoundError
|
|
177
|
-
} from '@mbc-cqrs-serverless/core';
|
|
178
|
-
|
|
179
|
-
@CommandHandler(UpdateUserCommand)
|
|
180
|
-
export class UpdateUserHandler implements ICommandHandler<UpdateUserCommand> {
|
|
181
|
-
async execute(command: UpdateUserCommand): Promise<void> {
|
|
182
|
-
if (!command.userId) {
|
|
183
|
-
throw new ValidationError('User ID is required');
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const user = await this.userService.findById(command.userId);
|
|
187
|
-
if (!user) {
|
|
188
|
-
throw new NotFoundError(`User ${command.userId} not found`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Implementation
|
|
210
|
+
async down(cmd: CommandModel): Promise<void> {
|
|
211
|
+
// Optional: handle rollback
|
|
192
212
|
}
|
|
193
213
|
}
|
|
194
214
|
```
|
|
195
215
|
|
|
216
|
+
## Environment Variables
|
|
217
|
+
|
|
218
|
+
| Variable | Description | Default |
|
|
219
|
+
|----------|-------------|---------|
|
|
220
|
+
| `DYNAMODB_ENDPOINT` | DynamoDB endpoint URL | AWS default |
|
|
221
|
+
| `DYNAMODB_REGION` | DynamoDB region | AWS default |
|
|
222
|
+
| `SFN_ENDPOINT` | Step Functions endpoint | AWS default |
|
|
223
|
+
| `SFN_REGION` | Step Functions region | AWS default |
|
|
224
|
+
| `SNS_ENDPOINT` | SNS endpoint | AWS default |
|
|
225
|
+
| `SQS_ENDPOINT` | SQS endpoint | AWS default |
|
|
226
|
+
| `COGNITO_ENDPOINT` | Cognito endpoint | AWS default |
|
|
227
|
+
| `COGNITO_REGION` | Cognito region | AWS default |
|
|
228
|
+
|
|
229
|
+
## Related Packages
|
|
230
|
+
|
|
231
|
+
| Package | Description |
|
|
232
|
+
|---------|-------------|
|
|
233
|
+
| [@mbc-cqrs-serverless/cli](https://www.npmjs.com/package/@mbc-cqrs-serverless/cli) | CLI for project scaffolding |
|
|
234
|
+
| [@mbc-cqrs-serverless/sequence](https://www.npmjs.com/package/@mbc-cqrs-serverless/sequence) | Sequence number generation |
|
|
235
|
+
| [@mbc-cqrs-serverless/task](https://www.npmjs.com/package/@mbc-cqrs-serverless/task) | Async task processing |
|
|
236
|
+
| [@mbc-cqrs-serverless/master](https://www.npmjs.com/package/@mbc-cqrs-serverless/master) | Master data management |
|
|
237
|
+
| [@mbc-cqrs-serverless/tenant](https://www.npmjs.com/package/@mbc-cqrs-serverless/tenant) | Multi-tenancy support |
|
|
238
|
+
| [@mbc-cqrs-serverless/import](https://www.npmjs.com/package/@mbc-cqrs-serverless/import) | Data import utilities |
|
|
239
|
+
| [@mbc-cqrs-serverless/ui-setting](https://www.npmjs.com/package/@mbc-cqrs-serverless/ui-setting) | UI configuration |
|
|
240
|
+
|
|
196
241
|
## Documentation
|
|
197
242
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
-
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
- API
|
|
243
|
+
Full documentation is available at [https://mbc-cqrs-serverless.mbc-net.com/](https://mbc-cqrs-serverless.mbc-net.com/)
|
|
244
|
+
|
|
245
|
+
- [Getting Started](https://mbc-cqrs-serverless.mbc-net.com/docs/introduction)
|
|
246
|
+
- [Build a Todo App Tutorial](https://mbc-cqrs-serverless.mbc-net.com/docs/build-todo-app)
|
|
247
|
+
- [Architecture Guide](https://mbc-cqrs-serverless.mbc-net.com/docs/architecture-overview)
|
|
248
|
+
- [API Reference](https://mbc-cqrs-serverless.mbc-net.com/docs/command-service)
|
|
204
249
|
|
|
205
250
|
## License
|
|
206
251
|
|
|
207
|
-
Copyright
|
|
208
|
-
|
|
252
|
+
Copyright © 2024-2025, Murakami Business Consulting, Inc. [https://www.mbc-net.com/](https://www.mbc-net.com/)
|
|
253
|
+
|
|
254
|
+
This project is under the [MIT License](../../LICENSE.txt).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mbc-cqrs-serverless/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "CQRS and event base core",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mbc",
|
|
@@ -88,5 +88,5 @@
|
|
|
88
88
|
"serverless-step-functions-local": "^0.5.1",
|
|
89
89
|
"supertest": "^7.0.0"
|
|
90
90
|
},
|
|
91
|
-
"gitHead": "
|
|
91
|
+
"gitHead": "1870821803cfb045c9c4d6bc18d513fa2558de1c"
|
|
92
92
|
}
|