Using NServiceBus (v6), is there a way to ensure that a property is set in the SagaData object before the Saga Handler for a message is fired?
Our environment is multi-tenant so I want to ensure that the correct CustomerId is used for db access etc.. and that developers don't forget to pull this value from the incoming message/message header.
For example given this saga data ...
public interface ICustomerSagaData : IContainSagaData
{
Guid CustomerId { get; set; }
}
public class SomeProcessSagaData : ICustomerSagaData
{
// IContainSagaData and other properties removed for brevity ...
#region ICustomerSagaData properties
public virtual Guid CustomerId { get; set; }
#endregion
}
... and the following Saga ...
public class SomeProcessSagaSaga :
Saga<SomeProcessSagaData>,
IAmStartedByMessages<StartProcess>
{
public async Task Handle(StartProcess message, IMessageHandlerContext context)
{
// How do I ensure that Data.CustomerId is already set at this point?
}
// ConfigureHowToFindSaga etc ...
}
I initially tried inserting a behaviour into the pipeline e.g.
public class MyInvokeHandlerBehavior : Behavior<IInvokeHandlerContext>
{
public override async Task Invoke(IInvokeHandlerContext context, Func<Task> next)
{
// Ideally I'd like to set the CustomerId here before the
// Saga Handler is invoked but calls to ...
// context.Extensions.TryGet(out activeSagaInstance);
// return a null activeSagaInstance
await next().ConfigureAwait(false);
// This is the only point I can get the saga data object but
// as mentioned above the hander has already been invoked
ActiveSagaInstance activeSagaInstance;
if (context.Extensions.TryGet(out activeSagaInstance))
{
var instance = activeSagaInstance.Instance.Entity as ICustomerSagaData;
if (instance != null)
{
Guid customerId;
if (Guid.TryParse(context.Headers["CustomerId"), out customerId))
{
instance.CustomerId = customerId;
}
}
}
}
}
... but this only allows access to the SagaData instance after the handler has been fired.
Late answer, but you need to make sure your behaviour executes after the SagaPersistenceBehavior.
In your IConfigureThisEndpoint implementation:
public virtual void Customize(EndpointConfiguration configuration)
{
configuration.Pipeline.Register<Registration>();
}
public class Registration : RegisterStep
{
public Registration()
: base(
stepId: "AuditMutator",
behavior: typeof(AuditMutator),
description: "Sets up for auditing")
{
this.InsertAfterIfExists("InvokeSaga");
}
}
So to answer your question directly Data.CustomerId is not going to be set when you handle StartProcess messages. You will need to set that with the id coming off of the message.
public async Task Handle(StartProcess message, IMessageHandlerContext context)
{
Data.CustomerId = message.CustomerId;
}
That being said your sample above is missing a crucial piece which is the code for determining how a saga can be looked up for continuation of processing:
protected override void ConfigureHowToFindSaga(SagaPropertyMapper<SomeProcessSagaData> mapper)
{
mapper.ConfigureMapping<StartProcess>(message => message.CustomerId)
.ToSaga(sagaData => sagaData.CustomerId);
}
Each time you send a message type that is handled by a saga you need to have the ConfigureHowToFindSaga() method configured so it can look up the previously started saga to continue processing with. So in essence you are going to start a new saga for every customerid you send with a StartProcess message. You can read more about it here: https://docs.particular.net/nservicebus/sagas/
So the real question now is do you really need to be using a saga at this point? The sample only seems to be handling one type of message so do you really need to be saving the state of CustomerId? The overhead of the saga isn't necessary in your sample and I believe a regular handler would be just fine based on the example above.
Related
I'm wrestling with a situation where we currently use the IBus interface (NServiceBus v5) in domain event handlers to send commands to a backend service for processing. With the IBus, these commands could be sent regardless of what triggered the event, whether while receiving a Web API request or as part of an NServiceBus handler (common domain model). But, in NServiceBus v6, with the shift to context specific interfaces, IEndpointInstance or IMessageHandlerContext, it seems that my domain event handlers now need to become context aware. And further, it looks like the IMessageHandlerContext is only available via method injection, so I may have to sprinkle this parameter all throughout the call stack?
Is there some approach that I'm not seeing whereby I can keep my domain event handlers context unaware? Or have I followed some bad practice that's revealing itself through this code smell?
EDIT
Here's an attempt at boiling down the scenario to the most relevant pieces. There's an order in the domain model whose status may change. When the status of the order changes, we've been firing off a StatusChanged domain event through a publisher. A subscriber to this particular domain event writes out a record of the status change and also sends out an NServiceBus command to communicate this status out - the handler for this particular command will follow some further logic on whether to send out emails, SMS messages, etc., the details of which I don't think are relevant.
Order Domain Object
public class Order
{
private OrderStatusCode _statusCode;
public OrderStatusCode StatusCode
{
get { return _statusCode; }
private set { _statusCode = value; }
}
public void ChangeStatus(OrderStatusCode status)
{
Status = status;
Publish(new StatusChanged(CreateOrderSnapshot(), status));
}
protected void Publish<T>(T #event) where T : IDomainEvent
{
DomainEventPublisher.Instance.Publish(#event);
}
}
Domain Event Publisher
public class DomainEventPublisher : IDomainEventPublisher
{
private static IDomainEventPublisher _instance;
public static IDomainEventPublisher Instance
{
get { return _instance ?? (_instance = new DomainEventPublisher()); }
}
public ISubscriptionService SubscriptionService { get; set; }
public void Publish<T>(T #event) where T : IDomainEvent
{
if (SubscriptionService == null) return;
var subscriptions = SubscriptionService.GetSubscriptions<T>();
subscriptions.ToList().ForEach(x => PublishToConsumer(x, #event).GetAwaiter().GetResult());
}
private static async Task PublishToConsumer<T>(IEventSubscriber<T> x, T eventMessage) where T : IDomainEvent
{
await x.HandleEvent(eventMessage);
}
}
Status Changed Domain Event Handler
public class StatusChangedHandler : IEventSubscriber<StatusChanged>
{
private readonly IBus _bus;
private readonly IOrdersRepository _ordersRepository;
public StatusChangedHandler(IBus bus, IOrdersRepository ordersRepository)
{
_bus = bus;
_ordersRepository = ordersRepository;
}
public async Task HandleEvent(StatusChanged #event)
{
var statusTrailEntry = new OrderStatusTrailEntry(#event.OrderSnapshot, #event.Status);
var txOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted };
using (
var scope = new TransactionScope(TransactionScopeOption.Required, txOptions))
{
await _ordersRepository.SaveStatusTrail(statusTrailEntry);
if (communicateStatus)
{
_bus.Send(new SendCommunicationCommand(#event.OrderSnapshot, #event.Status));
}
scope.Complete();
}
}
}
The things is, up until now none of the sample code above has needed to know whether the status changed as a result of a request coming in through a Web API request or as a result of a status being changed within the context of an NServiceBus message handler (within a windows service) - the IBus interface is not context specific. But with the differentiation between IEndpointInstance and IMessageHandlerContext in NServiceBus v6, I don't feel that I have the same flexibility.
If I understand correctly, I'm able to register the IEndpointInstance with my container and inject into the EventSubscriber, so I'd be covered in the case of a Web API call, but I'd also need to add an IMessageHandlerContext as a parameter to optionally be passed down through the call stack from ChangeStatus to the Publisher and finally to the Domain Event Subscriber if the status happens to be changed within the context of a message handler. Really doesn't feel right to be adding this parameter all throughout the call stack.
I have implemented a my connector using nservice bus saga. Below is the code
public class ClientSaga : Saga<ClientSagaState>,
IAmStartedByMessages<ClientChangeMessage>,
IAmStartedByMessages<ClientContactChangeMessage>,
IAmStartedByMessages<ClientPictureChangeMessage>,
IHandleTimeout<ClientSagaState>
{
[SetterProperty]
public IClientContactChangeDb ClientContactChangeDb{get;set;}
[SetterProperty]
public IBusRefTranslator BusRefTranslator{get;set;}
public void Handle(ClientContactChangeMessage message)
{
var state=this.Data;
//Some handling logic
//Check if client is not in database then store the state
state.ClientContactChange=message;
state.ClientRef =message.ClientRef;
//if client is in the data base then
MarkAsComplete();
}
public void Handle(ClientChangeMessage message)
{
var state=this.data;
//Update or create the client depending on the situation
//check for dependencies
if(state.ClientContactChange !=null)
{
//Handle the contact change
}
else
{
state.ClientChangeMessage=message;
state.ClientRef=message.ClientRef;
}
}
public void Handle(ClientPictureChangeMessage message)
{
var state=this.Data;
//If the client is there then update the picture else store in saga
state.ClientPictureChangeMessage =message;
state.ClientRef=message.ClientRef;
}
}
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ClientContactChangeMessage>(s => s.ClientRef, m => m.ClientRef);
ConfigureMapping<ClientPictureChangeMessage>(s => s.ClientRef, m => m.ClientRef);
ConfigureMapping<ClientChangeMessage>(s => s.ClienttnRef, m => m.Id);
}
}
public class ClientSagaState: IContainSagaData
{
//i dont need these three fields
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
// the fields which i needed
public Guid ClientRef {gee; set;}
public ClientChangeMessage ClientChange {get;set;}
public ClientContactChange ClientContactChange {get;set;}
public ClientPictureChangeMessage ClientPictureChangeMessage {get;set;}
}
Now in my connector a client cannot be created w/o client contact change message being present.
Case when saga fails:
When i send the the client picture message first it creates a new
saga and stores it.
Then i send a client change message it creates another saga and
stores it i.e does not find the saga created by the client picture
message
Then i send the client contact change message it somehow finds the
saga created by client picture change but now cannot find the staff.
I can't make out why this is happening.
Case when saga succeeds:
When i send the client change message first it creates the saga.
Then i send the client contact change message it finds the saga
and executes fine.
Can anyone please explain why this behaviour is happening.
Please let me know if more information is needed.
Thanks
UPDATE
On checking my code again, i found the cause of this . My ClientChangeMessage was also inheriting from IContainSaga data(something which i was trying out but had forgotten to remove). After removing the inheritance link everything was working fine. (Head hanging in shame)
In all your handlers, you need to set the ClientRef on the Saga Data.
So, you would have:
public void Handle(ClientContactChangeMessage message)
{
Data.ClientRef = message.ClientRef
...
}
As any of these messages can start the saga, you'll need to set this value in your saga state. When other messages come in, then it will be co-rrelated by this id as there is already an instance of the saga with this Id.
To refer to your saga state variables, use Data. intead of this.
I have 2 different sagas (I mean saga types) that handle the same message.
public class AttachMessageToBugSaga : TpSaga<AttachMessageToBugSagaData>, IAmStartedByMessages<MessageIsNotAttached>, IHandleMessages<MessageAttachedToGeneralMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<MessageAttachedToGeneralMessage>(
saga => saga.Id,
message => message.SagaId
);
}
public void Handle(MessageIsNotAttachedToBug message)
{
Send(new AttachMessageToGeneralCommand { MessageId = 66, GeneralId = 13 });
}
public void Handle(MessageAttachedToGeneralMessage message)
{
//do some stuf fhere
}
}
public class AttachMessageToBugSagaData : IContainSagaData
{
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
}
public class AttachMessageToRequestSaga : TpSaga<AttachMessageToRequestSagaData>, IAmStartedByMessages<MessageIsNotAttachedToRequest>, IHandleMessages<MessageAttachedToGeneralMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<MessageAttachedToGeneralMessage>(
saga => saga.Id,
message => message.SagaId
);
}
public void Handle(MessageIsNotAttachedMessageToRequest message)
{
//do some stuff here
}
public void Handle(MessageAttachedToGeneralMessage message)
{
//do some stuff here
}
}
public class AttachMessageToRequestSagaData : IContainSagaData
{
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
}
When I run the sample I get an exception :
System.InvalidCastException: Unable to cast object of type 'MyCustomPlugin.AttachMessageToGeneralSagaData' to type 'MyCustomPlugin.AttachMessageToRequestSagaData'.
I understand why it happens, but I still need some workaround. I tried to implement my own IFindSagas class :
public class SagaFinder : IFindSagas<AttachMessageToGeneralSagaData>.Using<MessageAttachedToGeneralMessage>,
IFindSagas<AttachMessageToRequestSagaData>.Using<MessageAttachedToGeneralMessage>,
IFindSagas<AttachMessageToRequestSagaData>.Using<MessageIsNotAttachedToRequest>,
IFindSagas<AttachMessageToRequestSagaData>.Using<MessageIsNotAttachedToBug>
{
AttachMessageToGeneralSagaData IFindSagas<AttachMessageToGeneralSagaData>.Using<MessageAttachedToGeneralMessage>.FindBy(MessageAttachedToGeneralMessage message)
{
return ObjectFactory.GetInstance<AttachMessageToGeneralSagaData>();
}
AttachMessageToRequestSagaData IFindSagas<AttachMessageToRequestSagaData>.Using<MessageAttachedToGeneralMessage>.FindBy(MessageAttachedToGeneralMessage message)
{
return ObjectFactory.GetInstance<AttachMessageToRequestSagaData>();
}
public AttachMessageToRequestSagaData FindBy(MessageIsNotAttachedToRequest message)
{
return new AttachMessageToRequestSagaData();
}
public AttachMessageToRequestSagaData FindBy(MessageIsNotAttachedToBug message)
{
return new AttachMessageToRequestSagaData();
}
}
But I do not get into my finders for "MessageAttachedToGeneralMessage".
Please tell me if there is some other workaround, or how to make this example working.
I'm not sure that having more than one Saga within the same process boundary works very well - at least, I've had problems with it too. It's probably better (in general) to have Sagas separated into two different processes anyway, because otherwise it would cause a lot of locking and potentially deadlocks on your saga storage.
Is your message that is handled by 2 Sagas Sent or Published? If it's published (or can be made to), it would be easy to separate the Sagas into two separate assemblies. Just be sure to manually call Bus.Subscribe() for the message type in each Saga, since Sagas don't auto-subscribe to messages listed in the app.config.
If your message is Sent, and there's nothing you can do to change it, then create a central handler for your existing message type that either Publishes a second message type to go to both Sagas, or Sends two separate messages to each saga.
Finally (after digging into the source code) I've found the solution. It seems the only way is to implement my own SagaPersister, where I can do anything I want.
Default implementation in NserviceBus of InMemorySagaPersister has the following code :
T ISagaPersister.Get<T>(Guid sagaId)
{
ISagaEntity result;
data.TryGetValue(sagaId, out result);
return (T)result;
}
And exception occurs while casting .
Are handlers reused to proceed another message?
public abstract class SomeHandler : IHandleMessages<MyEvent>
{
public IBus Bus { get; set; }
public String Message { get; set; }
public void Handle(T message)
{
Message = "Test";
SomeInstanceMethod();
}
public void SomeInstanceMethod()
{
if (Message = ...) // Can i use Message here?
return;
}
}
By default, message handlers are configured as ComponentCallModelEnum.Singlecall, which means that each call on the component will be performed on a new instance.
So, two messages will be processed by different instances of the class and cannot share state.
However, what you have here is setting a class property and then calling another method in the class that retrieves that property. That would work fine. However, in my opinion, that is kind of confusing, and if that is what you're after, you're probably better off passing values to another method as a parameter.
While my service executes, many classes will need to access User.Current (that is my own User class). Can I safely store _currentUser in a [ThreadStatic] variable? Does WCF reuse its threads? If that is the case, when will it clean-up the ThreadStatic data? If using ThreadStatic is not safe, where should I put that data? Is there a place inside OperationContext.Current where I can store that kind of data?
Edit 12/14/2009: I can assert that using a ThreadStatic variable is not safe. WCF threads are in a thread pool and the ThreadStatic variable are never reinitialized.
There's a blog post which suggests implementing an IExtension<T>. You may also take a look at this discussion.
Here's a suggested implementation:
public class WcfOperationContext : IExtension<OperationContext>
{
private readonly IDictionary<string, object> items;
private WcfOperationContext()
{
items = new Dictionary<string, object>();
}
public IDictionary<string, object> Items
{
get { return items; }
}
public static WcfOperationContext Current
{
get
{
WcfOperationContext context = OperationContext.Current.Extensions.Find<WcfOperationContext>();
if (context == null)
{
context = new WcfOperationContext();
OperationContext.Current.Extensions.Add(context);
}
return context;
}
}
public void Attach(OperationContext owner) { }
public void Detach(OperationContext owner) { }
}
Which you could use like that:
WcfOperationContext.Current.Items["user"] = _currentUser;
var user = WcfOperationContext.Current.Items["user"] as MyUser;
An alternative solution without adding extra drived class.
OperationContext operationContext = OperationContext.Current;
operationContext.IncomingMessageProperties.Add("SessionKey", "ABCDEFG");
To get the value
var ccc = aaa.IncomingMessageProperties["SessionKey"];
That's it
I found that we miss the data or current context when we make async call with multiple thread switching. To handle such scenario you can try to use CallContext. It's supposed to be used in .NET remoting but it should also work in such scenario.
Set the data in the CallContext:
DataObject data = new DataObject() { RequestId = "1234" };
CallContext.SetData("DataSet", data);
Retrieving shared data from the CallContext:
var data = CallContext.GetData("DataSet") as DataObject;
// Shared data object has to implement ILogicalThreadAffinative
public class DataObject : ILogicalThreadAffinative
{
public string Message { get; set; }
public string Status { get; set; }
}
Why ILogicalThreadAffinative ?
When a remote method call is made to an object in another AppDomain,the current CallContext class generates a LogicalCallContext that travels along with the call to the remote location.
Only objects that expose the ILogicalThreadAffinative interface and are stored in the CallContext are propagated outside the AppDomain.