class ApplicationLayer {ctor(ISendOnlyBus bus) { }public void Do() {bus.Publish(new Message());}}
class WebApiController : ApiController {ctor(ApplicationLayer layer) {}public void Get() {layer.Do();}
}
class ServiceHandler: IHandleMessages<Foo> {ctor(ApplicationLayer layer) {}public void Handle(Foo message) {layer.Do();}}
if(headersArePresent) {bus.Publish(new ContinueConversation() { SomeProperty = headerFromIncomingMessage });}else {bus.Publish(new StartNewConversation() { });}
class ApplicationLayer {
ctor(ISendOnlyBus bus) { }
public void Do() {
bus.Publish(new Message());
}
}
and then you resolve the application layer once in the request scope of the WebApi and once in the message handling scope like this
class WebApiController : ApiController {
ctor(ApplicationLayer layer) {}
public void Get() {
layer.Do();
}
}
class ServiceHandler: IHandleMessages<Foo> {
ctor(ApplicationLayer layer) {}
public void Handle(Foo message) {
layer.Do();
}
}
We decided that we no longer support subscribe and unsubscribe operations from within message handlers as this very tricky when it comes to failing messages, retries and transactions.
- Why Subscribe and Unsubscribe methods are part of IMessageSession and not of IEndpointInstance?
// Included in WebAPI.csproj project and referencing ApplicationLayer.csproj running in a IIS
class WebApiController : ApiController {
ctor(ApplicationLayer layer) {} public void Get() { layer.Do(); }}
// Included in NServiceBusHandlers.csproj project and referencing ApplicationLayer.csproj running in NServiceBus.Host
class ServiceHandler: IHandleMessages<Foo> { ctor(ApplicationLayer layer) {} public void Handle(Foo message) { layer.Do(); }}
// Included in ApplicationLayer.csproj project
class ApplicationLayer { ctor(ISendOnlyBus bus) { } public void Do() { bus.Publish(new Message()); }}
> we have application services that are called by arbitrary code (WebApi) and as a result of a message.
Do I understand correctly, that you are calling WebApi Controller Actions from within the pipeline triggered by an incoming message? Or do you initiate web requests to a Web API from a message handler?
class MessageSessionAdapter : IMessageSession { ctor(IMessageHandlerContext messageHandlerContext) {}
Task Publish(object event) { messageHandlerContext.Publish(event); } ... //Other implementations for IMessageSession}
> but with the limitation of not being in the handlers as this will make impossible shared code between handlers and API. For instance we might want (as it is the case) to be able to offer the possibility to create a resource via API and as well to create it automatically as a result of an event ( so in a handler).
This is indeed one of the downsides of the split API. What else is involved to create the resource? If the resource can be created within a dedicated handler, both paths (API & pipeline) just have to send the associated CreateResource command which is a simple send operation, I don't think there would be a lot of pain of "two ways" to send the same message.
> For this reason the responsibility of configuring and endpoint shouldn't be in IEndpointInstance?
Configuration of the Endpoint happens in EndpointConfiguration. I'm not sure I can follow, do you have a suggestion where you would put it?
In case of NServiceBusHandlers, we cannot migrate in a easy way because:
- NSB6 pipeline doesn't register in child container the instance of IHandleMessageContext. That's the reason I suggest the solution of registering by myself in the child container using a Behavior.
- Given I can register the instance using a Behavior. In NSB6, IHandleMessageContext doesn't inherit from IMessageSession, that implies I got to add in the constructor of each class of my application layer, a IHandleMessageContext property and the if sentence (which is not very SOLID approach), that's why a suggested the adapter approach but using IMessageSession interface instead of a custom one. And then, register it in the child container created by the pipeline.
From a framework perspective we cannot assume child containers can rebind in a consistent way and quite frankly it is an anti-pattern we wouldn't like to impose on our users. We made a conscious choice:
Any state that is created by the framework per message handling pipeline invocation has to be explicitly carried into the clients that require it. For more explanations please see
http://www.planetgeek.ch/2016/04/26/avoid-threadstatic-threadlocal-and-asynclocal-float-the-state-instead/
or my presentation at NDC Oslo https://vimeo.com/172111826
We can't rely anymore on magic context carrying provided per containers with PerThread or PerRequest scope since NServiceBus's future is to remove all those assumptions to enable more messaging scenarios and higher throughput by decoupling it from DI containers.
We strongly believe it will make your code more intention revealing because you cannot "just" open up a constructor anymore and let the bus magically be injected deep into the hierarchies. Anytime you try to use the context deep down you need to either do the nasty hackery with the child containers you outlined or carry on the context over method injection into the code that needs it. The second approach will highlight on the method declaration what layer of your code is bound to the message handling pipeline (because arguably it always was contextually) which will enforce to rethink that decision.
If you carry on those ideas you might see how this design approach moves you closer to the ports and adapters architecture approach. The session and context access becomes a ports and adapters concern and your application layer is forced to return a decision matrix based on the input it has received. Based on the return value of the application layer your port can then publish a message (in the controller it will be the controller directly, in the handler it will be the handler).
class WebApiController : ApiController { ctor(ApplicationLayer layer, IMessageSession session) {} public Response Post() {
// I avoid factories for simplicity
var adapter = new MessageSessionAdapter(context);
var result = layer.Do(context);
return result.ToResponse(); }
}
class ServiceHandler: IHandleMessages<Foo> { ctor(ApplicationLayer layer) {}
public void Handle(Foo message, IMessageHandlerContext context) {
// I avoid factories for simplicity
var adapter = new MessageHandlerContextAdapter(context);
layer.Do(adapter); }}
class ApplicationLayer {
ctor(ISession session) { }
public Result Do(IPublishAndSendMessages adapter) {
var transaction = session.BeginTransaction(); // Execute domain operations, updates, inserts... ...
... adapter.Publish(new Message());
transaction.Commit(); // or rollback transaction.Dispose(); return Result.Ok(); }}
interface IPublishAndSendMessages { Task Publish(object event); Task Send(object event); ...}
class MessageSessionAdapter : IPublishAndSendMessages { ctor(IMessageSession messageSession) {}
Task Publish(object event) { messageSession.Publish(event); } Task Send(object command) { messageSession.Send(command); } ... //Other implementations for IPublishAndSendMessages}
class MessageHandlerContextAdapter : IPublishAndSendMessages {
ctor(IMessageHandlerContext messageHandlerContext) {}
Task Publish(object event) { messageHandlerContext.Publish(event); }
Task Send(object command) { messageHandlerContext.Send(command); } ... //Other implementations for IPublishAndSendMessages}