Sending message objects - ends up with ugly "Event_impl" enclosed type

nservicebus

(Charles Solar) #1

I noticed when using IMessageSession or the IMessageHandlerContext to send or publish an event if you publish like so:

ctx.Publish<MyEvent>(x => {
    x.Name = "Foo";
});

the message type / exchange name which shows up in rabbitmq is “MyEvent”

However if I send something like this:

var @event = _mapper.CreateInstance<MyEvent>(x => {
    x.Name = "Foo";
});
ctx.Publish(@event);

Then the message type / exchange name is “MyEvent_impl”

As far as I know this doesn’t affect anything… but maybe when constructing the OutgoingPublishContext it might make sense to do a “mapper.GetMappedTypeFor()” ?


(Tim Bussmann) #2

I’d say this is expected behavior. By using var @event = _mapper.CreateInstance<MyEvent>..., @event will be a proxy class MyEvent_impl, NServiceBus does assume this is the type you intended to use. This approach is internally used when messages are defined as interfaces which I’m not sure is the case in your scenario as I don’t know whether MyEvent is an interface or not.

  • If MyEvent is no interface, don’t use mapper.CreateInstance at all.
  • If MyEvent is an interface you should use the delegate parameter to ctx.Publish<MyEvent> to initialize the message instead.

Is there a specific reason you’re using the MessageMapper explicitly?


(Charles Solar) #3

Yes - MyEvent is an interface.

I’m using CreateInstance because users of my library are not directly publishing events but rather “queuing” the event to be published should no business exceptions happen.

Its a DDD library which I do things like

public void PostInvoice(decimal amount) {
    Apply<InvoicePosted>(x => {
        x.Amount = amount;
    });
}

inside the entity or aggregate root. The Apply method is calling CreateInstance to instantiate a new event message which is published at the end of processing the message assuming everything goes well.

When I write these events to the eventstore I use MessageMapper inside the serialize to convert the MyEvent_impl type into MyEvent

Makes sense to me that publishing out of NSB would do the same. But like I said I don’t think it effects anything I’m doing I just end up with more exchanges in rabbit than I like.

Well that and it makes subscribing to events harder since my endpoints want to subscribe to “MyEvent” not “MyEvent_impl”


(Tim Bussmann) #4

based on your code snippet, wouldn’t you be able to use the Publish<T>(IBehaviorContext context, Action<T> messageConstructor, PublishOptions options) overload so you won’t have to call the mapper manually?


(Charles Solar) #5

I can’t use publish at that point because its not known at the point the event message is created if the event will actually be sent.

something like this might happen

public void PostInvoice(decimal amount) {
    Apply<InvoicePosted>(x => {
        x.Amount = amount;
    });

    throw new BusinessException();
}

In which case no events should be published


(Tim Bussmann) #6

@charles if you’re using NServiceBus v6, NServiceBus will not dispatch messages sent/published within a message handler if the handlers don’t finish successfully. See https://docs.particular.net/nservicebus/messaging/batched-dispatch
within handlers it’s therefore safe to publish even if there is a chance of an exception alter in the handler logic.


(Charles Solar) #7

That’s interesting! learned something new today

However there’s not a lot of doc on this feature - in the case of the above code, it wouldn’t publish the event, but does the pipeline have to completely fail with an exception to not dispatch?

So basically the bad command would be run X times (for immediate + delayed retries) not publishing anything?

This makes sense, but unfortunately I count BusinessExceptions as a special type of failure which I handle in a pipeline step and remove the exception from bubbling up further. (To prevent silly retries on a command that obviously won’t ever work)

I assume that because I catch and don’t rethrow this BusinessException the event would actually get published as the message “completed” correctly?

I’m a tough customer I know … haha


(Andreas Öhlund) #8

This makes sense, but unfortunately I count BusinessExceptions as a special type of failure which I handle in a pipeline step and remove the exception from bubbling up further. (To prevent silly retries on a command that obviously won’t ever work)

Not sure if this is 100% applicable to your situation but we do have the concept of “unrecoverable exceptions” that might be useful:

https://docs.particular.net/nservicebus/recoverability/custom-recoverability-policy#implement-a-custom-policy-full-customization


(Tim Bussmann) #9

the exception thrown in a message handler needs to bubble up the pipeline. Otherwise this would consume the message and and the message won’t be retried, causing a message loss (and loss of the outgoing messages).

I’d recommend to not handle business exceptions in a special way within the pipeline. If you want a different recoverability mechanism applied to specific exceptions, that would be configurable with a custom retry policy or with the “unrecoverable exceptions” configuration option.

I assume that because I catch and don’t rethrow this BusinessException the event would actually get published as the message “completed” correctly?

in general yes, but this can depend on the place in the pipeline where you catch and swallow the exception. I’d recommend to catch business exceptions you want to ignore directly in the handler where it’s visible and obvious to a reader that the exceptions have no impact on the message processing.


(Charles Solar) #10

Ah I remember seeing this from when I converted off 5.0.

I chose not to use it because I do some other exception voodoo such as sending responses back to the originator should exceptions or business exceptions happen and ErrorContext doesn’t have extensions for sending messages. So therefore I would be adding a bunch of code just to change a “return” to a “throw” so recoverability gets triggered.
(which btw adding the ability to send replies from recoverability would be pretty neat)

Not publishing “Published” messages would be an additional bonus - but to take advantage of the feature I would have to rewrite a portion of the Entity related code in my library to call Bus.Publish when applying these types of events that are published out NSB. All just so the exchange name is not “_impl” - not sure if the benefits outweigh the additional complexity as the majority of uses of Apply<> will create an event to be saved to eventstore not NSB - so I’ll need to queue events until the unit of work finishes in any case.

in general yes, but this can depend on the place in the pipeline where you catch and swallow the exception. I’d recommend to catch business exceptions you want to ignore directly in the handler where it’s visible and obvious to a reader that the exceptions have no impact on the message processing.

A solid point - but BusinessExceptions are known pretty generally to not be message errors - just validation errors. When an entity throws a BusinessException the handler is not catching it explicitly but there would be virtually no benefit to swallowing it there because I still have to reply to the originator that the command failed validation. It would be more clear to someone reading the code but would lead to A LOT of duplicated code to

try
    invoice.Post(100.0M);
catch(BusinessException)
    ctx.Reply()

being that explicit would just be overkill since everyone knows what a BusinessException means AND I’d still have to send a reply. If I were throwing some other exception I’d be right there with ya


(Tim Bussmann) #11

I cannot really give any more solution proposals given your requirements other than continuing to recommend to not use interface messages and the message mapper directly.

It would be more clear to someone reading the code but would lead to A LOT of duplicated code to

it might be easier to encapsulate the logic in your snippet to be called from a handler rather than embedding this functionality into the NServiceBus pipeline directly?