snatch-dev / chronicle Goto Github PK
View Code? Open in Web Editor NEWImplementation of saga pattern for .NET Core
License: MIT License
Implementation of saga pattern for .NET Core
License: MIT License
Additionally, an unhandled exception thrown from a HandleAsync() method will cause Reject() to be called and begin the compensation.
But this does not happen in this version (or maybe in the newer version as well) I had to catch the exception in the HandleAsync method and explicitly call reject. This looks not so clean at the moment - is this how it is supposed to work? This is how my code looks now. I was expecting not to handle any exceptions and let your awesome package take care of calling compensation.
/// <summary>
/// Handles the asynchronous.
/// </summary>
/// <param name="task">The task.</param>
/// <param name="sagaContext">The saga context.</param>
public async Task HandleAsync(CompleteTask task, ISagaContext sagaContext)
{
try
{
// service call here.
this.CompleteSaga();
await Task.CompletedTask;
}
catch (System.Exception ex)
{
// log exception here
await RejectAsync();
}
}
It would be nice if the saga could set a timeout in the message handlers.
For example, the saga could implement such an interface, similar to ISagaAction
:
public interface ISagaTimeoutAction
{
Task HandleTimeoutAsync(ISagaContext context);
Task CompensateTimeoutAsync(ISagaContext context);
}
(I'm supposing that a timeout doesn't necessarily complete the Saga.)
Timeout could be scheduled in message handler through a method of ISaga
public interface ISaga
{
// ...
void ScheduleTimeout(TimeSpan timeSpan, ISagaContext context);
}
Source of inspiration: https://docs.particular.net/nservicebus/sagas/timeouts
There's a possibility that once the HandleAsync
method finishes the state of the saga will not be persisted into DB. This leads to the state where we have inconsistent transaction. Outbound queue might be handy in this case.
I saw your the DNC-DShop video, and the bus that you use to publish event i guess is RabbitMQ Raw, i want to know if i can use Chronicle library with mediatoR, the current lib for bus i 'm using
How do I reuse the coordinator. I tried to use the Reject functionality and it's working fine. But the next time unable to use the coordinator in subsequent request. I can see the state as Rejected.
need some sample for integration of redis or mongo in test project for custom implementation of ISagaStateRepository
Superseding issue #62
Hi @GooRiOn,
I have a small request with adding possibility of registering Sagas also from dependent assemblies. Currently in Chronicle.Extensions class in method RegisterSagas(...) you use Scrutor scan with method FromAssemblies(..), I think adding also FromAssemblyDependencies(...) can extend the possible usage of Chronicle.
What you think about it?
btw. Chronicle is great and powerful weapon!
Can you please provide more documentation
Is there any "legal" way to wait until the Saga is Completed?
Sometimes need to start Saga and block, for example, UI operation until Saga is Completed or Rejected.
var coordinator = app.ApplicationServices.GetService<ISagaCoordinator>();
var context = SagaContext
.Create()
.WithCorrelationId(Guid.NewGuid())
.Build();
// Sending initial Message/Event
coordinator.ProcessAsync(new Message1 { Text = "Hello" }, context);
// Desired behavior: block until Saga is Completed or Rejected and return ISagaState
var state = coordinator.WaitAsync(context);
Now I can do it with a few stupid lines of code:
// Helper extensions
public static class SagaExtensions
{
public static async Task<ISagaState> WaitAsync(this ISagaCoordinator coordinator, ISagaContext context, ISagaStateRepository sagaStateRepository)
{
var sagaState = await sagaStateRepository.GetStateAsync(context);
while (sagaState is null || sagaState.State == SagaStates.Pending)
{
await Task.Delay(TimeSpan.FromSeconds(1));
sagaState = await sagaStateRepository.GetStateAsync(context);
}
return sagaState;
}
public static async Task<ISagaState> GetStateAsync(this ISagaStateRepository sagaStateRepo, ISagaContext context)
{
return await sagaStateRepo.ReadAsync(context.SagaId, (Type) context.Metadata.First(md => md.Key == "sagaType").Value);
}
}
// Then in some place
var coordinator = app.ApplicationServices.GetService<ISagaCoordinator>();
var context = SagaContext
.Create()
.WithSagaId(SagaId.NewSagaId())
.WithOriginator("Test")
.WithMetadata("key", "lulz")
.WithMetadata("sagaType", typeof(SampleSaga))
.Build();
// Sending initial Message/Event
coordinator.ProcessAsync(new Message1 { Text = "Hello" }, context);
// Block until Saga is Completed or Rejected and return ISagaState
var sagaStateRepo = app.ApplicationServices.GetService<ISagaStateRepository>();
var state = await coordinator.WaitAsync(context, sagaStateRepo);
if (sagaState.State is SagaStates.Rejected)
{
// onRejected()
}
Chronicle.Integrations.MongoDb only supports configuration of a MongoClient by use of a connection string.
MongoClient has an overload that accepts an instance of MongoClientSettings, which supports many additional properties that aren't configurable by use of a simple connectionstring.
"disitrbuted "
Link from Readme.MD is broken :
https://github.com/chronicle-stack/Chronicle/blob/master/chronicle.readthedocs.io
The Chronicle_
package name is a bit confusing, sinche there is another Chronicle
on NuGet.
Have you considered renaming it?
I have tried to use Chronicle ProcessAsync from Azure Service Bus message handler, like below:
`
public void RegisterQueueHandler(string primaryKey, string queueName) where T : class
{
var messageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler)
{
MaxConcurrentCalls = 1,
AutoComplete = false
};
_queueClient = new QueueClient(primaryKey, queueName);
_queueClient.RegisterMessageHandler(ProcessMessagesAsync, messageHandlerOptions);
}
private async Task ProcessMessagesAsync<T>(Message message, CancellationToken token) where T : class
{
using var serviceProviderScope = _serviceProvider.CreateScope();
var payload = JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(message.Body));
if (payload is ISagaCommand)
{
var sagaCoordinator = serviceProviderScope.ServiceProvider.GetRequiredService<ISagaCoordinator>();
await sagaCoordinator.ProcessAsync(payload, null);
}
else
{
var mediator = serviceProviderScope.ServiceProvider.GetRequiredService<IMediator>();
await mediator.Send(payload, token);
}
await _queueClient.CompleteAsync(message.SystemProperties.LockToken);
}
`
When any exception happens inside saga HandleAsync
, CompensateAsync
is ignored and ExceptionReceivedHandler
fired.
What about adding a method to get all the pending saga of a given type?
Something like this:
public interface ISagaStateRepository
{
// ...
Task<IEnumerable<ISagaState>> ReadPendingAsync(Type type);
// or...
Task<IEnumerable<ISagaState>> ReadAsync(Type type, SagaStates state);
}
When an exception gets thrown in a HandleAsync()
, the saga is rejected. Do you think there is a preferred way of handling an unhandled exception thrown from a CompensateAsync()
method that would allow the saga to be recovered and continue compensations at a later date?
Maybe update the log to maintain a status of successfully executed compensations and an additional state of 'RejectCompleted'?
Either way, I'm going to start considering adding an optional UseLogging(ILogger<>) to the ChronicleBuilder and expand on logging.
Saga
class needs version property to avoid concurrent saving in repository.
It would be good to be able to perform optimistic concurrency check to avoid data corruption when running a few instances at the same time.
I propose to add Revision
property of type uint
to the ISagaState
.
Hi is it possible to add more properties in ISagaContext interface?
I'm working on a RedisSagaStateRepository and RedisSagaLog. Do you think a configuration option to immediately remove the SagaLog and SagaState on SagaStatus.Completed would be appropriate or leave that for the onComplete() and onRejected() methods?
I think it would require adding void Remove(SagaId id, Type type)
to ISagaLog and ISagaStateRepository.
We are using this library for some time and we want to continue using - is there a plan to upgrade to .net core 2.2?
Running TestApp (repository's test application) with the Chronicle source code, the application works fine. If you use Chronicle_ NuGet and run TestApp, Reject() does not work as expected.
public Task HandleAsync(Message2 message, ISagaContext context)
{
Reject();
Data.IsMessage2 = true;
Console.WriteLine("M2 reached!");
CompleteSaga();
return Task.CompletedTask;
}
Suppose you don't have any context to pass to the Saga Coordinator:
sagaCoordinator.ProcessAsync(myMessage);
Unfortunately this call is ambiguous because the two method overloads have only optional parameters.
Task ProcessAsync<TMessage>(TMessage message, ISagaContext context = null) where TMessage : class;
Task ProcessAsync<TMessage>(TMessage message, Func<TMessage, ISagaContext, Task> onCompleted = null, Func<TMessage, ISagaContext, Task> onRejected = null, ISagaContext context = null) where TMessage : class;
So in the end you get this error:
CS0121 The call is ambiguous between the following methods or properties: 'ISagaCoordinator.ProcessAsync<TMessage>(TMessage, ISagaContext)' and 'ISagaCoordinator.ProcessAsync<TMessage>(TMessage, Func<TMessage, ISagaContext, Task>, Func<TMessage, ISagaContext, Task>, ISagaContext)'
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.