From 284759561b39372c2d7933d2162956d0bc5915b8 Mon Sep 17 00:00:00 2001 From: stidsborg Date: Fri, 24 Jan 2025 11:35:43 +0100 Subject: [PATCH] Simplify serializer --- .../RActionWithStateRegistrationTests.cs | 25 ++---- .../RFuncRegistrationTests.cs | 26 ++---- .../RFuncWithStateRegistrationTests.cs | 23 +---- .../CustomMessageSerializerTests.cs | 23 +---- .../Messaging/TestTemplates/MessagesTests.cs | 25 ++---- .../RFunctionTests/ControlPanelTests.cs | 2 +- .../TestTemplates/StoreTests.cs | 2 +- .../Invocation/InvocationHelper.cs | 16 ++-- .../CustomSerializableDecorator.cs | 86 ++++++++++++++++++ .../Serialization/DefaultSerializer.cs | 26 +----- .../Serialization/ErrorHandlingDecorator.cs | 90 ++----------------- .../Serialization/ICustomSerializable.cs | 7 ++ .../CoreRuntime/Serialization/ISerializer.cs | 13 ++- .../Domain/Effect.cs | 12 +-- .../Domain/ExistingEffects.cs | 4 +- .../Domain/ExistingStates.cs | 4 +- .../Domain/StateFetcher.cs | 2 +- .../Domain/States.cs | 6 +- 18 files changed, 155 insertions(+), 237 deletions(-) create mode 100644 Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/CustomSerializableDecorator.cs create mode 100644 Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ICustomSerializable.cs diff --git a/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RActionWithStateRegistrationTests.cs b/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RActionWithStateRegistrationTests.cs index 998d4e67..410aca6b 100644 --- a/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RActionWithStateRegistrationTests.cs +++ b/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RActionWithStateRegistrationTests.cs @@ -54,38 +54,23 @@ private class Serializer : ISerializer public bool Invoked { get; set; } private ISerializer Default { get; } = DefaultSerializer.Instance; - public byte[] SerializeParameter(TParam parameter) + public byte[] Serialize(T value) { Invoked = true; - return Default.SerializeParameter(parameter); + return Default.Serialize(value); } - public TParam DeserializeParameter(byte[] json) - => Default.DeserializeParameter(json); + public T Deserialize(byte[] json) + => Default.Deserialize(json); public StoredException SerializeException(FatalWorkflowException exception) => Default.SerializeException(exception); public FatalWorkflowException DeserializeException(FlowId flowId, StoredException storedException) => Default.DeserializeException(flowId, storedException); - - public byte[] SerializeResult(TResult result) - => Default.SerializeResult(result); - public TResult DeserializeResult(byte[] json) - => Default.DeserializeResult(json); - + public SerializedMessage SerializeMessage(TEvent message) where TEvent : notnull => Default.SerializeMessage(message); public object DeserializeMessage(byte[] json, byte[] type) => Default.DeserializeMessage(json, type); - - public byte[] SerializeEffectResult(TResult result) - => Default.SerializeEffectResult(result); - public TResult DeserializeEffectResult(byte[] json) - => Default.DeserializeEffectResult(json); - - public byte[] SerializeState(TState state) where TState : FlowState, new() - => Default.SerializeState(state); - public TState DeserializeState(byte[] json) where TState : FlowState, new() - => Default.DeserializeState(json); } } \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RFuncRegistrationTests.cs b/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RFuncRegistrationTests.cs index 2f6c4737..667c342d 100644 --- a/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RFuncRegistrationTests.cs +++ b/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RFuncRegistrationTests.cs @@ -59,38 +59,22 @@ private class Serializer : ISerializer public bool Invoked { get; set; } private ISerializer Default { get; } = DefaultSerializer.Instance; - public byte[] SerializeParameter(TParam parameter) + public byte[] Serialize(T value) { Invoked = true; - return Default.SerializeParameter(parameter); + return Default.Serialize(value); } - public TParam DeserializeParameter(byte[] json) - => Default.DeserializeParameter(json); + public T Deserialize(byte[] json) + => Default.Deserialize(json); public StoredException SerializeException(FatalWorkflowException exception) => Default.SerializeException(exception); public FatalWorkflowException DeserializeException(FlowId flowId, StoredException storedException) => Default.DeserializeException(flowId, storedException); - - public byte[] SerializeResult(TResult result) - => Default.SerializeResult(result); - public TResult DeserializeResult(byte[] json) - => Default.DeserializeResult(json); - + public SerializedMessage SerializeMessage(TEvent message) where TEvent : notnull => Default.SerializeMessage(message); public object DeserializeMessage(byte[] json, byte[] type) => Default.DeserializeMessage(json, type); - - public byte[] SerializeEffectResult(TResult result) - => Default.SerializeEffectResult(result); - public TResult DeserializeEffectResult(byte[] json) - => Default.DeserializeEffectResult(json); - - public byte[] SerializeState(TState state) where TState : FlowState, new() - => Default.SerializeState(state); - - public TState DeserializeState(byte[] json) where TState : FlowState, new() - => Default.DeserializeState(json); } } \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RFuncWithStateRegistrationTests.cs b/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RFuncWithStateRegistrationTests.cs index e7f616c6..03fff6b9 100644 --- a/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RFuncWithStateRegistrationTests.cs +++ b/Core/Cleipnir.ResilientFunctions.Tests/InMemoryTests/RegistrationTests/RFuncWithStateRegistrationTests.cs @@ -59,37 +59,22 @@ private class Serializer : ISerializer public bool Invoked { get; set; } private ISerializer Default { get; } = DefaultSerializer.Instance; - public byte[] SerializeParameter(TParam parameter) + public byte[] Serialize(T parameter) { Invoked = true; - return Default.SerializeParameter(parameter); + return Default.Serialize(parameter); } - public TParam DeserializeParameter(byte[] json) - => Default.DeserializeParameter(json); + public T Deserialize(byte[] bytes) + => Default.Deserialize(bytes); public StoredException SerializeException(FatalWorkflowException exception) => Default.SerializeException(exception); public FatalWorkflowException DeserializeException(FlowId flowId, StoredException storedException) => Default.DeserializeException(flowId, storedException); - public byte[] SerializeResult(TResult result) - => Default.SerializeResult(result); - public TResult DeserializeResult(byte[] json) - => Default.DeserializeResult(json); - public SerializedMessage SerializeMessage(TEvent message) where TEvent : notnull => Default.SerializeMessage(message); public object DeserializeMessage(byte[] json, byte[] type) => Default.DeserializeMessage(json, type); - - public byte[] SerializeEffectResult(TResult result) - => Default.SerializeEffectResult(result); - public TResult DeserializeEffectResult(byte[] json) - => Default.DeserializeEffectResult(json); - - public byte[] SerializeState(TState state) where TState : FlowState, new() - => Default.SerializeState(state); - public TState DeserializeState(byte[] json) where TState : FlowState, new() - => Default.DeserializeState(json); } } \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions.Tests/Messaging/TestTemplates/CustomMessageSerializerTests.cs b/Core/Cleipnir.ResilientFunctions.Tests/Messaging/TestTemplates/CustomMessageSerializerTests.cs index f2fe4b2a..9e4499c3 100644 --- a/Core/Cleipnir.ResilientFunctions.Tests/Messaging/TestTemplates/CustomMessageSerializerTests.cs +++ b/Core/Cleipnir.ResilientFunctions.Tests/Messaging/TestTemplates/CustomMessageSerializerTests.cs @@ -61,22 +61,17 @@ private class EventSerializer : ISerializer public Utils.SyncedList EventToSerialize { get; } = new(); public Utils.SyncedList> EventToDeserialize { get; }= new(); - public byte[] SerializeParameter(TParam parameter) - => DefaultSerializer.Instance.SerializeParameter(parameter); + public byte[] Serialize(T value) + => DefaultSerializer.Instance.Serialize(value); - public TParam DeserializeParameter(byte[] json) - => DefaultSerializer.Instance.DeserializeParameter(json); + public T Deserialize(byte[] json) + => DefaultSerializer.Instance.Deserialize(json); public StoredException SerializeException(FatalWorkflowException exception) => DefaultSerializer.Instance.SerializeException(exception); public FatalWorkflowException DeserializeException(FlowId flowId, StoredException storedException) => DefaultSerializer.Instance.DeserializeException(flowId, storedException); - public byte[] SerializeResult(TResult result) - => DefaultSerializer.Instance.SerializeResult(result); - public TResult DeserializeResult(byte[] json) - => DefaultSerializer.Instance.DeserializeResult(json); - public SerializedMessage SerializeMessage(TEvent message) where TEvent : notnull { EventToSerialize.Add(message); @@ -87,15 +82,5 @@ public object DeserializeMessage(byte[] json, byte[] type) EventToDeserialize.Add(Tuple.Create(json.ToStringFromUtf8Bytes(), type.ToStringFromUtf8Bytes())); return DefaultSerializer.Instance.DeserializeMessage(json, type); } - - public byte[] SerializeEffectResult(TResult result) - => DefaultSerializer.Instance.SerializeEffectResult(result); - public TResult DeserializeEffectResult(byte[] json) - => DefaultSerializer.Instance.DeserializeEffectResult(json); - - public byte[] SerializeState(TState state) where TState : FlowState, new() - => DefaultSerializer.Instance.SerializeState(state); - public TState DeserializeState(byte[] json) where TState : FlowState, new() - => DefaultSerializer.Instance.DeserializeState(json); } } \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions.Tests/Messaging/TestTemplates/MessagesTests.cs b/Core/Cleipnir.ResilientFunctions.Tests/Messaging/TestTemplates/MessagesTests.cs index b481488b..f700991d 100644 --- a/Core/Cleipnir.ResilientFunctions.Tests/Messaging/TestTemplates/MessagesTests.cs +++ b/Core/Cleipnir.ResilientFunctions.Tests/Messaging/TestTemplates/MessagesTests.cs @@ -434,22 +434,17 @@ private class ExceptionThrowingEventSerializer : ISerializer public ExceptionThrowingEventSerializer(Type failDeserializationOnType) => _failDeserializationOnType = failDeserializationOnType; - public byte[] SerializeParameter(TParam parameter) - => DefaultSerializer.Instance.SerializeParameter(parameter); + public byte[] Serialize(T value) + => DefaultSerializer.Instance.Serialize(value); - public TParam DeserializeParameter(byte[] json) - => DefaultSerializer.Instance.DeserializeParameter(json); + public T Deserialize(byte[] json) + => DefaultSerializer.Instance.Deserialize(json); public StoredException SerializeException(FatalWorkflowException exception) => DefaultSerializer.Instance.SerializeException(exception); public FatalWorkflowException DeserializeException(FlowId flowId, StoredException storedException) => DefaultSerializer.Instance.DeserializeException(flowId, storedException); - - public byte[] SerializeResult(TResult result) - => DefaultSerializer.Instance.SerializeResult(result); - public TResult DeserializeResult(byte[] json) - => DefaultSerializer.Instance.DeserializeResult(json); - + public SerializedMessage SerializeMessage(TEvent message) where TEvent : notnull => DefaultSerializer.Instance.SerializeMessage(message); @@ -461,15 +456,5 @@ public object DeserializeMessage(byte[] json, byte[] type) return DefaultSerializer.Instance.DeserializeMessage(json, type); } - - public byte[] SerializeEffectResult(TResult result) - => DefaultSerializer.Instance.SerializeEffectResult(result); - public TResult DeserializeEffectResult(byte[] json) - => DefaultSerializer.Instance.DeserializeEffectResult(json); - - public byte[] SerializeState(TState state) where TState : FlowState, new() - => DefaultSerializer.Instance.SerializeState(state); - public TState DeserializeState(byte[] json) where TState : FlowState, new() - => DefaultSerializer.Instance.DeserializeState(json); } } \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/RFunctionTests/ControlPanelTests.cs b/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/RFunctionTests/ControlPanelTests.cs index d7183ff1..c5bfd856 100644 --- a/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/RFunctionTests/ControlPanelTests.cs +++ b/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/RFunctionTests/ControlPanelTests.cs @@ -376,7 +376,7 @@ protected async Task SucceedingExistingFunctionFromControlPanelSucceeds(Task(sf.Result!); + var result = DefaultSerializer.Instance.Deserialize(sf.Result!); result.ShouldBe("hello world"); unhandledExceptionCatcher.ShouldNotHaveExceptions(); diff --git a/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/StoreTests.cs b/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/StoreTests.cs index a4eec6f2..a826ac67 100644 --- a/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/StoreTests.cs +++ b/Core/Cleipnir.ResilientFunctions.Tests/TestTemplates/StoreTests.cs @@ -373,7 +373,7 @@ await store.PostponeFunction( sf.Epoch.ShouldBe(0); sf.Status.ShouldBe(Status.Executing); DefaultSerializer.Instance - .DeserializeParameter(sf.Parameter!) + .Deserialize(sf.Parameter!) .ShouldBe(PARAM); } diff --git a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Invocation/InvocationHelper.cs b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Invocation/InvocationHelper.cs index 33da80c9..e70bee49 100644 --- a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Invocation/InvocationHelper.cs +++ b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Invocation/InvocationHelper.cs @@ -96,7 +96,7 @@ public async Task WaitForFunctionResult(FlowId flowId, StoredId storedI return storedFunction.Result == null ? default! - : _settings.Serializer.DeserializeResult(storedFunction.Result); + : _settings.Serializer.Deserialize(storedFunction.Result); case Status.Failed: throw Serializer.DeserializeException(flowId, storedFunction.Exception!); case Status.Postponed: @@ -274,7 +274,7 @@ public async Task PrepareForReInvocation(StoredId storedId { var param = sf.Parameter == null ? default - : Serializer.DeserializeParameter(sf.Parameter); + : Serializer.Deserialize(sf.Parameter); return new PreparedReInvocation(flowId, param, sf.Epoch, runningFunction, sf.ParentId); } @@ -370,10 +370,10 @@ public async Task Interrupt(IReadOnlyList storedIds) Param: sf.Parameter == null ? default - : serializer.DeserializeParameter(sf.Parameter), + : serializer.Deserialize(sf.Parameter), Result: sf.Result == null ? default - : serializer.DeserializeResult(sf.Result), + : serializer.Deserialize(sf.Result), FatalWorkflowException: sf.Exception == null ? null : serializer.DeserializeException(flowId, sf.Exception) @@ -400,7 +400,7 @@ await _functionStore.BulkScheduleFunctions( new IdWithParam( new StoredId(_storedType, bw.Instance.ToStoredInstance()), bw.Instance, - _isParamlessFunction ? null : serializer.SerializeParameter(bw.Param) + _isParamlessFunction ? null : serializer.Serialize(bw.Param) ) ), parent?.StoredId @@ -487,7 +487,7 @@ public DistributedSemaphores CreateSemaphores(StoredId storedId, Effect effect) return param is null ? null - : Serializer.SerializeParameter(param); + : Serializer.Serialize(param); } private byte[]? SerializeResult(TReturn? result) @@ -497,7 +497,7 @@ public DistributedSemaphores CreateSemaphores(StoredId storedId, Effect effect) return result is null ? null - : Serializer.SerializeResult(result); + : Serializer.Serialize(result); } public InnerScheduled CreateInnerScheduled(List scheduledIds, Workflow? parentWorkflow, bool? detach) @@ -543,7 +543,7 @@ public InnerScheduled CreateInnerScheduled(List scheduledIds, W FlowId = fc.Id, Result = fc.Result == null ? default! - : serializer.DeserializeResult(fc.Result) + : serializer.Deserialize(fc.Result) } ).ToDictionary(a => a.FlowId, a => a.Result); diff --git a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/CustomSerializableDecorator.cs b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/CustomSerializableDecorator.cs new file mode 100644 index 00000000..0bb57cdc --- /dev/null +++ b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/CustomSerializableDecorator.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using Cleipnir.ResilientFunctions.Domain; +using Cleipnir.ResilientFunctions.Helpers; +using Cleipnir.ResilientFunctions.Storage; + +namespace Cleipnir.ResilientFunctions.CoreRuntime.Serialization; +/* +public class CustomSerializableDecorator : ISerializer +{ + private readonly ISerializer _inner; + private readonly Dictionary> _deserializers = new(); + private readonly Lock _lock = new(); + + public CustomSerializableDecorator(ISerializer inner) => _inner = inner; + + public byte[] SerializeParameter(TParam parameter) + => parameter is ICustomSerializable customSerializable + ? customSerializable.Serialize(this) + : _inner.SerializeParameter(parameter); + + public TParam DeserializeParameter(byte[] json) + { + return typeof(TParam).IsAssignableTo(typeof(ICustomSerializable)) + ? CustomDeserialize(json) + : _inner.DeserializeParameter(json); + } + + public StoredException SerializeException(FatalWorkflowException exception) + => _inner.SerializeException(exception); + + public FatalWorkflowException DeserializeException(FlowId flowId, StoredException storedException) + => _inner.DeserializeException(flowId, storedException); + + public byte[] SerializeResult(TResult result) + => result is ICustomSerializable customSerializable + ? customSerializable.Serialize(this) + : _inner.SerializeResult(result); + + public TResult DeserializeResult(byte[] json) + { + return typeof(TResult).IsAssignableTo(typeof(ICustomSerializable)) + ? CustomDeserialize(json) + : _inner.DeserializeParameter(json); + } + + public SerializedMessage SerializeMessage(TEvent message) where TEvent : notnull + => _inner.SerializeMessage(message); //todo allow custom serializer + public object DeserializeMessage(byte[] json, byte[] type) => _inner.DeserializeMessage(json, type); + + public byte[] SerializeEffectResult(TResult result) + => result is ICustomSerializable customSerializable + ? customSerializable.Serialize(this) + : _inner.SerializeEffectResult(result); + + public TResult DeserializeEffectResult(byte[] json) + { + return typeof(TResult).IsAssignableTo(typeof(ICustomSerializable)) + ? CustomDeserialize(json) + : _inner.DeserializeEffectResult(json); + } + + public byte[] SerializeState(TState state) where TState : FlowState, new() + => _inner.SerializeState(state); + public TState DeserializeState(byte[] json) where TState : FlowState, new() + => _inner.DeserializeState(json); + + private T CustomDeserialize(byte[] bytes) + { + lock (_lock) + { + if (!_deserializers.ContainsKey(typeof(T))) + { + //var serializeMethodInfo = typeof(T).GetMethod(nameof(ICustomSerializable.Serialize), BindingFlags.Public | BindingFlags.Static); + //var serializeFunc = (Func) Delegate.CreateDelegate(typeof(Func), serializeMethodInfo!); + var deserializeMethodInfo = typeof(T).GetMethod(nameof(ICustomSerializable.Deserialize), BindingFlags.Public | BindingFlags.Static); + var deserializeFunc = (Func) Delegate.CreateDelegate(typeof(Func), deserializeMethodInfo!); + _deserializers[typeof(T)] = deserializeFunc; + } + + return (T) _deserializers[typeof(T)](bytes, this); + } + } +}*/ \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/DefaultSerializer.cs b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/DefaultSerializer.cs index 624ce791..10a4026b 100644 --- a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/DefaultSerializer.cs +++ b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/DefaultSerializer.cs @@ -11,12 +11,10 @@ public class DefaultSerializer : ISerializer { public static readonly DefaultSerializer Instance = new(); private DefaultSerializer() {} - - public byte[] SerializeParameter(TParam parameter) - => JsonSerializer.SerializeToUtf8Bytes(parameter); - public TParam DeserializeParameter(byte[] json) - => JsonSerializer.Deserialize(json)!; - + + public byte[] Serialize(T value) => JsonSerializer.SerializeToUtf8Bytes(value); + public T Deserialize(byte[] bytes) => JsonSerializer.Deserialize(bytes)!; + public StoredException SerializeException(FatalWorkflowException exception) => new( exception.FlowErrorMessage, @@ -27,24 +25,8 @@ public StoredException SerializeException(FatalWorkflowException exception) public FatalWorkflowException DeserializeException(FlowId flowId, StoredException storedException) => FatalWorkflowException.Create(flowId, storedException); - public byte[] SerializeResult(TResult result) - => JsonSerializer.SerializeToUtf8Bytes(result); - public TResult DeserializeResult(byte[] json) - => JsonSerializer.Deserialize(json)!; - public SerializedMessage SerializeMessage(TEvent message) where TEvent : notnull => new(JsonSerializer.SerializeToUtf8Bytes(message, message.GetType()), message.GetType().SimpleQualifiedName().ToUtf8Bytes()); - public object DeserializeMessage(byte[] json, byte[] type) => JsonSerializer.Deserialize(json, Type.GetType(Encoding.UTF8.GetString(type), throwOnError: true)!)!; - - public byte[] SerializeEffectResult(TResult result) - => JsonSerializer.SerializeToUtf8Bytes(result); - public TResult DeserializeEffectResult(byte[] json) - => JsonSerializer.Deserialize(json)!; - - public byte[] SerializeState(TState state) where TState : FlowState, new() - => JsonSerializer.SerializeToUtf8Bytes(state); - public TState DeserializeState(byte[] json) where TState : FlowState, new() - => JsonSerializer.Deserialize(json)!; } \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ErrorHandlingDecorator.cs b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ErrorHandlingDecorator.cs index 1903bbe7..57ead6c1 100644 --- a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ErrorHandlingDecorator.cs +++ b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ErrorHandlingDecorator.cs @@ -11,17 +11,13 @@ public class ErrorHandlingDecorator : ISerializer public ErrorHandlingDecorator(ISerializer inner) => _inner = inner; - public byte[] SerializeParameter(TParam parameter) - => _inner.SerializeParameter(parameter); - public TParam DeserializeParameter(byte[] json) + public byte[] Serialize(T value) => _inner.Serialize(value); + + public T Deserialize(byte[] bytes) { try { - return _inner.DeserializeParameter(json) - ?? throw new DeserializationException( - $"Deserialized parameter was null for type '{typeof(TParam).SimpleQualifiedName()}' and json: '{Convert.ToBase64String(json)}'", - new NullReferenceException() - ); + return _inner.Deserialize(bytes); } catch (DeserializationException) { @@ -30,11 +26,12 @@ public TParam DeserializeParameter(byte[] json) catch (Exception e) { throw new DeserializationException( - $"Unable to deserialize parameter with type: '{typeof(TParam).SimpleQualifiedName()}' and json: '{Convert.ToBase64String(json)}'", + $"Unable to deserialize value with type: '{typeof(T).SimpleQualifiedName()}' and bytes: '{Convert.ToBase64String(bytes)}'", e ); } } + public StoredException SerializeException(FatalWorkflowException exception) => _inner.SerializeException(exception); @@ -57,31 +54,6 @@ public FatalWorkflowException DeserializeException(FlowId flowId, StoredExceptio } } - public byte[] SerializeResult(TResult result) - => _inner.SerializeResult(result); - public TResult DeserializeResult(byte[] json) - { - try - { - return _inner.DeserializeResult(json) - ?? throw new DeserializationException( - $"Deserialized result was null with type: '{typeof(TResult).SimpleQualifiedName()}' and json: '{Convert.ToBase64String(json)}'", - new NullReferenceException() - ); - } - catch (DeserializationException) - { - throw; - } - catch (Exception e) - { - throw new DeserializationException( - $"Unable to deserialize result with type: '{typeof(TResult).SimpleQualifiedName()}' and json: '{Convert.ToBase64String(json)}'", - e - ); - } - } - public SerializedMessage SerializeMessage(TEvent message) where TEvent : notnull => _inner.SerializeMessage(message); public object DeserializeMessage(byte[] json, byte[] type) @@ -106,54 +78,4 @@ public object DeserializeMessage(byte[] json, byte[] type) ); } } - - public byte[] SerializeEffectResult(TResult result) - => _inner.SerializeEffectResult(result); - public TResult DeserializeEffectResult(byte[] json) - { - try - { - return _inner.DeserializeEffectResult(json) - ?? throw new DeserializationException( - $"Deserialized Effect's result was null with type: '{typeof(TResult)}' and json: '{Convert.ToBase64String(json)}'", - new NullReferenceException() - ); - } - catch (DeserializationException) - { - throw; - } - catch (Exception e) - { - throw new DeserializationException( - $"Unable to deserialize effect to type: '{typeof(TResult)}' and bytes: '{Convert.ToBase64String(json)}'", - e - ); - } - } - - public byte[] SerializeState(TState state) where TState : FlowState, new() - => _inner.SerializeState(state); - public TState DeserializeState(byte[] json) where TState : FlowState, new() - { - try - { - return _inner.DeserializeState(json) - ?? throw new DeserializationException( - $"Deserialized state was null with type: '{typeof(TState)}' and json: '{Convert.ToBase64String(json)}'", - new NullReferenceException() - ); - } - catch (DeserializationException) - { - throw; - } - catch (Exception e) - { - throw new DeserializationException( - $"Unable to deserialize state with type: '{typeof(TState)}' and json: '{Convert.ToBase64String(json)}'", - e - ); - } - } } \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ICustomSerializable.cs b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ICustomSerializable.cs new file mode 100644 index 00000000..14c51a5a --- /dev/null +++ b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ICustomSerializable.cs @@ -0,0 +1,7 @@ +namespace Cleipnir.ResilientFunctions.CoreRuntime.Serialization; + +public interface ICustomSerializable +{ + public byte[] Serialize(ISerializer serializer); + public static abstract object Deserialize(byte[] bytes, ISerializer serializer); +} \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ISerializer.cs b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ISerializer.cs index 78178e84..50885378 100644 --- a/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ISerializer.cs +++ b/Core/Cleipnir.ResilientFunctions/CoreRuntime/Serialization/ISerializer.cs @@ -5,16 +5,13 @@ namespace Cleipnir.ResilientFunctions.CoreRuntime.Serialization; public interface ISerializer { - byte[] SerializeParameter(TParam parameter); - TParam DeserializeParameter(byte[] json); + byte[] Serialize(T value); + T Deserialize(byte[] bytes); + StoredException SerializeException(FatalWorkflowException fatalWorkflowException); FatalWorkflowException DeserializeException(FlowId flowId, StoredException storedException); - byte[] SerializeResult(TResult result); - TResult DeserializeResult(byte[] json); + SerializedMessage SerializeMessage(TMessage message) where TMessage : notnull; object DeserializeMessage(byte[] json, byte[] type); - byte[] SerializeEffectResult(TResult result); - TResult DeserializeEffectResult(byte[] json); - byte[] SerializeState(TState state) where TState : FlowState, new(); - TState DeserializeState(byte[] json) where TState : FlowState, new(); + } \ No newline at end of file diff --git a/Core/Cleipnir.ResilientFunctions/Domain/Effect.cs b/Core/Cleipnir.ResilientFunctions/Domain/Effect.cs index 16a1dc39..077ba55d 100644 --- a/Core/Cleipnir.ResilientFunctions/Domain/Effect.cs +++ b/Core/Cleipnir.ResilientFunctions/Domain/Effect.cs @@ -93,13 +93,13 @@ internal async Task CreateOrGet(EffectId effectId, T value) lock (_sync) { if (effectResults.TryGetValue(effectId, out var existing) && existing.WorkStatus == WorkStatus.Completed) - return serializer.DeserializeEffectResult(existing.Result!); + return serializer.Deserialize(existing.Result!); if (existing?.StoredException != null) throw serializer.DeserializeException(flowId, existing.StoredException!); } - var storedEffect = StoredEffect.CreateCompleted(effectId, serializer.SerializeEffectResult(value)); + var storedEffect = StoredEffect.CreateCompleted(effectId, serializer.Serialize(value)); await effectsStore.SetEffectResult(storedId, storedEffect); lock (_sync) @@ -113,7 +113,7 @@ internal async Task Upsert(EffectId effectId, T value) { var effectResults = await GetEffectResults(); - var storedEffect = StoredEffect.CreateCompleted(effectId, serializer.SerializeEffectResult(value)); + var storedEffect = StoredEffect.CreateCompleted(effectId, serializer.Serialize(value)); await effectsStore.SetEffectResult(storedId, storedEffect); lock (_sync) @@ -131,7 +131,7 @@ internal async Task> TryGet(EffectId effectId) { if (storedEffect.WorkStatus == WorkStatus.Completed) { - var value = serializer.DeserializeEffectResult(storedEffect.Result!)!; + var value = serializer.Deserialize(storedEffect.Result!)!; return Option.Create(value); } @@ -257,7 +257,7 @@ private async Task InnerCapture(string id, EffectType effectType, Func(storedEffect.Result))!; + return (storedEffect.Result == null ? default : serializer.Deserialize(storedEffect.Result))!; if (success && storedEffect!.WorkStatus == WorkStatus.Failed) throw FatalWorkflowException.Create(flowId, storedEffect.StoredException!); if (success && resiliency == ResiliencyLevel.AtMostOnce) @@ -310,7 +310,7 @@ private async Task InnerCapture(string id, EffectType effectType, Func> AllIds return storedEffect.Result == null ? default - : serializer.DeserializeEffectResult(storedEffects[effectId].Result!); + : serializer.Deserialize(storedEffects[effectId].Result!); } public async Task GetResultBytes(string effectId) => await GetResultBytes(effectId.ToEffectId()); @@ -81,7 +81,7 @@ public Task SetSucceeded(EffectId effectId) public Task SetSucceeded(string effectId, TResult result) => SetSucceeded(effectId.ToEffectId(), result); public Task SetSucceeded(EffectId effectId, TResult result) - => Set(new StoredEffect(effectId, effectId.ToStoredEffectId(), WorkStatus.Completed, Result: serializer.SerializeEffectResult(result), StoredException: null)); + => Set(new StoredEffect(effectId, effectId.ToStoredEffectId(), WorkStatus.Completed, Result: serializer.Serialize(result), StoredException: null)); public Task SetFailed(string effectId, Exception exception) => SetFailed(effectId.ToEffectId(), exception); public Task SetFailed(EffectId effectId, Exception exception) diff --git a/Core/Cleipnir.ResilientFunctions/Domain/ExistingStates.cs b/Core/Cleipnir.ResilientFunctions/Domain/ExistingStates.cs index 8c6b871c..19c0cb95 100644 --- a/Core/Cleipnir.ResilientFunctions/Domain/ExistingStates.cs +++ b/Core/Cleipnir.ResilientFunctions/Domain/ExistingStates.cs @@ -47,7 +47,7 @@ private async Task> GetStoredStates() if (!storedStates.TryGetValue(stateId, out var storedState)) throw new KeyNotFoundException($"State '{stateId}' was not found"); - var state = _serializer.DeserializeState(storedState.StateJson); + var state = _serializer.Deserialize(storedState.StateJson); state.Initialize(onSave: () => Set(stateId, state)); return state; } @@ -66,7 +66,7 @@ public async Task Remove(string stateId) public async Task Set(string stateId, TState state) where TState : FlowState, new() { var storedStates = await GetStoredStates(); - var json = _serializer.SerializeState(state); + var json = _serializer.Serialize(state); var storedState = new StoredState(stateId, json); await _effectsStore.SetEffectResult(_storedId, StoredEffect.CreateState(storedState)); storedStates[stateId] = storedState; diff --git a/Core/Cleipnir.ResilientFunctions/Domain/StateFetcher.cs b/Core/Cleipnir.ResilientFunctions/Domain/StateFetcher.cs index f1cbbe35..43d837ad 100644 --- a/Core/Cleipnir.ResilientFunctions/Domain/StateFetcher.cs +++ b/Core/Cleipnir.ResilientFunctions/Domain/StateFetcher.cs @@ -19,7 +19,7 @@ public class StateFetcher(StoredType storedType, IEffectsStore effectsStore, ISe foreach (var storedEffect in storedStates) if (storedEffect.EffectId.Id == stateId.Value) { - var state = serializer.DeserializeState(storedEffect.Result!); + var state = serializer.Deserialize(storedEffect.Result!); state.Initialize( onSave: () => throw new InvalidOperationException("State cannot be modified outside of an executing flow - except when using control-panel") ); diff --git a/Core/Cleipnir.ResilientFunctions/Domain/States.cs b/Core/Cleipnir.ResilientFunctions/Domain/States.cs index 26f161c1..0f507c97 100644 --- a/Core/Cleipnir.ResilientFunctions/Domain/States.cs +++ b/Core/Cleipnir.ResilientFunctions/Domain/States.cs @@ -53,7 +53,7 @@ private async Task> GetExistingStoredStates() return (T)state; else if (existingStoredStates.TryGetValue(key: id, out var storedState)) { - var s = serializer.DeserializeState(storedState.StateJson); + var s = serializer.Deserialize(storedState.StateJson); _existingStates[id] = s; s.Initialize(onSave: () => SaveState(id, s)); return s; @@ -63,7 +63,7 @@ private async Task> GetExistingStoredStates() var newState = new T(); newState.Initialize(onSave: () => SaveState(id, newState)); _existingStates[id] = newState; - existingStoredStates[id] = new StoredState(id, serializer.SerializeState(newState)); + existingStoredStates[id] = new StoredState(id, serializer.Serialize(newState)); return newState; } } @@ -97,7 +97,7 @@ private async Task RemoveInner(string id) private async Task SaveState(string id, T state) where T : FlowState, new() { - var json = serializer.SerializeState(state); + var json = serializer.Serialize(state); var storedState = new StoredState(new StateId(id), json); var storedEffect = StoredEffect.CreateState(storedState); await effectStore.SetEffectResult(storedId, storedEffect);