rebus-org / Rebus.AzureServiceBus

:bus: Azure Service Bus transport for Rebus
https://mookid.dk/category/rebus
Other
33 stars 20 forks source link

Using AzureServiceBus with azure functions #68

Closed Tsjunne closed 2 years ago

Tsjunne commented 3 years ago

I was browsing the web for some clues on how to effectively use Rebus to host message handlers in an Azure Function. There are many opportunities for cost savings on Azure when moving to serverless functions, but I hate to loose the added value of the Rebus pipeline. I fell upon an implementation for NServiceBus: https://docs.particular.net/samples/azure-functions/ So this is clearly possible from Azure Extensibility view.

Has this been done or tried before using Rebus? Is this even remotely possible in Rebus' current architecture?

mookid8000 commented 3 years ago

Yes I think it would be possible, but there's no API that helps with configuring it at the moment.

Rebus receives messages using "workers", and then incoming messages are dispatched by calling a "pipeline invoker" – you can see the lines responsible here.

As you can probably see, the design fully supports the "here, Rebus, take this message and handle it" scenario 🙂

georgechond94 commented 2 years ago

Hi @mookid8000, any guidelines on how we can achieve this? I tried to still use the ServiceBusTrigger in my Function and then dispatch the message to the Rebus pipeline, but I had no luck with it yet.

Thanks!

mookid8000 commented 2 years ago

Can you tell me how far you got?

I know that e.g. DefaultPipelineInvoker is internal (and possibly other related classes), so unfortunately it's not just

using var scope = new RebusTransactionScope();
var transportMessage = (...);
var invoker = new DefaultPipelineInvoker(...);
var context = new IncomingStepContext(transportMessage, scope.TransactionContext);
await invoker.Invoke(context);
await scope.CompleteAsync();

buuut I don't think you would need to copy that many lines of code to emulate that in a function....

michielvoo commented 2 years ago

I have not found a way to create a Rebus pipeline and send the message through it. Classes required to build a pipeline are internal (for example the JSON serializers), and I believe this is because the Rebus' fluent configuration API hides the actual classes and only exposes extensions methods (for example UseNewtonsoftJson()).

So it seems that to support this use case would require opening up some parts of the Rebus API to allow one to easily create a fully functional pipeline without an actual bus.

I also looked at StandardConfigurer<T> in the hope I could use it to build a pipeline. But its constructor is internal, and its public static GetConfigurerFrom() method requires an OptionsConfigurer which also has an internal constructor.

michielvoo commented 2 years ago

I have implemented dispatch to Rebus message handlers via the Rebus pipeline for incoming messages using reflection to get the IPipelineInvoker and by implementing a custom TransactionContext. If IBus would expose its IPipelineInvoker as a public read-only property and if Rebus.Transactions.TransactionContext were a public (sealed) class, then it would be much easier. @mookid8000 How do you feel about those changes?

mookid8000 commented 2 years ago

Well, I can definitely see how the pipeline invoker would need to be made public for this to be really good.

Regarding the transactions context, you can easily have one by going

using var scope = new RebusTransactionScope();

var context = scope.TransactionContext; //< 👈 here it is 🙂

// do stuff here
await DispatchMessage(message, context, whatever);

// complete the transaction here
await scope.CompleteAsync();
C0DK commented 2 years ago

So is this still an open issue? :) It doesn't seem to be possible. Or am I skipping relevant parts of the discussion?