#Best approach for encapsulation of CQRS command/queries

13 messages · Page 1 of 1 (latest)

nova bough
#

Hey. I am working on an NestJS application that will be depending on Nest CQRS implementation. The app will be a complex monolith, including several modules (UsersModule, PostsModule, MessagesModule etc - just an example). I'd like to implement the onion architecture model, but I am not sure how exactly it works and how to achieve it.

Let's say we have basic command:

// UsersModule/queries
GetUserByIdQuery(id: string) => GetUserByIdQueryResult
// UsersModule/query-handlers
GetUserBydIdQueryHandler {
   process(query) => { return this.db.getUserById(query.id); } 
}```

---

I'd like to call mentioned query within PostsModule
const myResult = this.queryBus.dispatch(new GetUserByIdQuery(myPayload));

How this should be done?
1. Normally call queryBus for example inside PostsService like above (do i have to import usersmodule to ensure handler being accessible?
2. Wrap queryBus.dispatch into UsersModule->usersService and import UsersModule to PostsModule and do it as: `this.usersService.getUserById()` and `getUserById` dispatches query to event bus
3. Do one global infrastructure module (for example StoreModule) that has all typeorm entities, repositories and command/queries?
Like:
PostsModule imports StoreModule that has User, Post entities and their repositories 
```/// some service inside PostsModule that imports StoreModule
const result = await this.storeService.executeQuery(new GetUserById(someId))
fleet matrix
#

I think the 1st option is the best one.
One of the main benefits of the CQRS with NestJS is the decoupling of the modules.
You don't have to import a specific module with the handler inside the consumer module, it will be resolved somewhere else.
As you noticed we should ensure that eventually it is accessible.
My answer is: integration test!
In the test you can instantiet the app with some problematic parts replaced or mocked and check your PostsModule if still works properly (I assume that if GetUserByIdQuery doesn't have a handler it should fail)

The 2nd option seems like there is no point to use CQRS if you have to call a specific serivce directly after all.

The 3rd one is bad for many reasons.

nova bough
#

@fleet matrix Thank you! But still I am not sure will there be any pros from CQRS (decoupling of modules)

Lets take an example

  1. There is UsersModule that has GetUserByIdQuery and its handler - GetUserByIdQueryHandler
  2. We have GroupsModule, that has the point where we need to find User:
    We call:
    this.queryBus.dispatch(new GetUserByIdQuery())

Now - we have to import UsersModule into GroupsModule to ensure that the handler will be accessible in GroupsModule - still we have coupling between users and groups

I was thinking I can implement onion architecture and separate modules for each Infrastructure (data retrieving etc), Logic (business logic) and Presentation (Rest) and then I'd like to import only Infrastructure Module, not whole UsersModule

fleet matrix
#

You don't have to import Users into Groups. You just need to import Users into AppModule

nova bough
#

Lol, thats kinda weird. I was doing it some years before and queries/commands was not consumed as long as I didn't import module with handler to module adding event to the bus

#

😄

#

Maybe nest issue? I have to test it now, maybe it was fixed

fleet matrix
#

I'm sure it works properly now

nova bough
#

Ok, thanks!

#

if i can ask also - whats bad with 3rd option? i have seen that many times but im not sure whats exactly wrong with this? its about single responsibility or something else?

#

i was thinking about one module for all data handling within the app:
StoreModule that has all the typeorm entities in the system and all queries/commands for all type of entities

#

asking out of curiosity

fleet matrix
#

So every module has to load this huge module.
From the app perspective it seems like not a big difference but from tests perspective it's huge.
Another thing is if you want to move a module to a different app/microservice then you have a big problem to take your own entities with you