Why IPipelineContext doesn't inherit from IMessageSession

605 visningar
Hoppa till det första olästa meddelandet

Marc Campoy

oläst,
16 jan. 2017 08:50:532017-01-16
till particula...@googlegroups.com
Helpful information to include
Product name: NServiceBus
Version: 6.1.1
Stacktrace: -
Description:

Hi, we are trying to migrate from NSB5 to NSB6. We have two kind message event publishers. One from the WebAPI, and another from a Bus Message. In summary our service has two entry points. In NSB5, it was really easy for us, we defined an application layer which is responsible of publishing the event. The fact is that we inject by constructor an ISendOnlyBus. As application service point of view, it doesn't care if it is an instance of SendOnlyBus injected by the WebApi or a IBus instance by the handler, lifetime of child container. That means:
  • When the WebApi receives a request and publishes an event, message context doesn't exist, so a new conversation starts using the ISendOnlyBus instance.
  • When the services handles a message and publishes an event, message context exists, so headers are copied from the received message headers, then the conversation continues.
With the new implementation we found two issues:
  1. IMessageSender, IEndpointConfig nor IMessageHandlerContext are registered in the session/per message container (child container) -> Ok, I guess that is not a problem creating a behavior and registering the instance in the child container. Is that a correct solution?
  2. Our major problem is that we cannot replace ISendOnlyBus by IMessageSession, because IMessageHandlerContext doesn't inherit from IMessageSession. So, in case of publishing a new message as a consequence of a received message, we will lose the context (header, correlation Id,...). -> Our solution in this case is to use the behavior, which registers the Message Context in the child container, and register an adapter from IMessageHandlerContext to IMessageSession. This adapter will preserve the context of the inbound message.
We were googling in your github and in your forums and we found a couple of discussions related to that: https://github.com/Particular/NServiceBus/issues/3417 and https://github.com/Particular/NServiceBus/issues/3871.

By the way, we don't understand the current inheritance and segregation of the responsibility, for instance:
  • Why Subscribe and Unsubscribe methods are part of IMessageSession and not of IEndpointInstance?
  • Given that, why IPipelineContext doesn't inherit from IMessageSession (excluding Subscribe/Unsubscribe both have the same methods)?
  • What is the difference between IPipelineContext and IMessageSession (from a functional point of view)?

Daniel Marbach

oläst,
16 jan. 2017 09:35:302017-01-16
till particula...@googlegroups.com
Hi Marc,

Thanks for chiming in. Are you saying you have the following scenario?

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();
   }
}

and your layer Do message has something like this pseudocode?

if(headersArePresent) {
   bus.Publish(new ContinueConversation() { SomeProperty = headerFromIncomingMessage });
}
else {
   bus.Publish(new StartNewConversation() { });
}

Thanks for clarifying.

Daniel

Marc Campoy

oläst,
16 jan. 2017 11:14:102017-01-16
till Particular Software
Hi Daniel!

Thanks for you quick response!

This part is correct:
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();
   }
}

Then in NSB5, we don't need the if sentence, because the ISendBusOnly instance is already injected in ctor with the message context (if it is received by the handler) and headers are correctly replicated in the new event, due to the fact IBus inherits from ISendOnlyBus. That results in the case of WebApi having a simple ISendOnlyBus injected and in handler case we will have an IBus wih the correct context injected.

So, the pseudocode is:

 bus.Publish(event => {}); // From WebApi Controller: NServiceBus creates a new ConversationId in headers (message context is null).
                                         // From ServiceHandler: NServiceBus copies/relates all headers from the previous message context (Foo) for instance ConversationId.

In NSB6:

We are not allowed to do that because it has two different ways of publishing messages and not inheritance between them (IMessageSession and IMessageHandlerContext). For that reason we where thinking of implementing something similar as the NSB5 behavior:

class MessageSessionAdapter : IMessageSession {
ctor(IMessageHandlerContext messageHandlerContext) {}

Task Publish(object event) {
messageHandlerContext.Publish(event);
}
... //Other implementations for IMessageSession
}

Incoming behavior (only registered in the pipeline of the handlers never in webapi)
class IncomingBehavior : IBehavior<IIncomingLogicalMessageContext> {
Task Invoke(IIncomingLogicalMessageContext context, Func<Task> next) {
 
var adapterMessageContext = new MessageSessionAdapter(context);
 
// messageContainer is the child Container created by NServiceBus pipeline 
messageContainer.RegisterInstance<IMessageSession>(adapterMessageContext, InstancePerUnitOfWork);

...
return next(); 
Meddelandet har raderats

Tim

oläst,
16 jan. 2017 12:06:262017-01-16
till Particular Software
Hey Marc,

>  IMessageSender, IEndpointConfig nor IMessageHandlerContext are registered in the session/per message container (child container) -> Ok, I guess that is not a problem creating a behavior and registering the instance in the child container. Is that a correct solution?

We split the original IBus interface from NSB 5 into two interfaces as you already noticed: IMessageSession and the IPipelineContext. The reason for this is, as you already described correctly, that sending messages has different behavior depending whether it comes from within the pipeline of an incoming message (e.g. maintainer conversation and correlation id headers) or whether the outgoing message is triggered by arbitrary user code (e.g. a Web API controller).

One reason we don't register any of them in the DI controller out of the box is because we want to avoid false usage of those. So there are essentially two rules:
* Inside the pipeline (behaviors or message handlers), NEVER use IMessageSession. While it would work in happy cases, it has some severe side-effects, not only about headers but more important about transactions and more. So a behavior which registers the session inside the pipeline is a very very dangerous idea. Inside the pipeline, always use the IPipelineContext (or one of it's derived types)
* Outside the pipeline, you can only use the IMessageSession as the IPipelineContext won't be available and also shouldn't be stored in any way as it's transaction will most likely be completed and the context will be disposed.

You can however register the IMessageSession (implemented by IEndpointInstance) yourself in the DI container to make it easily available in your API controllers. You can do this by registering the instance retrieved by Endpoint.Start in the DI container yourself. However please make sure to not use it from within the pipeline!

Regarding your other questions:
  • Why Subscribe and Unsubscribe methods are part of IMessageSession and not of IEndpointInstance?
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.

  • Given that, why IPipelineContext doesn't inherit from IMessageSession (excluding Subscribe/Unsubscribe both have the same methods)?
As described above, we wanted to differentiate between those not only semantically but also with the syntax. It's likely that those interfaces my develop differently in the future regarding their API surface. A lot of this was also driven by our async/await support which required certain design changes.

  • What is the difference between IPipelineContext and IMessageSession (from a functional point of view)?
Also as described above, the PipelineContext is a context shared within the pipeline which might contain state, manages transactions and more to ensure all required state is flowed correctly in the asynchronous pipeline. Earlier versions made use of thread related language features which didn't work well with async/await.

If you need to use some kind of ApplicationLayer as described by Daniel, it requires indeed a certain logic to reuse the same code both from inside and outside the handlers. We usually recommend to keep bus operations close to the message handler code and not bury it down in application logic for this reason. Maybe you could use something like this:

class SessionAdapter
{
    public SessionAdapter(Func<MessageType, Task> sendOperation){
    {
        // save in field
    }

    public Task Send(MessageType message)
    {
        return this.sendOperation(message);
    }
}

which you then can pass down the business logic via parameter while you create it like this:
var sessionAdapter = new SessionAdapter(m => messageSession.Send(m));
var contextAdapter = new SessionAdapter(m => handlerContext.Send(m));


Does this help you understanding the difference between those interfaces and how to use them?

Cheers,
Tim

Oleguer Ortega

oläst,
16 jan. 2017 13:01:372017-01-16
till Particular Software
Hi Tim,

I understand the difference you explain between messages sent between a pipeline of incoming message and arbitrary message sent. And how you wanted to split this two different approaches with  IMessageSession and the IPipelineContext.
On the other hand I see the point of Marc, when sometimes we have application services that are called by arbitrary code (WebApi) and as a result of a message.
Is not the approach you propose of passing SessionAdapter to application service as a parameter hiding this difference as it would do as well the approach Marc explained of registering a custom implementation of IMessageSender?

Regarding this question:
  • Why Subscribe and Unsubscribe methods are part of IMessageSession and not of IEndpointInstance?
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.
 
I understand your point and completely agree. For this reason the responsibility of configuring and endpoint shouldn't be in IEndpointInstance? 
Is not that I want to unsubscribe from the handlers at all, but it looks to me that is something related to an Endpoint and not to a Message session. For instance if a service is hosted with NserviceBus.Host the subscribe/unsubscribe will be in the IConfigureThisEndpoint (NSB5) implementation, isn't it? I find misleading that subscribe is in the IMessageSession.

I see the point and the benefits of having bus operations as close as possible to handles, 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).

Thanks!!

El dilluns, 16 gener de 2017 18:06:26 UTC+1, Tim va escriure:

Tim

oläst,
17 jan. 2017 02:58:232017-01-17
till Particular Software
Hi Oleguer,

> 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?

> it would do as well the approach Marc explained of registering a custom implementation of IMessageSender?

As far as I understood Marc's idea, it would have resulted in the usage of the IMessageSession from within the pipeline which is what I wanted t avoid with my sample.

> 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?

> Is not that I want to unsubscribe from the handlers at all, but it looks to me that is something related to an Endpoint and not to a Message session

I agree with you on that. We have several ideas on developing the message session further and decoupling it from the IEndpointInstance interface (e.g. something like endpoint.CreateSession() ) where subscribe/unsubscribe would not be part of the session any longer. But we weren't able to fully define the API during the development of v6 and therefore decided to release as is and address this API later on, once we have a better idea about how it should behave.

>  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.

Cheers,
Tim

Marc Campoy

oläst,
17 jan. 2017 07:00:342017-01-17
till Particular Software
Hi all,

Thank you for participating in this discussion.

@Daniel and @Tim, I'm going to try to explain it using the same code Daniel wrote.

// 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?

Tim, handlers don't call WebAPI Controller Actions and we don't initiate web requests from a message handlers. The misunderstanding point and it is my fault is that I didn't say that the WebAPI and the handlers are running in different services and they share the Application Layer code. That means, we have two services sharing the same application layer and business logic, we did this approach because we want that WebAPI works in synchronous request/response and not in a fire and forget way.

WebAPI is deployed in a IIS server and the handlers using NServiceBus.Host. So, the configuration for each service is different.
So, for migrating WebAPI configuration is quite easy because is the same, just replacing ISendOnlyBus by IMessageSession. 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.
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.

You are right, if you want to do a fire and forget WebApi, but that approach doesn't work if you want to offer a request/response WebAPI.

> 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?

I think I understand Oleguer's point of view, I guess he is suggesting to move Subscribe and Unsubscribe methods from IMessageSession to IEndpointInstance. And using IEnpointInstance in the configuration phase, and IMessageSession for publishing and sending messages.

I hope my explanation helps!

Cheers,

Marc

Daniel Marbach

oläst,
17 jan. 2017 11:42:282017-01-17
till Particular Software
Hi Marc, hi Oleguer

Oleg is right the Subscribe and Unsubscribe method should not be on the IMessageSession interface but on the endpoint instance itself. We discussed this in our design sessions but unfortunately we only discovered this quite late in the v6 machinery. So we decided to postpone the decision since we want to redesign the subscribe and unsubscribe part at some point in the future.

Marc to your points. 

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.
Tim already explained the main reasons why we split the interface. Let me add another technical reason here and I'm going to explore the design reasons as well in later explanations.

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

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.

Unfortunately this decision means our design forces you to either write your adapters to hide the distinction we introduced (which I wouldn't recommend) or try to lean over to our side of thinking and embrace the floating state concept ;)

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).

This completely decouples your application layer from NServiceBus and enforces even for your application layer a more functional style where instead of executing side effects inside the application layer you are returning the state that determines the side effects. I'd call that a win/win ;)

Hope that helps

Daniel

Daniel Marbach

oläst,
17 jan. 2017 11:55:232017-01-17
till Particular Software
Hi Marc, hi Oleguer

I hit the post button too fast. There is even another benefit of that separation of concern that the new v6 design enforces. Since v6 is fully asynchronous when you are calling the context or message operations the whole code path using those operations needs to become fully async as well. By moving the responsibility into the port and adapter (web api or handler in your case) it is trivial to make those async (Api Controller can easily be made async and the handler is already). Your business logic can then remain sync if it doesn't do additional IO bound work or if the dependencies that it is using haven't yet moved to async/await style.

Daniel

Marc Campoy

oläst,
18 jan. 2017 08:03:122017-01-18
till particula...@googlegroups.com
Hi Daniel,

Thanks for your reply! It is really helpful. I love this kind of discussion, that helps me to make better software!

So, let's go... First, I totally agree with this quote:

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.

More, after reading https://docs.particular.net/nservicebus/containers/child-containers, I took into account that as a framework cannot depend on if a container allows child container (Spring). I really agree with you choice and giving the responsibility to the developer of deciding what to do with the context (passing by parameter or registering in his DI container). It is the same as ASP.Net MVC/WebAPI does with HTTPRequest object. We can discuss about throughput of using child containers, but it is another topic :P

I understand your point of view in this quote:
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.

But, in my opinion it depends on how you want to build the architecture. I really love child containers because they give me the control of the life cycle of the instance of an object, and as you know it helps a lot to define an isolated execution context/session (including using threads). It is what you did in previous versions of NSB5. For example, in our application we control the life cycle of NHibernate ISessions using child container in our application layer, that's why we do the same for the Message Context in NSB5.

About this paragraph:
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).

In this case, the proposed design of the application layer abstracts the execution from the incoming port. I mean, for this layer doesn't mind if it is executing an HTTP request, an WCF Request or a NServiceBus Handler, that's the point. Our implementation of the application layer returns a result based on the input received, this pattern help our teams to not programming based on Exceptions. And for example, WebAPI controllers build responses based on this result. But, we cannot give the responsibility of publishing events or sending commands to our handlers or controllers based on the result returned by application. Because transactions, we begin and commit/rollback transactions (in this case NHibernate transactions), in we cannot commit a transaction until a event has been published. And we don't want to give the responsibility of transaction management and publishing to our controllers and handler. That may imply repeating code between controllers and handlers.

Scenario when migrating to NSB6

So, this pseudocode is more or less defining how our application layer works. As you see, and I had explained previously to avoid replicating code between handlers and controllers and taking into account your article and your tips, now the Message Context is passed as parameter and using the adapter we made agnostic our application of the entry point/port. Doing in this way, we have really close to handlers/controller the sending and publishing of messages, and it preserves the transactions.

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();
   }
}

And the adapters!

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
}

So, let's go to the root of topic and the question written in the subject "Why IPipelineContext doesn't inherit from IMessageSession". We are forced to build and adapter (as you see is not a big effort) to make it feasible. I totally understand and agree in why you distinguish between IMessageHandlerContext and IMessageSession from a functional point of view, but in my opinion a Message Context is more or less the same as Message Session, the difference should come in the implementation as IBus and ISendOnlyBus did.

Reading your code, some ideas came to my head:
  1. As Oleg said, in https://github.com/Particular/NServiceBus/blob/master/src/NServiceBus.Core/IMessageSession.cs, I would move Subscribe and UnSubscribe methods to https://github.com/Particular/NServiceBus/blob/master/src/NServiceBus.Core/IEndpointInstance.cs
  2. After that, IMessageSession and https://github.com/Particular/NServiceBus/blob/master/src/NServiceBus.Core/IPipelineContext.cs are exactly the same.
  3. Removing all methods from IPipelineContext and make it inherits from IMessageSession, all methods will be inherited and message pipeline should remain it current behavior.
  4. That inheritance will cause that https://github.com/Particular/NServiceBus/blob/master/src/NServiceBus.Core/IMessageHandlerContext.cs inherits via IPipelineContext from IMessageSession, preserving the old inheritance between ISendOnlyBus and IBus, and avoiding to build adapters.
  5. From a functional perspective, it is the same. IHandleMessages interface remains as it is using IMessageHandlerContext as a parameter, in order to keep in mind to the developer that this Message has incoming context.
What do you think?

Daniel Marbach

oläst,
18 jan. 2017 15:44:552017-01-18
till Particular Software
Hi Mark,

> So, let's go to the root of topic and the question written in the subject "Why IPipelineContext doesn't inherit from IMessageSession".
> From a functional perspective, it is the same. IHandleMessages interface remains as it is using IMessageHandlerContext as a parameter, in order to keep in mind to the developer that this Message has incoming context.

Our points are exactly the opposite. Just because two things have Send and Publish methods with overloads doesn't mean they are the same thing. I think what we are doing here is Abductive reasoning which leads to the following humorous quote ;) :

> If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

A context bound object carries on state which is only known to the current pipeline execution while the message session is stateless. 

If you want to make message session quack and swim like a duck you have to duck type it together with the context. For us the risk of exposing users to side effects like loosing transactional guarantees when wrongly used is too high, that's why we moved away from the common abstraction.

We might think about in the future about exposing MessageOperations for advanced users like you to open up for scenarios like you described (proposed by Tim Bussmann). Although we need to first consider the maintainability and evolvability costs of such a potential change.

Thanks for challenging our thoughts, it is highly appreciated!

Daniel

Marc Campoy

oläst,
18 jan. 2017 17:16:062017-01-18
till Particular Software
Hi,

Thank you all for participating in this topic, I've learnt a lot!

@Tim and @Daniel, many thanks for your quick and clear explanations, for your support and for sharing your knowledge, I really appreciate it! Continue on this way, your product is awesome!

Sincerely,

Marc
Svara alla
Svara författaren
Vidarebefordra
0 nya meddelanden