From d5bf79dd71eaef349f5c1f23ee37a497b598371f Mon Sep 17 00:00:00 2001 From: Nick Cromwell Date: Tue, 21 Jan 2020 00:10:33 -0500 Subject: [PATCH 1/5] Add Redis persistence to Chronicle Solution; Json; Refactor to remove need for public SagaId constructor --- .../src/Persistence/RedisSagaLog.cs | 6 ++--- .../src/Persistence/RedisSagaLogData.cs | 24 ++++++------------- .../src/Persistence/RedisSagaState.cs | 19 ++++++--------- .../Persistence/RedisSagaStateRepository.cs | 7 +++--- src/Chronicle.sln | 19 +++++++++++++++ 5 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaLog.cs b/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaLog.cs index e0bc763..fc823f4 100644 --- a/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaLog.cs +++ b/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaLog.cs @@ -31,7 +31,7 @@ public async Task> ReadAsync(SagaId id, Type sagaType) var sagaLogDatas = new List(); var deserializedSagaLogDatas = new List(); var cachedSagaLogDatas = await cache.GetStringAsync(LogId(id, sagaType)); - + if (!string.IsNullOrWhiteSpace(cachedSagaLogDatas)) { sagaLogDatas = JsonConvert.DeserializeObject>(cachedSagaLogDatas); @@ -39,7 +39,7 @@ public async Task> ReadAsync(SagaId id, Type sagaType) { { var message = (sld.Message as JObject)?.ToObject(sld.MessageType); - deserializedSagaLogDatas.Add(new RedisSagaLogData(sld.Id, sld.Type, sld.CreatedAt, message, sld.MessageType)); + deserializedSagaLogDatas.Add(new RedisSagaLogData { SagaId = sld.Id, Type = sld.Type, CreatedAt = sld.CreatedAt, Message = message, MessageType = sld.MessageType }); } }); } @@ -54,7 +54,7 @@ public async Task WriteAsync(ISagaLogData logData) } var sagaLogDatas = (await ReadAsync(logData.Id, logData.Type)).ToList(); - var sagaLogData = new RedisSagaLogData(logData.Id, logData.Type, logData.CreatedAt, logData.Message, logData.Message.GetType()); + var sagaLogData = new RedisSagaLogData { SagaId = logData.Id, Type = logData.Type, CreatedAt = logData.CreatedAt, Message = logData.Message, MessageType = logData.Message.GetType() }; sagaLogDatas.Add(sagaLogData); diff --git a/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaLogData.cs b/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaLogData.cs index ac4901e..d88b86c 100644 --- a/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaLogData.cs +++ b/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaLogData.cs @@ -5,23 +5,13 @@ namespace Chronicle.Integrations.Redis.Persistence { internal sealed class RedisSagaLogData : ISagaLogData { - public SagaId Id { get; } - public Type Type { get; } - public long CreatedAt { get; } - public object Message { get; } - public Type MessageType { get; } + public string SagaId { get; set; } + [JsonIgnore] + public SagaId Id => SagaId; + public Type Type { get; set; } + public long CreatedAt { get; set; } + public object Message { get; set; } + public Type MessageType { get; set; } - [JsonConstructor] - public RedisSagaLogData(SagaId id, Type type, long createdAt, object message, Type messageType) - { - Id = id; - Type = type; - CreatedAt = createdAt; - Message = message; - MessageType = messageType; - } - - public static ISagaLogData Create(SagaId sagaId, Type sagaType, object message) - => new RedisSagaLogData(sagaId, sagaType, DateTimeOffset.Now.ToUnixTimeMilliseconds(), message, message.GetType()); } } \ No newline at end of file diff --git a/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaState.cs b/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaState.cs index 94af561..5e12f93 100644 --- a/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaState.cs +++ b/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaState.cs @@ -5,18 +5,13 @@ namespace Chronicle.Integrations.Redis.Persistence { internal sealed class RedisSagaState : ISagaState { - public SagaId Id { get; } - public Type Type { get; } - public SagaStates State { get; private set; } - public object Data { get; private set; } - public Type DataType { get; } - - [JsonConstructor] - public RedisSagaState(SagaId id, Type type, SagaStates state, object data = null, Type dataType = null) - => (Id, Type, State, Data, DataType) = (id, type, state, data, dataType); - - public static ISagaState Create(SagaId sagaId, Type sagaType, SagaStates state, object data = null, Type dataType = null) - => new RedisSagaState(sagaId, sagaType, state, data, dataType); + public string SagaId { get; set; } + [JsonIgnore] + public SagaId Id => SagaId; + public Type Type { get; set; } + public SagaStates State { get; set; } + public object Data { get; set; } + public Type DataType { get; set; } public void Update(SagaStates state, object data = null) { diff --git a/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaStateRepository.cs b/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaStateRepository.cs index 9df3d68..5282f28 100644 --- a/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaStateRepository.cs +++ b/src/Chronicle.Integrations.Redis/src/Persistence/RedisSagaStateRepository.cs @@ -12,7 +12,7 @@ internal sealed class RedisSagaStateRepository : ISagaStateRepository public RedisSagaStateRepository(IDistributedCache cache) => _cache = cache; - + public async Task ReadAsync(SagaId sagaId, Type sagaType) { if (string.IsNullOrWhiteSpace(sagaId)) @@ -26,7 +26,7 @@ public async Task ReadAsync(SagaId sagaId, Type sagaType) RedisSagaState state = null; var cachedSagaState = await _cache.GetStringAsync(StateId(sagaId, sagaType)); - + if (!string.IsNullOrWhiteSpace(cachedSagaState)) { state = JsonConvert.DeserializeObject(cachedSagaState); @@ -42,8 +42,7 @@ public async Task WriteAsync(ISagaState state) throw new ChronicleException($"{nameof(state)} was null."); } - var sagaState = new RedisSagaState(state.Id, state.Type, state.State, state.Data, state.Data.GetType()); - + var sagaState = new RedisSagaState { SagaId = state.Id, Type = state.Type, State = state.State, Data = state.Data, DataType = state.Data.GetType() }; var serializedSagaState = JsonConvert.SerializeObject(sagaState); await _cache.SetStringAsync(StateId(state.Id, state.Type), serializedSagaState); diff --git a/src/Chronicle.sln b/src/Chronicle.sln index a10f270..23fb6de 100644 --- a/src/Chronicle.sln +++ b/src/Chronicle.sln @@ -11,6 +11,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chronicle.Tests", "Chronicl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chronicle.Integrations.MongoDB", "Chronicle.Integrations.MongoDB\src\Chronicle.Integrations.MongoDB.csproj", "{DFB344D1-3121-43F9-9E66-20BC6B0DD9CC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chronicle.Integrations.Redis", "Chronicle.Integrations.Redis", "{3B661443-315C-4A0F-A67E-62A792AF35D3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chronicle.Integrations.Redis", "Chronicle.Integrations.Redis\src\Chronicle.Integrations.Redis.csproj", "{755F7A4D-7FC3-444E-86D3-528787961B2F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +73,18 @@ Global {DFB344D1-3121-43F9-9E66-20BC6B0DD9CC}.Release|x64.Build.0 = Release|Any CPU {DFB344D1-3121-43F9-9E66-20BC6B0DD9CC}.Release|x86.ActiveCfg = Release|Any CPU {DFB344D1-3121-43F9-9E66-20BC6B0DD9CC}.Release|x86.Build.0 = Release|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Debug|x64.ActiveCfg = Debug|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Debug|x64.Build.0 = Debug|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Debug|x86.ActiveCfg = Debug|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Debug|x86.Build.0 = Debug|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Release|Any CPU.Build.0 = Release|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Release|x64.ActiveCfg = Release|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Release|x64.Build.0 = Release|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Release|x86.ActiveCfg = Release|Any CPU + {755F7A4D-7FC3-444E-86D3-528787961B2F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -76,4 +92,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F6DDA96B-847E-4F6D-86EC-126800155219} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {755F7A4D-7FC3-444E-86D3-528787961B2F} = {3B661443-315C-4A0F-A67E-62A792AF35D3} + EndGlobalSection EndGlobal From 665e913afb7c64867a9971bd48af66ca574c47e9 Mon Sep 17 00:00:00 2001 From: Nick Cromwell Date: Tue, 18 Feb 2020 16:14:17 -0500 Subject: [PATCH 2/5] Do not process saga actions for sagas that have been completed --- src/Chronicle/Managers/SagaCoordinator.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Chronicle/Managers/SagaCoordinator.cs b/src/Chronicle/Managers/SagaCoordinator.cs index 60f940f..91f1d8c 100644 --- a/src/Chronicle/Managers/SagaCoordinator.cs +++ b/src/Chronicle/Managers/SagaCoordinator.cs @@ -17,7 +17,7 @@ internal sealed class SagaCoordinator : ISagaCoordinator private readonly ISagaPostProcessor _postProcessor; private static readonly KeyedLocker Locker = new KeyedLocker(); - public SagaCoordinator(ISagaSeeker seeker, ISagaInitializer initializer, ISagaProcessor processor, + public SagaCoordinator(ISagaSeeker seeker, ISagaInitializer initializer, ISagaProcessor processor, ISagaPostProcessor postProcessor) { _seeker = seeker; @@ -26,7 +26,7 @@ public SagaCoordinator(ISagaSeeker seeker, ISagaInitializer initializer, ISagaPr _postProcessor = postProcessor; } - public Task ProcessAsync(TMessage message, ISagaContext context = null) where TMessage : class + public Task ProcessAsync(TMessage message, ISagaContext context = null) where TMessage : class => ProcessAsync(message: message, onCompleted: null, onRejected: null, context: context); public async Task ProcessAsync(TMessage message, Func onCompleted = null, @@ -59,11 +59,11 @@ private async Task ProcessAsync(TMessage message, ISagaAction Date: Mon, 31 Aug 2020 13:15:45 -0400 Subject: [PATCH 3/5] Update packages --- .../src/Chronicle.Integrations.MongoDB.csproj | 6 +++--- .../src/Chronicle.Integrations.Redis.csproj | 6 +++--- src/Chronicle.Tests/Chronicle.Tests.csproj | 13 ++++++++----- src/Chronicle/Chronicle.csproj | 4 ++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Chronicle.Integrations.MongoDB/src/Chronicle.Integrations.MongoDB.csproj b/src/Chronicle.Integrations.MongoDB/src/Chronicle.Integrations.MongoDB.csproj index ba633bb..5b8e9c7 100644 --- a/src/Chronicle.Integrations.MongoDB/src/Chronicle.Integrations.MongoDB.csproj +++ b/src/Chronicle.Integrations.MongoDB/src/Chronicle.Integrations.MongoDB.csproj @@ -18,9 +18,9 @@ - - - + + + diff --git a/src/Chronicle.Integrations.Redis/src/Chronicle.Integrations.Redis.csproj b/src/Chronicle.Integrations.Redis/src/Chronicle.Integrations.Redis.csproj index edb2465..4f0b965 100644 --- a/src/Chronicle.Integrations.Redis/src/Chronicle.Integrations.Redis.csproj +++ b/src/Chronicle.Integrations.Redis/src/Chronicle.Integrations.Redis.csproj @@ -17,10 +17,10 @@ 2.0 - + - - + + diff --git a/src/Chronicle.Tests/Chronicle.Tests.csproj b/src/Chronicle.Tests/Chronicle.Tests.csproj index 039cdd3..ed48e16 100644 --- a/src/Chronicle.Tests/Chronicle.Tests.csproj +++ b/src/Chronicle.Tests/Chronicle.Tests.csproj @@ -6,11 +6,14 @@ - - - - - + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/src/Chronicle/Chronicle.csproj b/src/Chronicle/Chronicle.csproj index aa237dc..1142558 100644 --- a/src/Chronicle/Chronicle.csproj +++ b/src/Chronicle/Chronicle.csproj @@ -19,8 +19,8 @@ - - + + From 234e9a52c43f32287547cb971c208e33bef7b668 Mon Sep 17 00:00:00 2001 From: Nick Cromwell Date: Wed, 2 Sep 2020 13:11:35 -0400 Subject: [PATCH 4/5] Add IChronicleConfiguration and default settings; AllowConcurrentWrites option for non-transient DbContext --- src/Chronicle/Builders/ChronicleBuilder.cs | 10 ++++++++++ src/Chronicle/ChronicleConfiguration.cs | 8 ++++++++ src/Chronicle/IChronicleBuilder.cs | 1 + src/Chronicle/IChronicleConfiguration.cs | 7 +++++++ src/Chronicle/Managers/SagaProcessor.cs | 22 ++++++++++++++++------ 5 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 src/Chronicle/ChronicleConfiguration.cs create mode 100644 src/Chronicle/IChronicleConfiguration.cs diff --git a/src/Chronicle/Builders/ChronicleBuilder.cs b/src/Chronicle/Builders/ChronicleBuilder.cs index 5a4e614..3041cc5 100644 --- a/src/Chronicle/Builders/ChronicleBuilder.cs +++ b/src/Chronicle/Builders/ChronicleBuilder.cs @@ -28,5 +28,15 @@ public IChronicleBuilder UseSagaStateRepository() where TRepository Services.AddTransient(typeof(ISagaStateRepository), typeof(TRepository)); return this; } + + public IChronicleBuilder UseChronicleConfiguration(IChronicleConfiguration configuration) + { + if (configuration is null) + { + configuration = new ChronicleConfiguration(); + } + Services.AddSingleton(configuration); + return this; + } } } diff --git a/src/Chronicle/ChronicleConfiguration.cs b/src/Chronicle/ChronicleConfiguration.cs new file mode 100644 index 0000000..7a03673 --- /dev/null +++ b/src/Chronicle/ChronicleConfiguration.cs @@ -0,0 +1,8 @@ +namespace Chronicle +{ + + public class ChronicleConfiguration : IChronicleConfiguration + { + public bool AllowConcurrentWrites { get; set; } = true; + } +} \ No newline at end of file diff --git a/src/Chronicle/IChronicleBuilder.cs b/src/Chronicle/IChronicleBuilder.cs index c24b649..aa130fe 100644 --- a/src/Chronicle/IChronicleBuilder.cs +++ b/src/Chronicle/IChronicleBuilder.cs @@ -8,5 +8,6 @@ public interface IChronicleBuilder IChronicleBuilder UseInMemoryPersistence(); IChronicleBuilder UseSagaLog() where TSagaLog : ISagaLog; IChronicleBuilder UseSagaStateRepository() where TRepository : ISagaStateRepository; + IChronicleBuilder UseChronicleConfiguration(IChronicleConfiguration configuration); } } diff --git a/src/Chronicle/IChronicleConfiguration.cs b/src/Chronicle/IChronicleConfiguration.cs new file mode 100644 index 0000000..aadfce9 --- /dev/null +++ b/src/Chronicle/IChronicleConfiguration.cs @@ -0,0 +1,7 @@ +namespace Chronicle +{ + public interface IChronicleConfiguration + { + bool AllowConcurrentWrites { get; set; } + } +} \ No newline at end of file diff --git a/src/Chronicle/Managers/SagaProcessor.cs b/src/Chronicle/Managers/SagaProcessor.cs index 98e4d4b..3e14f28 100644 --- a/src/Chronicle/Managers/SagaProcessor.cs +++ b/src/Chronicle/Managers/SagaProcessor.cs @@ -7,11 +7,13 @@ namespace Chronicle.Managers internal sealed class SagaProcessor : ISagaProcessor { private readonly ISagaStateRepository _repository; + private readonly IChronicleConfiguration _configuration; private readonly ISagaLog _log; - public SagaProcessor(ISagaStateRepository repository, ISagaLog log) + public SagaProcessor(ISagaStateRepository repository, IChronicleConfiguration configuration, ISagaLog log) { _repository = repository; + _configuration = configuration; _log = log; } @@ -49,13 +51,21 @@ private async Task UpdateSagaAsync(TMessage message, ISaga saga, ISaga state.Update(saga.State, updatedSagaData); var logData = SagaLogData.Create(saga.Id, sagaType, message); - var persistenceTasks = new [] + if (_configuration.AllowConcurrentWrites) { - _repository.WriteAsync(state), - _log.WriteAsync(logData) - }; + var persistenceTasks = new[] + { + _repository.WriteAsync(state), + _log.WriteAsync(logData) + }; - await Task.WhenAll(persistenceTasks).ConfigureAwait(false); + await Task.WhenAll(persistenceTasks).ConfigureAwait(false); + } + else + { + await _repository.WriteAsync(state); + await _log.WriteAsync(logData); + } } } } From c281314240bab4756e8ff70f3f3234af564a6296 Mon Sep 17 00:00:00 2001 From: Nick Cromwell Date: Wed, 2 Sep 2020 13:50:21 -0400 Subject: [PATCH 5/5] Register default IChronicleConfiguration --- src/Chronicle/Extensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Chronicle/Extensions.cs b/src/Chronicle/Extensions.cs index e6b6ba8..f5afe5e 100644 --- a/src/Chronicle/Extensions.cs +++ b/src/Chronicle/Extensions.cs @@ -17,6 +17,7 @@ public static IServiceCollection AddChronicle(this IServiceCollection services, services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddSingleton(); var chronicleBuilder = new ChronicleBuilder(services);