How to log if nservicebus saga is already started - c#

I have a saga:
public class MySaga : Saga<MySagaEntity>,
IAmStartedByMessages<Message1>,
IAmStartedByMessages<Message2> {
}
In general I need to see easily from logs which of the messages starts which saga.
What I need is to log something like :
Recieved message Message1 with ... which starts a new saga
recieved message Message2 with ... for exsisting saga wiht Id=...
As alternatives i have following ways:
1. check if log file if that saga was not started
2. check if correlationid of saga is empty (so as it will be filled within handlers which start the saga)
if (Data.CorrelationId == default_value)
_log.DebugFormat("message starts saga CorrelationId={0}", message.CorrelationId)
Does anyone knew better ways for this?

There isn't currently a way in NServiceBus to get notified if a saga has been created or if a existing instance was loaded. (I've opened up a github issue for further discussion)
That said if the fact that the saga was created by a given message has a business meaning you're probably better off setting a boolean flag on your saga data to record this explicitly.
if(Data.SagaWasStartedByAOnlineCustomer)
Bus.Send(new VerifySomethingForOnlineCustomersCommand);

Related

Does publishing a message on the same ConsumeContext that received it have any benefits?

I'm asking about a situation where the consumer gets the message, processes it and then the result of that processing is another message, so something like:
class MyConsumer : IConsumer<MyMessage> {
public async Task Consume(ConsumeContext<MyMessage> context) {
// ...do the processing and then:
await context.Publish<MyResponse>(new()
{
Data = "some data"
});
}
}
So the question is - does using context.Publish have any benefits over injecting IPublishEndpoint? It would be done if the processing would require another component to be separated from the consumer - another class. Then the result of that component processing would be a message which could be published by the injected IPublishEndpoint.
In a consumer (and any of the consumers dependencies), messages published and/or sent should use:
ConsumeContext - easiest in the consumer, since it already has it as part of the method signature.
ISendEndpointProvider or IPublishEndpoint - should be injected into any dependencies of the consumer that need to produce messages. MassTransit is essentially redirecting these two interfaces to the current ConsumeContext behind the scenes.
This is also covered in the documentation.

Is it possible to add dynamic data to an MassTransit courier/routing slip custom event?

I have a MassTransit routing slip configured and working. For reference, the routing slip takes in an ID of an item in a MongoDB database and then creates a "version" of that document in a SQL database using EF Core. The activities (as commands) are:
Migrate document to SQL
Update audit info in MongoDB document
Update MongoDB document status (i.e. to published)
All of the above are write commands.
I have added a new 1st step which runs a query to make sure the MongoDB document is valid (e.g. name and description fields are completed) before running the migration. If this step fails it throws a custom exception, which in turns fires a failed event which is then picked up and managed by my saga. Below is a snippet of my activity code followed by the routing slip builder code:
Activity code
var result = await _queryDispatcher.ExecuteAsync<SelectModuleValidationResultById, ModuleValidationResult>(query).ConfigureAwait(false);
if (!result.ModuleValidationMessages.Any())
{
return context.Completed();
}
return context.Faulted(new ModuleNotValidException
{
ModuleId = messageCommand.ModuleId,
ModuleValidationMessages = result.ModuleValidationMessages
});
Routing slip builder code
builder.AddActivity(
nameof(Step1ValidateModule),
context.GetDestinationAddress(ActivityHelper.BuildQueueName<Step1ValidateModule>(ActivityQueueType.Execute)),
new SelectModuleValidationResultById(
context.Message.ModuleId,
context.Message.UserId,
context.Message.LanguageId)
);
builder.AddSubscription(
context.SourceAddress,
RoutingSlipEvents.ActivityFaulted,
RoutingSlipEventContents.All,
nameof(Step1ValidateModule),
x => x.Send<IModuleValidationFailed>(new
{
context.Message.ModuleId,
context.Message.LanguageId,
context.Message.UserId,
context.Message.DeploymentId,
}));
Whilst all of this works and the event gets picked up by my saga I would ideally like to add the ModuleValidationMessages (i.e. any failed validation messages) to the event being returned but I can't figure out how or even if that's possible (or more fundamentally if it's right thing to do).
It's worth noting that this is a last resort check and that the validation is checked by the client before even trying the migration so worse case scenario I can just leave it has "Has validation issues" but ideally I would like to include the derail in the failed response.
Good use case, and yes, it's possible to add the details you need to the built-in routing slip events. Instead of throwing an exception, you can Terminate the routing slip, and include variables - such as an array of messages, which are added to the RoutingSlipTerminated event that will be published.
This way, it isn't a fault but more of a business decision to terminate the routing slip prematurely. It's a contextual difference, which is why it allows variables to be specified (versus Faulted, which is a full-tilt exception).
You can then pull the array from the variables and use those in your saga or consumer.

How can you get the exception from a MassTransit courier routing slip activity?

I have a MassTransit routing slip with a couple of activities that works perfectly (I love MT) but I now want to add additional information to the failed events (i.e. error name and description).
At the moment I catch my custom exceptions in the activity and send back a faulted response, which kicks in all of the compensating activites as planned, but I can't seem to get the exception details in the subscriber (to add to the event I then send back to the saga).
My activity looks like this:
public async Task<ExecutionResult> Execute(ExecuteContext<ICreateLink> context)
{
var messageCommand = context.Arguments;
var command = new CreateLink(
messageCommand.LinkId,
messageCommand.GroupId);
try
{
await _commandDispatcher.ExecuteAsync(command).ConfigureAwait(false);
return context.Completed();
}
catch (Exception ex)
{
return context.Faulted(ex);
}
}
Then when building the routing slip I have:
builder.AddSubscription(
context.SourceAddress,
RoutingSlipEvents.ActivityFaulted,
RoutingSlipEventContents.All,
nameof(CreateLinkActivity),
x => x.Send<ICreateLinkFailed>(new CreateLinkFailed
{
LinkId = context.Message.LinkId,
LinkName = context.Message.Name
}));
I thought I would be able to access the exception information from the context but alas I can't seem to find it and this is the last piece of the puzzle for me.
I'm beginning to think that I'm not thinking about this right. Ultimately I want to pass the error type back to the routing slip and then to it's calling saga.
With your subscription, your event type can include properties with the same type/name as the built-in event that is published. Those properties will be added by MT to the event automatically (no need to map them in the Send call).
https://github.com/MassTransit/MassTransit/blob/develop/src/MassTransit/Courier/Contracts/RoutingSlipActivityFaulted.cs#L19
So in your case, the ExceptionInfo property could be copied into your event interface - and that data will be presented when the event is consumed.
Under the hood, MassTransit merges the JSON of the built-in event and your own event assignment into a combined JSON document.

Rebus: Should IdempotencyData be persisted along with IdempotentSagaData instance?

I'm trying to use the IdempotentSagas in Rebus with MongoDb as a storage.
I enable idempotency when configuring Rebus like this:
Configure
...
.Options( o => { o.EnableIdempotentSagas(); } )
...
.Sagas( s => { s.StoreInMongoDb( mongoDatabase ); } )
I can see in debug that (during handling the message) the property IdempotencyData in the IdempotentSagaData instance stores the handled message id.
But when the saga data gets persisted, the IdempotencyData is always stored as an empty document:
{
"_id" : NUUID("0aa63d69-f8f9-46bd-ab29-f1e46411a166"),
"Revision" : 1,
"IdempotencyData" : {},
...
}
and thus it always appears empty when the saga data is loaded from the storage to handle a message.
It seems that this neglects all the idempotency checks, and later redelivered messages will be handled as if they are completely new. But the IdempotencyData class seems to be designed in a way which prevents it from being serialized by the default MongoDb BsonSerializer (get-only properties, private backing fields).
Is it an intentional behavior? Maybe I'm missing some configuration step which would allow the idempotency data to be persisted?
Thanks in advance for your help.
Rebus (all versions prior to Rebus 5.0.0-b14) had a BSON serializer-unfriendly IdempotencyData, thus making it impossible to properly roundtrip this pretty important piece of data when using IIdempotentSagaData.
It has been fixed in Rebus.MongoDb 5.0.0-b02 and Rebus 5.0.0-b14.
Rebus' IdempotencyData now has proper constructors in place, allowing for serializers to initialize the entire state that way.
Rebus.MongoDb now registers appropriate class maps during initialization of the saga storage.

How do I hook into NServiceBus to write to a log file when a message is transferred to the error queue?

We are currently using self-hosted NServiceBus to handle queuable messages in our system. Right now there are instances where a queued message might fail on the first try and work on the automatic retries.
Right now we are logging on all failures, but we really don't care (at least for alerts) if a message failed the first time but worked on a re-try. What we do want to get alerted to is if all retries failed and a message goes into the error queue.
Is there any way native to NServiceBus to have code run when it's moving a message to the error queue?
If you are using the rest of the Service Platform (and you should!) that means that your error queue will have ServiceControl sitting on top of it, reading messages out of error and audit and persisting the details to its database so that it can serve up that information via its REST API to ServicePulse (for monitoring system health and uptime) and ServiceInsight (for exploration and debugging.)
Assuming you are using ServiceControl, it's pretty easy to have an endpoint subscribe to MessageFailed events that are published by ServiceControl. I explained how to do it in my blog post Failed Message Notification with ServiceControl.
This way, each endpoint doesn't have to be responsible for this task, and it is accomplished asynchronously by a centralized error monitoring endpoint.
It appears the correct way to do this is to create a custom implementation of IManageMessageFailures and registering the custom fault manager curing configuration time.
An example of this is:
public class CustomFaultManager : IManageMessageFailures
{
private readonly IManageMessageFailures faultManager;
static CustomFaultManager()
{
Configure.Instance.MessageForwardingInCaseOfFault();
}
public CustomFaultManager()
{
faultManager = new FaultManager();
((FaultManager)faultManager).ErrorQueue = ConfigureFaultsForwarder.ErrorQueue;
}
void IManageMessageFailures.SerializationFailedForMessage(TransportMessage message, Exception e)
{
faultManager.SerializationFailedForMessage(message, e);
}
void IManageMessageFailures.ProcessingAlwaysFailsForMessage(TransportMessage message, Exception e)
{
faultManager.ProcessingAlwaysFailsForMessage(message, e);
//Custom code goes here
}
void IManageMessageFailures.Init(Address address)
{
faultManager.Init(address);
}
}
from https://github.com/Particular/NServiceBus/issues/463

Categories