pardahlman / RawRabbit

A modern .NET framework for communication over RabbitMq
MIT License
747 stars 144 forks source link

RawRabbit Unit Of Work #381

Open DevOnBike opened 5 years ago

DevOnBike commented 5 years ago

Hello everybody,

I'm currently starting journey with RawRabbit. In enterprise solutions there are mostly requirement to implement Unit Of Work when executing subscriber handler code. How can I it be done with RawRabbit? Are there any extensions already in framework?

Regards, Macko

houseofcat commented 5 years ago

To better assist with questions like this, why don't you propose an architectural solution/plan you were thinking, then we can assist with answering how best to work with RawRabbit.

Unit of Work is not an object or framework - unless you are referencing one, I then apologize - but a design pattern.

reisenberger commented 5 years ago

I faced this question with another messaging framework. That messaging framework didn't offer any form of batch-publishing or transactionalisation (which is basically what I needed for a unit-of-work pattern).

I solved the problem by implementing my own interface IMessagePublisher over whatever the message-publishing method was, .PublishAsync(...) or similar. I then just fulfilled the interface with a TransactionalisedMessagePublisher : IMessagePublisher which simply stored the method calls when they were first made (a private List<Func<TMessage, Task<TResult>>, or similar). A method TransactionalisedMessagePublisher.CommitTransaction() would just fire off all those queued method calls (with suitable error handling); TransactionalisedMessagePublisher.RollbackTransaction() just forgot them. That provided enough to wrap some transactionalisation around the messaging framework, and hook into our wider unit-of-work code.

(Sorry for jumping in - I'm not involved with RawRabbit. Just to provide some concrete suggestions.)

DevOnBike commented 5 years ago

To better assist with questions like this, why don't you propose an architectural solution/plan you were thinking, then we can assist with answering how best to work with RawRabbit.

Ok, my question was generic because I don't want to suggest a solution in first place (maybe somebody can think out-of-box and find better approch).

So, with EasyNetQ which is similar to RawRabbit, UoW I have placed around 2 common communication models with queues: request-response and publish-subscriber. General approch is to place UoW logic around execution logic in subscriber using decorator: TransactionalHandler that has injected some handler and whole logic is as simple as:


public class TransactionalHandler<TRequest> {
private readonly IUoW _uow;
private readonly IHandler<TRequest> _innerHandler;

public TransactionalHandler(IHandler<TRequest> handler, IUoW uow) {
_uow = uow;
_innerHandler=handler;
}

void Handle(TRequest request){
{
_uow.Begin();
  try {
_innerHandler.Handle(request);
_uow.Commit();
}
catch (Exception) {
_uow.Rollback();
}
}

How dependencies are injected is not important, but it is important how to plumb in to RawRabbit pipline. Developer should only care about business logic in subscriber part (uow is transparent for him).

Unit of Work is not an object or framework - unless you are referencing one, I then apologize - but a design pattern.

I mean design pattern which in simplest form can be implemented as 3 methods: begin, commit & rollback - I'm not focusing on its implementation but rather how & where use it with RawRabbit.

DevOnBike commented 5 years ago

I faced this question with another messaging framework. That messaging framework didn't offer any form of batch-publishing or transactionalisation (which is basically what I needed for a unit-of-work pattern).

I solved it with EasyNetQ but I want not less features in working with RawRabbit if I would like to switch this messaging framework.

I solved the problem by implementing my own interface IMessagePublisher over whatever the message-publishing method was, .PublishAsync(...) or similar. I then just fulfilled the interface with a TransactionalisedMessagePublisher : IMessagePublisher which simply stored the method calls when they were first made (a private List<Func<TMessage, Task<TResult>>, or similar). A method TransactionalisedMessagePublisher.CommitTransaction() would just fire off all those queued method calls (with suitable error handling); TransactionalisedMessagePublisher.RollbackTransaction() just forgot them. That provided enough to wrap some transactionalisation around the messaging framework, and hook into our wider unit-of-work code.

It is good point to start thinking more about where to place UoW - I'm placing UoW rather on subscriber side in context of messaging, but on request/publish side there is a need of some kind transactionality also.

(Sorry for jumping in - I'm not involved with RawRabbit. Just to provide some concrete suggestions.) ok, no problem, I get your idea perfectly :)

reisenberger commented 5 years ago

Apologies, I failed to see 👓 the part of your question where you stated "when executing subscriber handler code". Even looking at the simplest part of the RawRabbit doco, I can't see any reason why your previous approach wouldn't transfer to RawRabbit.

I'm placing UoW rather on subscriber side in context of messaging, but on request/publish side there is a need of some kind transactionality also.

Our UoW was also on subscriber side (or while processing a RESTful call). We needed to make both database-write and message-publishing operate as a single unit-of-work - either all succeed or all fail. Hence the need to transactionalise message publishing as part of the unit-of-work already operating around handling a request or message.