How to implement a Saga on Topos? - saga

Topos it .NET Event Processing library, similar to Rebus. Unlike Rebus, it is not so much for messages, as for event processing.
Rebus supports Sagas out of the "box", including in terms of persistence, correlation and concurrency. How to implement a Saga on Topos?
If Topos supports Sagas, is there an example of a Saga implementation somewhere?

Topos does not have any kind of built-in sagas, unfortunately.
In Fleet Manager (the Rebus management app that comes with Rebus Pro, and the reason I made Topos) I made a saga-like event processor that uses MongoDB or LiteDB for persistence.
This implementation is completely proprietary though, as it's part of a commercial software product, and it's not quite generic enough to be suited for reuse. I can tell you a little bit about it here anyway, hopefully to give you some inspiration on how you could go about building something like it yourself. 🙂
The event processor is hosted in a Topos consumer, which dispatches all received events to a bunch of "projections", thus implementing the classic event sourced "left-fold" (current_state + event => new_state).
Fleet Manager has projections in two flavors: process managers (i.e. projections that cause other events to be emitted by issuing commands) and views. The two types combined would be what you call a "saga" 🙂
One possible view could be implemented like this (with lots of stuff removed for brevity):
public class QueueInstanceView : ViewInstance<InstancePerQueue>, IExpire, IHaveAccountId, IHaveQueueName, ICanBeHidden
{
public string AccountId { get; set; }
public string QueueName { get; set; }
public DateTime LastActivity { get; set; }
public bool Hidden { get; set; }
protected override void DispatchEvent(AuditEvent auditEvent)
{
if (auditEvent.Body is EntityHidden entityHidden)
{
QueueName ??= entityHidden.Id;
Hidden = !entityHidden.Reverse;
}
else
{
QueueName ??= auditEvent.GetQueueName();
}
LastActivity = auditEvent.GetTime();
}
}
Note how the view class inherits from the generic ViewInstance<> class, closing it with the InstancePerQueue type. The base class keeps track of the ID of the view instance and some other stuff used to implement idempotency, and then InstancePerQueue defines how events are mapped to view instances.
It looks like this:
public class InstancePerQueue : ViewLocator
{
public override string[] GetViewIds(AuditEvent auditEvent)
{
if (auditEvent.Body is EntityHidden entityHidden)
{
if (entityHidden.HasType(EntityTypeNames.Queue))
{
var accountId = auditEvent.GetAccountId();
return new[] { $"{accountId}/{entityHidden.Id}" };
}
return Array.Empty<string>();
}
var queueName = auditEvent.GetQueueNameOrNull();
if (queueName == null) return Array.Empty<string>();
var accountId = auditEvent.GetAccountId();
return new[] { $"{accountId}/{queueName}" };
}
}
thus correlating events with IDs on the form "/" (where "account" in Fleet Manager terminology is basically just an environment, i.e. the queue names get to IDENTIFY the queues within an account).
Of course lots of logic is then implemented in the projection implementations, but while it's lengthy, it's also fairly straightforward.
I hope that this could give you some inspiration on how you might want to approach building "sagas" for Topos. 🙂
Btw. I cannot take credit for this particular design. I was originally exposed to a design very similar to this back in 2013-2014 by Emil Krog Ingerslev, who came up with it for an event-sourced application we were building at d60.
I later imitated all of the moving parts to implement persistent projections for Cirqus, which we used for a couple of event-sourced apps.
And finally I made my current implementation for Fleet Manager, back in 2016 when I needed something similar, only without the aggregate root stuff present in Cirqus, and working on Kafka instead of normal databases.

Related

How best to handle data fetching needed for FluentValidation

In the app I'm working on, I'm using Mediatr and its pipelines to handle database interaction, some minor business logic, validation, etc.
There's a few checks for things like access control I can handle in the pipeline, since I'm using a context object as described here https://jimmybogard.com/sharing-context-in-mediatr-pipelines/ to go from ASP.Net identity to a custom context object with user information and claims.
One problem I'm having is that since this application is multi-tenant, I need to ensure that even if an object exists, it belongs to that tenant, and the only way to be sure of that is to grab the object from the database and check it. It seems to me the validation shouldn't have side effects, so I don't want to rely on that to populate the context object. But then that pushes a bunch of validation down into the Mediatr handlers as they check for object existence, and so on, leading to a lot of repeated code. I don't really want to query the database multiple times since some queries can be expensive.
Another issue with doing the more complicated validation in the actual request handlers is getting what are essentially validation errors back out. Currently, if one of these checks fail I throw a ValidationException, which is then caught by middleware and turned into a ProblemDetails that's returned to the API caller. This is basically exceptions as flow control, and a validation failure really isn't "exceptional" anyhow.
The thoughts I'm having on how to solve this are:
Somewhere in the pipeline, when I'm building the context, include attempting to fetch the objects needed from the database. Validation then fails if any of these are null. This seems like it would make testing harder, as well as needing to decorate the requests somehow (or use reflection) so the pipeline can know to attempt to load these objects.
Have the queries in the validator, but use some sort of cache aware repository so when the same object is queried later, it's served from the cache, and not the database. The handlers would also use this cache aware repository (Currently the handlers interact directly with the EF Core DbContext to query). This then adds the issue of cache invalidation, which I'm going to have to handle at some point, anyhow (quite a few items are seldom modified). For testing, a dummy cache object can be injected that doesn't actually cache anything.
Make all the responses from requests implement an interface (or extend an abstract class) that has validation info, general success flags, etc. This can either be returned through the API directly, or have some pipeline that transforms failures into ProblemDetails. This would add some boilerplate to every response and handler, but avoids exceptions as flow control, and the caching/reflection issues in the other options.
Assume for 1 and 2 that any sort of race conditions are not an issue. Objects don't change owners, and things are seldom actually deleted from the database for auditing/accounting purposes.
I know there's no true one size fits all for problems like this, but I would like to know if there's additional options I'm missing, or any long term maintainability issues anyone with a similar pipeline has encountered if they went with one of these listed options.
We use MediatR IRequestPreProcessor for fetching data that we need both in RequestHandler and in FluentValidation validators.
RequestPreProcessor:
public interface IProductByIdBinder
{
int ProductId { get; }
ProductEntity Product { set; }
}
public class ProductByIdBinder<T> : IRequestPreProcessor<T> where T : IProductByIdBinder
{
private readonly IRepositoryReadAsync<ProductEntity> productRepository;
public ProductByIdBinder(IRepositoryReadAsync<ProductEntity> productRepository)
{
this.productRepository = productRepository;
}
public async Task Process(T request, CancellationToken cancellationToken)
{
request.Product = await productRepository.GetAsync(request.ProductId);
}
}
RequestHandler:
public class ProductDeleteCommand : IRequest, IProductByIdBinder
{
public ProductDeleteCommand(int id)
{
ProductId = id;
}
public int ProductId { get; }
public ProductEntity Product { get; set; }
private class ProductDeleteCommandHandler : IRequestHandler<ProductDeleteCommand>
{
private readonly IRepositoryAsync<ProductEntity> productRepository;
public ProductDeleteCommandHandler(
IRepositoryAsync<ProductEntity> productRepository)
{
this.productRepository = productRepository;
}
public Task<Unit> Handle(ProductDeleteCommand request, CancellationToken cancellationToken)
{
productRepository.Delete(request.Product);
return Unit.Task;
}
}
}
FluentValidation validator:
public class ProductDeleteCommandValidator : AbstractValidator<ProductDeleteCommand>
{
public ProductDeleteCommandValidator()
{
RuleFor(cmd => cmd)
.Must(cmd => cmd.Product != null)
.WithMessage(cmd => $"The product with id {cmd.ProductId} doesn't exist.");
}
}
I see nothing wrong with handling business logic validation in the handler layer.
Moreover, I do not think it is right to throw exceptions for them, as you said it is exceptions as flow control.
Introducing a cache seems like overkill for the use case too. The most reasonable option is the third IMHO.
Instead of implementing an interface you can use the nifty OneOf library and have something like
using HandlerResponse = OneOf<Success, NotFound, ValidationResponse>;
public class MediatorHandler : IRequestHandler<Command, HandlerResponse>
{
public async Task<HandlerResponse> Handle(
Command command,
CancellationToken cancellationToken)
{
Resource resource = await _userRepository
.GetResource(command.Id);
if (resource is null)
return new NotFound();
if (!resource.IsValid)
return new ValidationResponse(new ProblemDetails());
return new Success();
}
And then map it in your API Layer like
public async Task<IActionResult> PostAsync([FromBody] DummyRequest request)
{
HandlerResponse response = await _mediator.Send(
new Command(request.Id));
return response.Match<IActionResult>(
success => Created(),
notFound => NotFound(),
failed => new UnprocessableEntityResult(failed.ProblemDetails))
);
}

Writing an event based SignalR Notification Service using DBContext ChangeTracker - separation of concerns

I have a controller that modifies appointments in a calendar. I want to use my SignalR hub to notify users à la "User X changed {appointmentTitle}: List: {Property} {OriginalValue} {NewValue}"
I'm a beginner in C# (Syntax-wise it's ok, but OOP concepts are new); I'm trying to use events to achieve the above.
Below are the handlers and arguments, an extract from the controller and a summary of my questions.
Code is abbreviated!
EventArgs
public class AppointmentChangeEventArgs : EventArgs
{
public EntityState AppointmentState = EntityState.Unchanged;
public EntityEntry Entity = null;
public ScheduleData Appointment = null;
}
EventHandler
// maybe this could be just one, and let the consumer decide based on EntityState?
public EventHandler<AppointmentChangeEventArgs> AppointmentChanged;
public EventHandler<AppointmentChangeEventArgs> AppointmentAdded;
public EventHandler<AppointmentChangeEventArgs> AppointmentRemoved;
protected virtual void OnAppointment(AppointmentChangeEventArgs appointmentChangeEventArgs)
{
switch (appointmentChangeEventArgs.AppointmentState)
{
case EntityState.Added:
AppointmentAdded?.Invoke(this, appointmentChangeEventArgs);
break;
case EntityState.Deleted:
AppointmentRemoved?.Invoke(this, appointmentChangeEventArgs);
break;
case EntityState.Modified:
AppointmentChanged?.Invoke(this, appointmentChangeEventArgs);
break;
default:
break;
}
}
Controller
public async Task<IActionResult> Batch([FromBody] ScheduleEditParameters param)
switch (param.Action) {
case "insert":
await _dbContext.Appointments.AddAsync(appointment);
break;
case "update":
// .. get Appointment from DB
appointment.Subject = value.Subject;
appointment.StartTime = value.StartTime;
// ...
case "remove":
// .. get Appointment from DB
_dbContext.Appointments.Remove(appointment);
}
var modifiedEntries = _dbContext.ChangeTracker
.Entries()
.Where(x => x.State != EntityState.Unchanged && x.State != EntityState.Detached)
.Select(x => new AppointmentChangeEventArgs() { Entity = (EntityEntry) x.Entity, AppointmentState = x.State, Appointment = appointment })
.ToList();
if (modifiedEntries.Any())
{
var notificationService = new NotificationService(signalRHub, notificationLogger);
AppointmentAdded += notificationService.OnAppointmentChanged;
AppointmentChanged += notificationService.OnAppointmentChanged;
AppointmentRemoved += notificationService.OnAppointmentChanged;
}
await _dbContext.SaveChangesAsync();
Questions
Is it ok to use EntityEntry and EntityState in event arguments?
for each modified Entry, I can obtain _dbContext.Entry(modifiedEntry).Properties.Where(x => x.IsModified).ToList(); - but does this belong in the NotificationService class? In order to do that, I'd also need to pass the DbContext over to NotificationService.
Might there be a simpler way to achieve this? Adding and Removing handlers are easy ("User X has added|removed ... appointment {Title}"), but in order to figure out the exact changes I'll have to look at the modified properties.
I'd be grateful if you could provide an insight into how you would structure & handle this task. Thank you.
To start off, I would generally recommend you not to use events here. Events are something that may sound very useful but due to the way they work (synchronously), they aren’t really the best way to achieve this in a web context, especially in a primarily asynchronous framework like ASP.NET Core.
Instead, I would recommend you to simply declare your own type, e.g. IAppointmentChangeHandler like this:
public interface IAppointmentChangeHandler
{
Task AddAppointment(ScheduleData appointment);
Task UpdateAppointment(ScheduleData appointment);
Task RemoveAppointment(ScheduleData appointment);
}
Your NotificationService can just implement that interface to be able to handle those events (obviously just send whatever you need to send there):
public class NotificationService : IAppointmentChangeHandler
{
private readonly IHubContext _hubContext;
public NotificationService(IHubContext hubContext)
{
_hubContext = hubContext;
}
public AddAppointment(ScheduleData appointment)
{
await _hubContext.Clients.InvokeAsync("AddAppointment", appointment);
}
public UpdateAppointment(ScheduleData appointment)
{
await _hubContext.Clients.InvokeAsync("UpdateAppointment", appointment);
}
public RemoveAppointment(ScheduleData appointment)
{
await _hubContext.Clients.InvokeAsync("RemoveAppointment", appointment);
}
}
And inside of your controller, you just inject that IAppointmentChangeHandler then and call the actual method on it. That way you have both the controller and the notification service completely decoupled: The controller does not need to construct the type first and you also do not need to subscribe to some events (which you would also have to unsubscribe from at some point again btw). And you can leave the instantiation completely to the DI container.
To answer your individual questions:
Is it ok to use EntityEntry and EntityState in event arguments?
I would avoid using it in a context outside of your database. Both are an implementation detail of your database setup, since you are using Entity Framework here. Not only would this couple your event handlers strongly with Entity Framework (meaning that everyone that wanted to be an event handler would need to reference EF even if they didn’t do anything with it), you are also leaking possibly internal state that may change later (you don’t own the EntityEntry so who knows what EF does with it afterwards).
for each modified Entry, I can obtain _dbContext.Entry(modifiedEntry).Properties.Where(x => x.IsModified).ToList();
If you look at your code, you are first calling Add, Update or Remove on your database set; and then you are using some logic to look at some internal EF stuff to figure out the exact same thing really. You could make this a lot less complex if you constructed the AppointmentChangeEventArgs within those three switch cases directly.
but does this belong in the NotificationService class? In order to do that, I'd also need to pass the DbContext over to NotificationService.
Does a notification service have anything to do with a database? I would say no; unless you are persisting those notifications into the database. When I think about a notification service, then I expect to be able to call something on it to actively trigger a notification, instead of having some logic within the service to figure out what notifications it could possibly trigger.
Might there be a simpler way to achieve this? Adding and Removing handlers are easy ("User X has added|removed ... appointment {Title}"), but in order to figure out the exact changes I'll have to look at the modified properties.
Think about it in the simplest way first: Where do you update the values of the database entity? Within that update case. So at that point, where you are copying over values from the passed object, you can also just check which properties you are actually changing. And with that, you can record easily which properties you need to notify about.
Decouple this completely from EF and you will be a lot more flexible in the long run.

Saga error nservicebus using raven db persistence

I have two messages , clientChangeMessage( responsible for creating the client) and clientContractChangeMEssage( responsible for the booking details of the client). Now in my database a client cannot be created until it has the client contract and vice-versa. On my local system everything is working fine i.e. if get a client change message first i store it in the saga and wait for the client contract message and when that arrives the saga executes both the messages. But on my testers machine when the client change message comes it gets stored in the saga but when a client contract change comes the saga does not find the client change saga and hence creates another saga. I have tried it with the exact same messages that my tester has tried ,it works on my machine, and am unable to figure out what might be going wrong. I am using raven db persistence. (Sorry i could not think of pasting any code for this)
ClientSagaState
public class ClientSagaState:IContainSagaData
{
#region NserviceBus
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
#endregion
public Guid ClientRef { get; set; }
public ClientMessage ClientChangeMessage { get; set; }
public ClientContractChangeMessage ClientContractChange { get; set; }
}
public class ClientSaga:Saga<ClientSagaState>,
IAmStartedByMessages<ClientChangeMessage>,
IAmStartedByMessages<ClientContractChangeMessage>
{
public override void ConfigureHowToFindSaga()
{
ConfigureMapping<ClientChangeMessage>(s => s.ClientRef, m => m.EntityRef);
ConfigureMapping<ClientContractChangeMessage>(s => s.ClientRef, m => m.PrimaryEntityRef);
}
public void Handle(ClientChangeMessage message)
{
if (BusRefTranslator.GetLocalRef(EntityTranslationNames.ClientChange, message.EntityRef.Value) != null)
{
GetHandler<ClientChangeMessage>().Handle(message);
CompleteTheSaga();
return;
}
HandleServiceUserChangeAndDependencies(message);
//MarkAsComplete();
CompleteTheSaga();
}
public void Handle(ClientContractChangeMessage message)
{
var state=this.Data;
//Some handling logic
//Check if client is not in database then store the state
state.ClientContractChange=message;
state.ClientRef =message.PrimaryEntityRef;
//if client is in the data base then
MarkAsComplete();
}
Thanks,
Because you are mapping to the saga data via the ClientRef property, you need to tell the persistence (Raven in this case) that this property is unique. What is probably happening is that, in some cases (it comes down to a race condition) the query done on the Raven index by the second message retrieves stale data, assumes there is no saga data, and creates new.
This should fix your issue:
[Unique]
public Guid ClientRef { get; set; }
With this information, the Raven saga persister will create an additional document based on this property (because loading by Id in Raven is fully atomic) so that the second message will be sure to find it.
If you were using another persistence medium like NHibernate, the same attribute would be used to construct a unique index on that column.
Edit based on comment
The unique constraint document and your saga data will be fully consistent, so depending on timing of incoming messages, one of 3 things will happen.
The message is truly the first message to arrive and be processed, so no saga data is found, so it is created.
The message is the second to arrive, so it looks for the saga data, finds it, and processes successfully.
The 2nd message arrives very close to the first message, so they are both processing in separate threads at the same time. Both threads look in the saga data and find nothing, so they both begin to process. The one that finishes first commits successfully and saves its saga data. The one that finishes second attempts to save the saga data, but finds that while it's been working the other thread has moved its cheese, so Raven throws a concurrency exception. Your message goes back on the queue and is retried, and now that the saga data exists, the retry acts like Scenario #2.

LightSwitch - bulk-loading all requests into one using a domain service

I need to group some data from a SQL Server database and since LightSwitch doesn't support that out-of-the-box I use a Domain Service according to Eric Erhardt's guide.
However my table contains several foreign keys and of course I want the correct related data to be shown in the table (just doing like in the guide will only make the key values show). I solved this by adding a Relationship to my newly created Entity like this:
And my Domain Service class looks like this:
public class AzureDbTestReportData : DomainService
{
private CountryLawDataDataObjectContext context;
public CountryLawDataDataObjectContext Context
{
get
{
if (this.context == null)
{
EntityConnectionStringBuilder builder = new EntityConnectionStringBuilder();
builder.Metadata =
"res://*/CountryLawDataData.csdl|res://*/CountryLawDataData.ssdl|res://*/CountryLawDataData.msl";
builder.Provider = "System.Data.SqlClient";
builder.ProviderConnectionString =
WebConfigurationManager.ConnectionStrings["CountryLawDataData"].ConnectionString;
this.context = new CountryLawDataDataObjectContext(builder.ConnectionString);
}
return this.context;
}
}
/// <summary>
/// Override the Count method in order for paging to work correctly
/// </summary>
protected override int Count<T>(IQueryable<T> query)
{
return query.Count();
}
[Query(IsDefault = true)]
public IQueryable<RuleEntryTest> GetRuleEntryTest()
{
return this.Context.RuleEntries
.Select(g =>
new RuleEntryTest()
{
Id = g.Id,
Country = g.Country,
BaseField = g.BaseField
});
}
}
public class RuleEntryTest
{
[Key]
public int Id { get; set; }
public string Country { get; set; }
public int BaseField { get; set; }
}
}
It works and all that, both the Country name and the Basefield loads with Autocomplete-boxes as it should, but it takes VERY long time. With two columns it takes 5-10 seconds to load one page.. and I have 10 more columns I haven't implemented yet.
The reason it takes so long time is because each related data (each Country and BaseField) requires one request. Loading a page looks like this in Fiddler:
This isn't acceptable at all, it should be a way of combining all those calls into one, just as it does when loading the same table without going through the Domain Service.
So.. that was a lot explaining, my question is: Is there any way I can make all related data load at once or improve the performance by any other way? It should not take 10+ seconds to load a screen.
Thanks for any help or input!s
My RIA Service queries are extremely fast, compared to not using them, even when I'm doing aggregation. It might be the fact that you're using "virtual relationships" (which you can tell by the dotted lines between the tables), that you've created using your RuleEntryTest entity.
Why is your original RuleEntry entity not related to both Country & BaseUnit in LightSwitch BEFORE you start creating your RIA entity?
I haven't used Fiddler to see what's happening, but I'd try creating "real" relationships, instead of "virtual" ones, & see if that helps your RIA entity's performance.

Passing IList<T> vs. IEnumerable<T> with protobuf-net

I noticed in the protobuf-net changelog that IList<> was supported but I'm getting the "Cannot create an instance of an interface" exception. If I change to IEnumerable<> then life is good. Does this sound correct?
// Client call
public override IList<IApplicationWorkflow> Execute(IRoleManagement service)
{
IList<ApplicationWorkflowMessagePart> list = service.RetrieveWorkflows(roleNames);
IList<IApplicationWorkflow> workflows = new List<IApplicationWorkflow>(list.Count);
foreach (ApplicationWorkflowMessagePart a in list)
{
workflows.Add(new ApplicationWorkflowImpl(a));
}
return workflows;
}
// Service contract
[OperationContract, ProtoBehavior]
[ServiceKnownType(typeof (ServiceFault))]
[FaultContract(typeof (ServiceFault))]
IList<ApplicationWorkflowMessagePart> RetrieveWorkflows(string[] roleNames);
// Service implementation
public IList<ApplicationWorkflowMessagePart> RetrieveWorkflows(string[] roleNames)
{
IList<IApplicationWorkflow> workflows = manager.RetrieveApplicationWorkflows(roleNames);
IList<ApplicationWorkflowMessagePart> workflowParts = new List<ApplicationWorkflowMessagePart>();
if (workflows != null)
{
foreach (IApplicationWorkflow workflow in workflows)
{
workflowParts.Add(
ModelMediator.GetMessagePart<ApplicationWorkflowMessagePart, IApplicationWorkflow>(workflow));
}
}
return workflowParts;
}
Thanks,
Mike
Also, is there document site that has this and other answers? I hate to be asking newb questions. :)
Currently it will support IList<T> as a property, as long as it doesn't have to create it - i.e. allowing things like (attributes not shown for brevity):
class Order {
private IList<OrderLine> lines = new List<OrderLine>();
public IList<OrderLine> Lines {get {return lines;}}
}
I would have to check, but for similar reasons, I expect it would work with Merge, but not Deserialize (which is what the WCF hooks use). However, I can't think of a reason it couldn't default to List<T>... it just doesn't at the moment.
The simplest option is probably to stick with List<T> / T[] - but I can have a look if you want... but I'm in a "crunch" on a (work) project at the moment, so I can't lift the bonnet today.
Re "this and other answers"... there is a google group, but that is not just protobuf-net (protobuf-net is simply one of many "protocol buffers" implementations).
You are also free to log an issue on the project site. I do mean to collate the FAQs and add them to the wiki on the site - but time is not always my friend...
But hey! I'm here... ;-p