Is there some way to keep domain events context unaware with NServiceBus 6 and the removal of IBus? - nservicebus

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.

Related

Transferring objects across service provider scopes

I have a CorrelationIdMiddleware that is inspecting incoming request headers and setting a scoped CorrelationId later propagated to all HttpClients.
public class CorrelationId {
public string Value { get;set; }
}
public void ConfigureServices(IServiceCollection services) {
...
services.AddScoped<CorrelationId>();
...
}
I have run into a use case where I need to create an isolated scope around a section of code, but would like the CorrelationId from the scope of the http request to propagate into the isolated scope (The isolated scope has an HttpClient which I would like to have the same header attached).
I would like to spawn off a background Task that is created from DI w/ any required dependencies and for any HttpClients to have headers injected via HttpClientFactory plugins.
public Controller {
public Controller(IServiceProvider serviceProvider, CorrelationId correlationId) { ... }
public IActionResult PostTask() {
var isolatedScope = _serviceProvider.CreateScope();
var action = () => {
using(isolatedScope) {
var backgroundJob = isolatedScope
.ServiceProvider
.GetRequiredService<IBackgroundJob>();
backgroundJob.Execute();
// scopedCorrelationId =/= correlationId
// how can i get correlationId to jump scopes?
}
};
return Task.Factory.StartNew(
action,
CancellationToken.None,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
}
Is there a way to transfer certain objects into the isolated scope? Ideally without having to know the typeof(object) i need to transfer.
It is not possible to transfer objects between the parent scope and the isolated scope.
Information like the CorrelationId arriving with the headers of a request better fits with the HttpContext or with an AsyncLocal variable if it needs to be propagated through an async execution flow.

Can SagaData be manipulated before the saga handler fires using NServiceBus

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.

WCF oneway exception faults channel

I haven't found a clear answer on this. so if there is already a question about this, my bad.
I have a WCF service that pushes data via a callback method to connected clients. this callback method is oneway. so everytime there is new data I loop over the connected users and push the data.
The problem I have right now is when a client disconnects it throws an error and the channel becomes faulted.
I always thought that oneway didn't care if the message arrives at the destination. So if there's no client, then bad luck. but no exception.
but there is an exception and that exception faults the channel.
Now I've read somewhere that if you enable reliable sessions, that the exception won't fault the channel. Is this true?
How can I prevent that the channel goes into faulted state when an exception happens on a oneway call?
The list of registered and avaiable clients you can store in some resource such as List. Create another interface which exposes Connect/Disconnect methods. Connect is invoked when application starts off and within method client is added to the list. Disconnect in turn is invoked when application shuts down in order to get rid client of list. OnStartup/OnClosing events or their equivalents, depending on what kind of application client is, refer to moment when application is launched and closed. Such a solution ensures that resource stores only users avaiable to be reached.
[ServiceContract]
interface IConnection
{
[OperationContract(IsOneWay = true)]
void Connect();
[OperationContract(IsOneWay = true)]
void Disconnect();
}
[ServiceContract]
interface IServiceCallback
{
[OperationContract(IsOneWay = true)]
void CallbackMethod();
}
[ServiceContract(CallbackContract = typeof(IServiceCallback))]
interface IService
{
[OperationContract]
void DoSth();
}
class YourService : IConnection, IService
{
private static readonly List<IServiceCallback> Clients = new List<IServiceCallback>();
public void Connect()
{
var newClient = OperationContext.Current.GetCallbackChannel<IServiceCallback>();
if (Clients.All(client => client != newClient))
Clients.Add(newClient);
}
public void Disconnect()
{
var client = OperationContext.Current.GetCallbackChannel<IServiceCallback>();
if (Clients.Any(cl => cl == client))
Clients.Remove(client);
}
public void DoSth()
{
foreach(var client in Clients)
client.CallbackMethod();
}
}
At the end expose another endpoint with IConnection so that client can create proxy meant to be used only for connection/disconnection.
EDIT:
I know it has been a while since I posted an answear but I did not find in order to prepare an example. The workaround is to let service's interface derive IConnection and then expose only service as an endpoint. I attach simple example of WCF and WPF app as client. Client's application violates MVVM pattern but in this case it is irrelevant. Download it here.
To add on what Maximus said.
I've implemented this pattern in a class where clients can subscribe to get updates of internal states of a system, so a monitoring client can show graphs and other clients do other stuff like enabling/disabling buttons if some state is active.
It removes faulted channels from the list when they fail. Also all current states are sent when a client connects.
here's the code, hope it helps!
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
public class Publish : IPublish
{
private struct SystemState
{
public string State;
public string ExtraInfo;
}
private static Dictionary<Key<string>, IPublishCallback> mCallbacks = new Dictionary<Key<string>, IPublishCallback>();
private static Dictionary<string, SystemState> mStates = new Dictionary<string, SystemState>();
public void RegisterClient(string name, string system)
{
lock (mCallbacks)
{
IPublishCallback callback = OperationContext.Current.GetCallbackChannel<IPublishCallback>();
Key<string> key = new Key<string>(name, system);
if (!mCallbacks.ContainsKey(key))
{
mCallbacks.Add(key, callback);
}
else
{
mCallbacks[key] = callback;
}
foreach (KeyValuePair<string, SystemState> s in mStates)
{
mCallbacks[key].ServiceCallback(s.Key, s.Value.State, s.Value.ExtraInfo);
}
}
}
public void UnregisterClient(string name)
{
lock (mCallbacks)
{
outer: foreach (var key in mCallbacks.Keys)
{
if (key.Key1 == name)
{
mCallbacks.Remove(key);
goto outer;
}
}
}
}
public void SetState(string system, string state, string extraInfo)
{
lock (mCallbacks)
{
List<Key<string>> toRemove = new List<Key<string>>();
SystemState s = new SystemState() { State = state, ExtraInfo = extraInfo };
SystemState systemState;
if (!mStates.TryGetValue(system, out systemState))
mStates.Add(system, s);
else
mStates[system] = s;
foreach (KeyValuePair<Key<string>, IPublishCallback> callback in mCallbacks)
{
try
{
callback.Value.ServiceCallback(system, state, extraInfo);
}
catch (CommunicationException ex)
{
toRemove.Add(new Key<string>(callback.Key.Key1, callback.Key.Key2));
}
catch
{
toRemove.Add(new Key<string>(callback.Key.Key1, callback.Key.Key2));
}
}
foreach (Key<string> key in toRemove)
mCallbacks.Remove(key);
}
}
}

REBUS Send message in Queue ,and receive in another application

I want to implement following scenario using rebus. I am creating on sender application and one receiving application. There will be a class suppose
public class GetPersonRequest
{
public int Id { get; set; }
public string Name { get; set; }
}
public class GetPersonResponse
{
public int Id { get; set; }
public string Name { get; set; }
}
I will send this class object in queue with values. And want to display those value in receiver. How to achieve this?
SENDER code like this:
static void Main(string[] args)
{
GetPersonRequest objGetPersonRequest = new GetPersonRequest();
objGetPersonRequest.Id = 12;
objGetPersonRequest.Name = "Kumar";
using (var activator = new BuiltinHandlerActivator())
{
activator.Register(() => new PrintName());
var bus = Configure.With(activator)
.Logging(l => l.None())
.Transport(t => t.UseMsmq("rebus-application.input"))
.Routing(r => r.TypeBased().Map<GetPersonRequest>("rebus.application.output"))
.Start();
bus.Send(objGetPersonRequest);
Console.WriteLine("Press enter to quit");
Console.ReadLine();
}
RECEIVER Code like this in another console application:
static void Main(string[] args)
{
using (var activator = new BuiltinHandlerActivator())
{
activator.Register(() => new PrintName());
var bus = Configure.With(activator)
.Logging(l => l.None())
.Transport(t => t.UseMsmq("rebus-application.output"))
.Routing(r => r.TypeBased().Map<GetPersonResponse>("rebus-application.input"))
.Start();
Console.WriteLine("Press enter to quit");
Console.ReadLine();
}
}
class PrintName : IHandleMessages<GetPersonResponse>
{
public async Task Handle(GetPersonResponse objGetPersonResponse)
{
Console.WriteLine("RebusDetails Name is {0}", objGetPersonResponse.Name);
}
}
How to achieve this?
I suggest you take a look at the request/reply sample from the RebusSamples repository - it shows the configuration needed in order to do proper request/reply.
From quickly glancing over your code, I can see the following issues/misunderstandings:
Rebus methods are asynchronous, hence bus.Send(objGetPersonRequest) will execute on another thread and you will not know if it failed - always either await bus.Send(...) or bus.Send(...).Wait()
In many cases, only "clients" (*) should have endpoint mappings - in your case, you should map GetPersonRequest (or possibly the entire assembly containing it?) to rebus.application.output, and then do an await bus.Reply(new GetPersonResponse(...)) in the handler - this way, the "server"(*) will not have any dependencies
Moreover - this might be a detail, but I think it leads to a better understanding and easier communication over time:
There's no such thing as an "output queue" - all queues are the input queue of the endpoint that has it as its input queue - therefore, I would argue that the name rebus-application.output is misleading
I suggest you change your queue names to something that identifies each endpoint better, e.g. since your server seems to be capable of returning a person's details, you could call it masterdata, crm, etc., possibly suffixing .input if you e.g. want to have an error queue for each endpoint (e.g. masterdata.input and masterdata.error).
I hope that makes sense :)
(*) In my experience, it's beneficial to have a pretty clear distinction between client and server roles for your endpoints, where clients are endpoints with no (or very few) afferent couplings, which allows for them to easily be added/removed/changed, and servers are endpoints with more afferent couplings.
When you await bus.Reply(...) from a server, it allows for the sender to remain a client and not have its endpoint address configured anywhere but in its own configuration.

NserviceBus not loading existing saga data

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.