Reputation: 1023
I am currently investigating using NServiceBus to solve the following problem. I just want to make sure that I am not going down the rabbit hole with this.
I have a solution based on a CQRS architecture. Essentially I have a series of commands being delivered to an endpoint via NServiceBus, I perform some processing to alter the state of the aggregate root, then fire off a series of events to notify the rest of the system of the changes.
The problem I have is that this all works great on one thread where I do not need to worry about locking down any given aggregate root while I alter the state.
We are getting to the point where one thread is not going to cut it and I need to start looking into using multiple worker threads/processes to process the messages.
There is a natural task break down based on the fact that I can process multiple aggregate roots at the same time, however I cannot process more than one message for the same aggregate root at the same time, as this will cause high contention due to the speed that the messages are being received.
I am trying to avoid the situation where I would need to lock a specific aggregate root id and instead assign an aggregate root to a queue/thread/process which will ensure that all messages from the same aggregate root are processed synchronously.
I am using a Pub/Sub model with NServiceBus to publish out the events. The problem as far as I see it is that these events would need to be published from the same endpoint, even though the processing of that message may be delegated to another. i.e. I need to trick the system into thinking that the message was published from "MyDomainQueue" when in fact the message was processed on "MyDomainQueue-Worker1".
My plan is to create a custom distributor which will allow the main endpoint to delegate the processing of messages to worker endpoints. The distributor would allocate a queue to any give aggregate root in a round robin fashion. The main endpoint would send commands to these workers, the processing would take place, and the worker would reply with a list of events. The main endpoint would then publish the events back onto the bus.
I am looking for some feedback regarding this approach. It feels like I am working a bit to hard to get this to work and I just want to check if any one else has dealt with a similar problem. Perhaps NServiceBus is the wrong tool for the job, or perhaps my approach is just wrong. Any and all feedback is welcome.
Thanks,
PS - I also have concerns around the amount of configuration which will be required for the solution described above.
Upvotes: 3
Views: 1121
Reputation: 18645
With regard to publishing events from different endpoints, I think you're getting a little too caught up in the concept of an event being published from one service. That's true only in the logical sense - one event should be fully owned by one logical service, however a logical service can be made up of many endpoints.
An event is not really published from a queue. When you think of the queue that an event is published from, what you really are talking about is the input queue where you send subscription requests for that event.
So, you can have multiple endpoints all publishing the same event, as long as the subscription requests for that event all go to the same place.
This is commonly the case, for instance, in a bulk vs. priority scenario, where you have two endpoints handling the same command (and then publishing the same event) except one is bulk with a long SLA, and the other has a much shorter SLA - maybe it's a big customer, or the command is coming from an actual human user waiting for a response. QueueA and PriorityQueueA both process the same command and publish the same event, but QueueA handles the subscriptions, so both processes "publish from" QueueA.
That said, have you tried just letting multiple threads access the aggregate roots? Even with some contention, you may find that with a small number of retries it may not be as contentious as you might think. I have some fairly contentious processes in production with NServiceBus and although I see the occasional evidence of contention as an exception in the log, with 5 retries I never have any of those progress to the error queue.
-- More recently, the second-level retries feature has been added which further decreases the chance of a message ending up in the error queue.
If there is that much contention, another strategy might be to maintain an in-memory list of aggregate roots currently being operated on, and then if a message comes in that should be "locked out" just call Bus.HandleCurrentMessageLater()
to stick that message back on the end of the queue.
Upvotes: 1