Skip to content

Commit 93137bd

Browse files
committed
Upgrade tactical core and align domain event handling
1 parent 393af23 commit 93137bd

29 files changed

Lines changed: 204 additions & 222 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
88

9+
### Changed
10+
11+
- Upgraded `ddd-tactical-core-boilerplate` to 2.0.0 and adopted its explicit
12+
aggregate identity, immutable value-object, and domain-event lifecycle
13+
contracts.
14+
915
## [1.0.2] - 2026-08-02
1016

1117
### Changed

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"node": ">=24 <25"
2828
},
2929
"dependencies": {
30-
"ddd-tactical-core-boilerplate": "^1.0.0",
30+
"ddd-tactical-core-boilerplate": "^2.0.0",
3131
"@fastify/autoload": "^6.3.1",
3232
"@fastify/cors": "^11.0.1",
3333
"@fastify/helmet": "^13.0.1",

backend/src/bounded-contexts/marketing/marketing/repository/user-write.repository.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ export class UserWriteRepository implements UserWriteRepoPort {
3939
[snapshot.id, snapshot.completedTodos, snapshot.email],
4040
);
4141
if (result.rowCount !== 1) throw new Error(`Marketing user ${snapshot.id} was not found`);
42-
await this.domainEventBus.publish(user.domainEvents);
43-
user.clearEvents();
42+
await this.domainEventBus.publish([...user.domainEvents]);
43+
user.clearDomainEvents();
4444
return ok();
4545
}
4646

@@ -49,8 +49,8 @@ export class UserWriteRepository implements UserWriteRepoPort {
4949
user: UserEntity,
5050
): Promise<Either<void, Application.Repo.Errors.Unexpected>> {
5151
await this.pool.query('DELETE FROM marketing_users WHERE id = $1', [user.id.toString()]);
52-
await this.domainEventBus.publish(user.domainEvents);
53-
user.clearEvents();
52+
await this.domainEventBus.publish([...user.domainEvents]);
53+
user.clearDomainEvents();
5454
return ok();
5555
}
5656

@@ -83,8 +83,8 @@ export class UserWriteRepository implements UserWriteRepoPort {
8383
SET email = EXCLUDED.email, updated_at = NOW()`,
8484
[snapshot.id, snapshot.completedTodos, snapshot.email],
8585
);
86-
await this.domainEventBus.publish(user.domainEvents);
87-
user.clearEvents();
86+
await this.domainEventBus.publish([...user.domainEvents]);
87+
user.clearDomainEvents();
8888
return ok();
8989
}
9090
}

backend/src/bounded-contexts/todo/todo/repository/todo-write.repository.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export class TodoWriteRepository implements TodoWriteRepoPort {
9797
throw new Error('Todo does not belong to the authenticated user');
9898
}
9999

100-
const pendingEvents = todo.domainEvents as Domain.DomainEvent<TodoEventPayload>[];
100+
const pendingEvents = todo.domainEvents as readonly Domain.DomainEvent<TodoEventPayload>[];
101101
if (pendingEvents.length === 0) return;
102102

103103
this.attachRequestMetadata(pendingEvents);
@@ -250,7 +250,9 @@ export class TodoWriteRepository implements TodoWriteRepoPort {
250250
return context.userId;
251251
}
252252

253-
private attachRequestMetadata(events: Domain.DomainEvent<TodoEventPayload>[]): void {
253+
private attachRequestMetadata(
254+
events: readonly Domain.DomainEvent<TodoEventPayload>[],
255+
): void {
254256
const store = asyncLocalStorage.getStore();
255257
const correlationId = store?.get('correlationId');
256258

backend/src/lib/bounded-contexts/marketing/marketing/domain/notification-template.entity.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,25 @@ type TNotificationTemplateSnapshot = {
1212
type: string;
1313
};
1414

15-
export class NotificationTemplateEntity extends Domain.Aggregate<NotificationTemplateProps> {
16-
private constructor(props: NotificationTemplateProps) {
17-
super(props, props.id);
15+
export class NotificationTemplateEntity extends Domain.Aggregate<
16+
NotificationTemplateProps,
17+
Domain.UUIDv4
18+
> {
19+
private constructor(props: NotificationTemplateProps, id: Domain.UUIDv4) {
20+
super(props, id);
1821
}
1922

2023
public static create(
2124
props: NotificationTemplateProps,
2225
): Either<NotificationTemplateEntity, never> {
23-
const notificationTemplate = new NotificationTemplateEntity(props);
26+
const id = props.id ?? Domain.UUIDv4.generate();
27+
const notificationTemplate = new NotificationTemplateEntity(
28+
{ ...props, id },
29+
id,
30+
);
2431
return ok(notificationTemplate);
2532
}
2633

27-
get id(): Domain.UUIDv4 {
28-
return this._id;
29-
}
30-
3134
get template() {
3235
return this.props.template;
3336
}
@@ -47,11 +50,12 @@ export class NotificationTemplateEntity extends Domain.Aggregate<NotificationTem
4750
public static fromPrimitives(
4851
data: TNotificationTemplateSnapshot,
4952
): Either<NotificationTemplateEntity, never> {
53+
const id = Domain.UUIDv4.fromString(data.id);
5054
const props: NotificationTemplateProps = {
51-
id: new Domain.UUIDv4(data.id) as Domain.UUIDv4,
55+
id,
5256
template: data.template, // TemplateVO.create(snapshot.template),
5357
type: data.type, //NotificationTypeVO.create(snapshot.type),
5458
};
55-
return NotificationTemplateEntity.create(props);
59+
return ok(new NotificationTemplateEntity(props, id));
5660
}
5761
}

backend/src/lib/bounded-contexts/marketing/marketing/domain/user.entity.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ type TUserEntityPrimitives = {
1616
email: string;
1717
};
1818

19-
export class UserEntity extends Domain.Aggregate<UserProps> {
20-
private constructor(props: UserProps) {
21-
super(props, props.id);
19+
export class UserEntity extends Domain.Aggregate<UserProps, Domain.UUIDv4> {
20+
private constructor(props: UserProps, id: Domain.UUIDv4) {
21+
super(props, id);
2222
}
2323

2424
public static create(props: UserProps): Either<UserEntity, never> {
25-
const user = new UserEntity(props);
25+
const id = props.id ?? Domain.UUIDv4.generate();
26+
const user = new UserEntity({ ...props, id }, id);
2627
return ok(user);
2728
}
2829

@@ -34,10 +35,6 @@ export class UserEntity extends Domain.Aggregate<UserProps> {
3435
return this.props.email;
3536
}
3637

37-
get id(): Domain.UUIDv4 {
38-
return this._id;
39-
}
40-
4138
changeEmail(
4239
email: string,
4340
): Either<void, DomainErrors.InvalidEmailDomainError> {
@@ -76,16 +73,17 @@ export class UserEntity extends Domain.Aggregate<UserProps> {
7673
}
7774

7875
public static fromPrimitives(data: TUserEntityPrimitives): UserEntity {
76+
const id = Domain.UUIDv4.fromString(data.id);
7977
const userEntityProps = {
80-
id: new Domain.UUIDv4(data.id),
78+
id,
8179
completedTodos: CompletedTodosVO.create({
8280
counter: data.completedTodos,
8381
}).value as CompletedTodosVO,
8482
email: EmailVO.create({
8583
email: data.email,
8684
}).value as EmailVO,
8785
};
88-
return new UserEntity(userEntityProps);
86+
return new UserEntity(userEntityProps, id);
8987
}
9088

9189
public toPrimitives(): TUserEntityPrimitives {
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
export const UPDATE_USER_SUCCESS_CASE = {
22
email: 'user@bitloops.com',
3-
userId: '123',
3+
userId: '10000000-0000-4000-8000-000000000003',
44
completedTodos: 1,
55
};
66

77
export const UPDATE_USER_REPO_ERROR_CASE = {
88
email: 'user2@bitloops.com',
9-
userId: '1234',
9+
userId: '10000000-0000-4000-8000-000000000004',
1010
completedTodos: 0,
1111
};

backend/src/lib/bounded-contexts/marketing/marketing/tests/__tests__/change-user-email/change-user-email.steps.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ import { ChangeUserEmailCommand } from '@src/lib/bounded-contexts/marketing/mark
77
import { ChangeUserEmailCommandHandler } from '@src/lib/bounded-contexts/marketing/marketing/application/command-handlers/change-user-email.command-handler';
88
import { MockUserWriteRepo } from './change-user-email-write-repo.mock';
99
import { mockAsyncLocalStorageGet } from '../../mocks/mockAsynLocalStorageGet.mock';
10-
import { UserEntityBuilder } from '../../builders/user-entity.builder';
1110
import { UserEntity } from '../../../domain/user.entity';
12-
import { UserPropsBuilder } from '../../builders/user-props.builder';
1311

1412
describe('Change user email feature test', () => {
1513
it('Changed user email successfully,', async () => {
@@ -29,12 +27,6 @@ describe('Change user email feature test', () => {
2927
const result = await updateUserEmailHandler.execute(updateUserEmailCommand);
3028

3129
//then
32-
const userProps = new UserPropsBuilder()
33-
.withId(userId)
34-
.withEmail(email)
35-
.withCompletedTodos(completedTodos)
36-
.build();
37-
3830
expect(mockUpdateUserWriteRepo.mockGetByIdMethod).toHaveBeenCalledWith(
3931
new Domain.UUIDv4(userId),
4032
);
@@ -44,12 +36,16 @@ describe('Change user email feature test', () => {
4436

4537
const userAggregate =
4638
mockUpdateUserWriteRepo.mockUpdateMethod.mock.calls[0][0];
47-
expect(userAggregate.props).toEqual(userProps);
39+
expect(userAggregate.toPrimitives()).toEqual({
40+
id: userId,
41+
email,
42+
completedTodos,
43+
});
4844
expect(result.value).toBe(undefined);
4945
});
5046

5147
it('Changed user email failed, repo error', async () => {
52-
const { email, userId, completedTodos } = UPDATE_USER_REPO_ERROR_CASE;
48+
const { email, userId } = UPDATE_USER_REPO_ERROR_CASE;
5349
// given
5450
const mockUpdateUserWriteRepo = new MockUserWriteRepo();
5551
mockAsyncLocalStorageGet(userId);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
export const CREATE_USER_SUCCESS_CASE = {
22
email: 'user@bitloops.com',
3-
userId: '123',
3+
userId: '10000000-0000-4000-8000-000000000003',
44
completedTodos: 0,
55
};
66

77
export const CREATE_USER_REPO_ERROR_CASE = {
88
email: 'user2@bitloops.com',
9-
userId: '1234',
9+
userId: '10000000-0000-4000-8000-000000000004',
1010
completedTodos: 0,
1111
};
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
export const INCREMENT_TODOS_SUCCESS_USER_EXISTS_CASE = {
2-
id: '123',
2+
id: '10000000-0000-4000-8000-000000000003',
33
completedTodos: 0,
44
email: 'test@gmail.com',
55
};
66

77
export const INCREMENT_TODOS_SUCCESS_USER_DOESNT_EXIST_CASE = {
8-
id: '1234',
8+
id: '10000000-0000-4000-8000-000000000004',
99
};
1010

1111
export const INCREMENT_TODOS_INVALID_COUNTER_CASE = {
12-
id: '12345',
12+
id: '10000000-0000-4000-8000-000000000005',
1313
completedTodos: -10,
1414
};
1515

1616
export const INCREMENT_TODOS_REPO_ERROR_GETBYID_CASE = {
17-
id: '123456',
17+
id: '10000000-0000-4000-8000-000000000006',
1818
completedTodos: 1,
1919
};
2020

2121
export const INCREMENT_TODOS_REPO_ERROR_SAVE_CASE = {
22-
id: '1234567',
22+
id: '10000000-0000-4000-8000-000000000007',
2323
completedTodos: 1,
2424
email: 'test@bitloops.com',
2525
};

0 commit comments

Comments
 (0)