Domain model with a clean architecture with ports and adapters. It takes into account some tactical patterns from DDD.
If you have a large monolith that contains many bounded contexts, then the service can be divided into modules by context.
If you have a micro service architecture and you prefer to allocate contexts to different services (which is preferable), then the service can be divided into modules by aggregates.
In this example, there are more than one bounded contexts, you have a monolith in front of you. And this monolith is internally divided into modules according to bounded contexts.
Firstly, we have a limitation - this is the change of one aggregate in one transaction (strong consistency). To change multiple aggregates at the same time, you need to use eventual consistency.
We cannot write a message directly to the broker, because it may not be available. Pattern Transactional outbox.
Transactional outbox can be done using synchronous calls, the broker is not biased. But this option is more suitable for point-to-point communication.
In a good way, each bounded context in a micro-service architecture should have its own Relay. In the demonstration monolith, I decided to limit myself to one.
This is not a production ready solution. For example, Event Emitter needs to be replaced with a separate message broker that supports retry with confirmation.
npm install
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
# unit tests
$ npm run test
# arch tests
$ npm run test:arch
# test coverage
$ npm run test:cov