Skip to content

Commit 091f7fb

Browse files
authored
Merge pull request #4 from destyk/main
feat: Add PrismaDatabaseDriver for @nestixis/nestjs-inbox-outbox
2 parents ce60226 + fbb777d commit 091f7fb

File tree

14 files changed

+1940
-179
lines changed

14 files changed

+1940
-179
lines changed

package-lock.json

Lines changed: 1386 additions & 137 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/README.md

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
The `InboxOutboxModule` is solution designed for NestJS to tackle the challenges of dual write and reliable event delivery in distributed systems. It addresses scenarios where one module emits an integration event, and another module must receive and process this information to maintain system-wide data consistency, which is not possible with a in-memory event bus.
44

55
## Outbox Part Visualization
6-
![outbox](https://github.com/user-attachments/assets/83fdb729-70dd-47f9-9449-cd40fe7ddd97)
76

7+
![outbox](https://github.com/user-attachments/assets/83fdb729-70dd-47f9-9449-cd40fe7ddd97)
88

99
## Inbox Part Visualization
10-
![inbox](https://github.com/user-attachments/assets/fb67a80a-b963-4710-b0d7-a0c28c5fe6a7)
1110

11+
![inbox](https://github.com/user-attachments/assets/fb67a80a-b963-4710-b0d7-a0c28c5fe6a7)
1212

1313
### Problems Addressed
1414

@@ -18,102 +18,99 @@ The `InboxOutboxModule` is solution designed for NestJS to tackle the challenges
1818

1919
3. **Cross-Module Consistency**: Facilitates keeping data consistent across different modules or microservices by ensuring that all relevant parts of the system are updated based on emitted events.
2020

21-
2221
## Implementation Guide
2322

2423
### Installation
24+
2525
```
2626
npm i @nestixis/nestjs-inbox-outbox
2727
```
2828

2929
### Event Implementation
3030

3131
```typescript
32-
import { InboxOutboxEvent } from '@nestixis/inbox-outbox';
32+
import { InboxOutboxEvent } from "@nestixis/inbox-outbox";
3333

3434
export class UserApplicationAssignedEvent implements InboxOutboxEvent {
3535
public readonly name = UserApplicationAssignedEvent.name;
3636

3737
constructor(
3838
public readonly userToken: string,
3939
public readonly applicationToken: string,
40-
public readonly userApplicationToken: string,
40+
public readonly userApplicationToken: string
4141
) {}
4242
}
4343
```
4444

45-
4645
### Event Listener Implementation
4746

4847
**Decorator Usage:** To listen for an event, apply the `@Listener` decorator to a class method, referencing the specific event's name.
49-
```typescript
50-
import { Listener } from '@nestixis/inbox-outbox';
5148

52-
@Listener(UserApplicationAssignedEvent.name)
53-
```
49+
```typescript
50+
import { Listener } from '@nestixis/inbox-outbox';
51+
52+
@Listener(UserApplicationAssignedEvent.name)
53+
```
54+
5455
**Interface Implementation:** Implement the `IListener<T>` interface to define the listener's behavior for the corresponding event.
55-
```typescript
5656

57-
import { IListener } from '@nestixis/inbox-outbox';
57+
```typescript
58+
import { IListener } from "@nestixis/inbox-outbox";
5859

59-
class EmitIntegrationEventOnUserApplicationUpdateListener implements IListener<UserApplicationAssignedEvent> {
60-
// Implementation details...
61-
}
62-
```
60+
class EmitIntegrationEventOnUserApplicationUpdateListener
61+
implements IListener<UserApplicationAssignedEvent> {
62+
// Implementation details...
63+
}
64+
```
6365

6466
Or in case when you want to listen to multiple events:
6567

6668
```typescript
67-
import { Listener, IListener } from '@nestixis/inbox-outbox';
69+
import { Listener, IListener } from "@nestixis/inbox-outbox";
6870

6971
@Listener([
7072
UserApplicationAssignedEvent.name,
7173
UserApplicationAssigningEvent.name,
7274
])
7375
export class EmitIntegrationEventOnUserApplicationUpdateListener
7476
implements
75-
IListener<
76-
| UserApplicationAssignedEvent
77-
| UserApplicationAssigningEvent
78-
>
77+
IListener<UserApplicationAssignedEvent | UserApplicationAssigningEvent>
7978
{
8079
constructor(
8180
private eventEmitter: EventEmitter2,
82-
private queryBus: QueryBus,
81+
private queryBus: QueryBus
8382
) {}
8483

8584
async handle(
86-
event:
87-
| UserApplicationAssignedEvent
88-
| UserApplicationAssigningEvent
85+
event: UserApplicationAssignedEvent | UserApplicationAssigningEvent
8986
): Promise<void> {
9087
// Implementation details...
9188
}
9289
}
9390
```
91+
9492
> **Note:** You should only group events that are related to each other (By case and data) in the same listener. If you have events that are not related to each other, you should create a separate listener for each event.
9593
9694
### Event Emission
9795

98-
9996
The module uses a `TransactionalEventEmitter` for reliable event emission. This component is designed to work similarly to eventemitter2, but with added transactional capabilities.
10097

101-
10298
1. **Transactional Emission:** The `TransactionalEventEmitter` takes two arguments:
99+
103100
- The event to be emitted
104101
- An array of entities to be saved or removed in the transaction
105102

106103
2. **Immediate Delivery Attempt:** Upon emission, the system immediately tries to deliver the event to registered listeners.
107104

108105
3. **Fallback Mechanism:** If immediate delivery fails (due to network issues, service unavailability, etc.), a built-in polling mechanism ensures eventual delivery.
109106

107+
#### By doing that we are achieving the following:
110108

111-
#### By doing that we are achieving the following:
112109
- Events are only emitted if the associated database transaction succeeds.
113110
- Even if immediate delivery fails, the event will eventually be processed.
114111

112+
- **Emitting an Event:** To emit an event, use the `emit` method of the `transactionalEventEmitter`, providing the event object and associated transactional entities. _Operation has to be awaited._
115113

116-
- **Emitting an Event:** To emit an event, use the `emit` method of the `transactionalEventEmitter`, providing the event object and associated transactional entities. *Operation has to be awaited.*
117114
```typescript
118115
import { TransactionalEventEmitterOperations, transactionalEventEmitter } from '@nestixis/inbox-outbox';
119116

@@ -133,6 +130,7 @@ The module uses a `TransactionalEventEmitter` for reliable event emission. This
133130
The `TransactionalEventEmitter` provides two methods for emitting events:
134131

135132
- **emit:**
133+
136134
- Fires the event and attempts immediate delivery to listeners, but does **not** wait for listeners to finish execution.
137135
- Use this when you want to trigger event delivery and continue your logic without waiting for listeners to complete.
138136

@@ -141,32 +139,42 @@ The `TransactionalEventEmitter` provides two methods for emitting events:
141139
- Use this when you need to ensure that all listeners have processed the event before proceeding (e.g., for transactional workflows or when listener side effects are required before continuing).
142140

143141
**Example:**
142+
144143
```typescript
145144
// Wait for all listeners to finish processing the event
146145
await this.transactionalEventEmitter.emitAsync(
147-
new UserApplicationAssignedEvent(user.token, application.token, userApplication.token),
148-
[{
149-
entity: userApplication,
150-
operation: TransactionalEventEmitterOperations.persist,
151-
}]
146+
new UserApplicationAssignedEvent(
147+
user.token,
148+
application.token,
149+
userApplication.token
150+
),
151+
[
152+
{
153+
entity: userApplication,
154+
operation: TransactionalEventEmitterOperations.persist,
155+
},
156+
]
152157
);
153158
```
154159

155160
> **Note:** Use `emitAsync` if you need to wait for listeners to execute and complete before moving on. Use `emit` if you want to fire-and-forget the event delivery.
156161
157162
### Event Contract:
163+
158164
Ensure that your event classes implement the `InboxOutboxEvent` interface for consistency and clarity.
159165

160166
### Module Registration
161167

162168
#### Options for Registration
169+
163170
- **expiresAtTTL**: This is how long the event will be stored in the database and will be retried
164171
- **maxExecutionTimeTTL**: This is how long it will wait for the listener to process the event, if it takes longer than this, it will be retried
165172
- **readyToRetryAfterTTL**: This is how long it will wait before retrying the event listeners
166173
- **retryEveryMilliseconds**: This is how often it will check for events that need to be retried
167174
- **maxInboxOutboxTransportEventPerRetry**: This is how many events it will retry at a time
168175

169176
#### Registration
177+
170178
- Register the `InboxOutboxModule` within your application's bootstrap process, specifying global accessibility and event configurations.
171179
```typescript
172180
InboxOutboxModule.registerAsync({
@@ -182,65 +190,76 @@ Ensure that your event classes implement the `InboxOutboxEvent` interface for co
182190
{
183191
name: UserApplicationAssignedEvent.name,
184192
listeners: {
185-
expiresAtTTL: 1000 * 60 * 60 * 24,
186-
maxExecutionTimeTTL: 1000 * 15,
187-
readyToRetryAfterTTL: 10000,
193+
expiresAtTTL: 1000 * 60 * 60 * 24,
194+
maxExecutionTimeTTL: 1000 * 15,
195+
readyToRetryAfterTTL: 10000,
188196
},
189197
},
190198
],
191-
retryEveryMilliseconds: 30_000,
192-
maxInboxOutboxTransportEventPerRetry: 10,
199+
retryEveryMilliseconds: 30_000,
200+
maxInboxOutboxTransportEventPerRetry: 10,
193201
};
194202
},
195203
inject: [DataSource],
196204
}),
197205
```
198206

199207
### Currently supported drivers
208+
200209
- [TypeORM](https://github.com/Nestixis/nestjs-inbox-outbox/tree/main/packages/typeorm-driver)
201210
- [MikroORM](https://github.com/Nestixis/nestjs-inbox-outbox/tree/main/packages/mikroorm-driver)
202-
211+
- [Prisma](https://github.com/Nestixis/nestjs-inbox-outbox/tree/main/packages/prisma-driver)
203212

204213
## Creating a New Driver
205214

206215
To extend the InboxOutboxModule with support for additional ORMs or databases, you can create a new driver. Follow these steps to implement and integrate your custom driver:
207216

208217
### 1. Fork the Repository
218+
209219
Begin by forking the main InboxOutboxModule repository to your own GitHub account.
210220

211221
### 2. Create a New Package
222+
212223
Use Lerna to create a new package in the `packages` folder:
224+
213225
```bash
214226
lerna create @nestixis/your-orm-driver
215227
```
228+
216229
Alternatively, you can copy an existing driver package and modify it.
217230

218231
### 3. Implement the DatabaseDriver Interface
232+
219233
Develop your driver by implementing the `DatabaseDriver` interface. Pay special attention to:
234+
220235
- Transaction handling
221236
- Pessimistic locking mechanisms
222237
- Persist and flush operations
223238

224239
These aspects are crucial for maintaining data consistency and performance.
225240

226241
### 4. Implement the DatabaseDriverFactory Interface
242+
227243
Create a factory class that implements the `DatabaseDriverFactory` interface. This factory will be responsible for instantiating your custom driver.
228244

229245
### 5. Create a Persistable Model
246+
230247
Create a model that can be persisted in your target database. This model shall implement the `InboxOutboxTransportEvent` interface.
231248

232249
### 6. Develop a Proof of Concept
250+
233251
Create a demo application that utilizes your new driver. This PoC will serve as both a testing ground and an example for other developers.
234252

235253
### 7. Contribute or Publish
254+
236255
You have two options for making your driver available:
256+
237257
- Create a Pull Request to the main InboxOutboxModule repository for inclusion in the official release.
238258
- Publish your driver to npm under your own namespace.
239259

240260
### Best Practices
261+
241262
- Ensure comprehensive test coverage for your driver.
242263
- Document any database-specific considerations or configurations.
243264
- Follow the coding standards and conventions established in the existing drivers.
244265
- Consider performance implications, especially for high-throughput systems.
245-
246-

packages/prisma-driver/.eslint.rc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module.exports = {
2+
parser: '@typescript-eslint/parser',
3+
parserOptions: {
4+
project: 'tsconfig.json',
5+
tsconfigRootDir: __dirname,
6+
sourceType: 'module',
7+
},
8+
plugins: ['@typescript-eslint/eslint-plugin'],
9+
extends: [
10+
'plugin:@typescript-eslint/recommended',
11+
'plugin:prettier/recommended',
12+
],
13+
root: true,
14+
env: {
15+
node: true,
16+
jest: true,
17+
},
18+
ignorePatterns: ['.eslintrc.js'],
19+
rules: {
20+
'@typescript-eslint/ban-types': 'off',
21+
'@typescript-eslint/no-floating-promises': 'warn',
22+
'@typescript-eslint/explicit-function-return-type': 'warn',
23+
'@typescript-eslint/no-unused-vars': 'off',
24+
'@typescript-eslint/no-explicit-any': 'off',
25+
},
26+
};

packages/prisma-driver/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/dist
2+
/node_modules
3+
/prisma/memorydb.db

packages/prisma-driver/.prettierrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all",
4+
"printWidth": 200
5+
}

0 commit comments

Comments
 (0)