Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,29 @@ public class saga_id_member_determination
[InlineData(typeof(SomeSagaMessage4), nameof(SomeSagaMessage4.Id))]
public void determine_the_member(Type messageType, string expectedMemberName)
{
SagaChain.DetermineSagaIdMember(messageType, typeof(SomeSaga)).Name
SagaChain.DetermineSagaIdMember(messageType, typeof(SomeSaga))?.Name
.ShouldBe(expectedMemberName);
}

[Fact]
public void member_is_determined_by_attribute()
{
var method = typeof(SomeSaga).GetMethod(nameof(SomeSaga.Handle));

SagaChain.DetermineSagaIdMember(typeof(SomeSagaMessage5), typeof(SomeSaga), method)
!.Name.ShouldBe(nameof(SomeSagaMessage5.Hello));
}
}

public record SomeSagaMessage1(Guid Id, [property: SagaIdentity]Guid RandomName);
public record SomeSagaMessage1(Guid Id, [property: SagaIdentity] Guid RandomName);
public record SomeSagaMessage2(Guid SagaId, Guid Id);
public record SomeSagaMessage3(Guid Id, Guid SomeSagaId, Guid SagaId);
public record SomeSagaMessage4(Guid Id);

public record SomeSagaMessage5(Guid Hello, Guid Id, Guid SagaId, Guid SomeSagaId);

public class SomeSaga
{
public Guid Id { get; set; }

public void Handle([SagaIdentityFrom(nameof(SomeSagaMessage5.Hello))] SomeSagaMessage5 message) { }
}
43 changes: 25 additions & 18 deletions src/Wolverine/Persistence/Sagas/SagaChain.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using System.Reflection;
using JasperFx;
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using JasperFx.Core;
using JasperFx.Core.Reflection;
using System.Reflection;
using Wolverine.Logging;
using Wolverine.Runtime;
using Wolverine.Runtime.Handlers;

namespace Wolverine.Persistence.Sagas;
Expand All @@ -29,8 +28,17 @@ public SagaChain(WolverineOptions options, IGrouping<Type, HandlerCall> grouping
{
try
{
SagaType = grouping.Where(x => x.HandlerType.CanBeCastTo<Saga>()).Select(x => x.HandlerType)
.Distinct().Single();
var saga = grouping.Where(x => x.HandlerType.CanBeCastTo<Saga>()).DistinctBy(x => x.HandlerType).Single();
SagaType = saga.HandlerType;
SagaMethodInfo = saga.Method;

SagaIdMember = DetermineSagaIdMember(MessageType, SagaType, saga.Method);

// Automatically audit the saga id
if (SagaIdMember != null && AuditedMembers.All(x => x.Member != SagaIdMember))
{
AuditedMembers.Add(new AuditedMember(SagaIdMember, SagaIdMember.Name, SagaIdMember.Name));
}
}
catch (Exception e)
{
Expand All @@ -41,14 +49,6 @@ public SagaChain(WolverineOptions options, IGrouping<Type, HandlerCall> grouping
$"Command types cannot be handled by multiple saga types. Message {MessageType.FullNameInCode()} is handled by sagas {handlerTypes}",
e);
}

SagaIdMember = DetermineSagaIdMember(MessageType, SagaType);

// Automatically audit the saga id
if (SagaIdMember != null && AuditedMembers.All(x => x.Member != SagaIdMember))
{
AuditedMembers.Add(new AuditedMember(SagaIdMember, SagaIdMember.Name, SagaIdMember.Name));
}
}

public override bool TryInferMessageIdentity(out PropertyInfo? property)
Expand All @@ -69,6 +69,8 @@ protected override void tryAssignStickyEndpoints(HandlerCall handlerCall, Wolver

public Type SagaType { get; }

public MethodInfo? SagaMethodInfo { get; set; }

public MemberInfo? SagaIdMember { get; set; }

public MethodCall[] ExistingCalls { get; set; } = [];
Expand All @@ -77,13 +79,18 @@ protected override void tryAssignStickyEndpoints(HandlerCall handlerCall, Wolver

public MethodCall[] NotFoundCalls { get; set; } = [];

public static MemberInfo? DetermineSagaIdMember(Type messageType, Type sagaType)
public static MemberInfo? DetermineSagaIdMember(Type messageType, Type sagaType, MethodInfo? sagaHandlerMethod = null)
{
var expectedSagaIdName = $"{sagaType.Name}Id";

var specifiedSagaIdMemberName = sagaHandlerMethod?.GetParameters()
.Select(x => x.GetCustomAttribute<SagaIdentityFromAttribute>())
.FirstOrDefault(a => a != null)?.PropertyName;

var members = messageType.GetFields().OfType<MemberInfo>().Concat(messageType.GetProperties()).ToArray();
return members.FirstOrDefault(x => x.HasAttribute<SagaIdentityAttribute>())
?? members.FirstOrDefault(x => x.Name == expectedSagaIdName)
?? members.FirstOrDefault(x => x.Name == (specifiedSagaIdMemberName ?? expectedSagaIdName))
?? members.FirstOrDefault(x => x.Name == expectedSagaIdName.Replace("Saga", "", StringComparison.InvariantCultureIgnoreCase))
?? members.FirstOrDefault(x => x.Name == SagaIdMemberName) ??
members.FirstOrDefault(x => x.Name.EqualsIgnoreCase("Id"));
}
Expand All @@ -97,14 +104,14 @@ internal override List<Frame> DetermineFrames(GenerationRules rules, IServiceCon
MessageVariable messageVariable)
{
applyCustomizations(rules, container);

if (AuditedMembers.Count != 0)
{
Middleware.Insert(0, new AuditToActivityFrame(this));
}

var frameProvider = rules.GetPersistenceProviders(this, container);

frameProvider.ApplyTransactionSupport(this, container);

NotFoundCalls = findByNames(NotFound);
Expand Down Expand Up @@ -133,7 +140,7 @@ internal override List<Frame> DetermineFrames(GenerationRules rules, IServiceCon
generateCodeForMaybeExisting(container, frameProvider, list);
}

// .Concat(handlerReturnValueFrames)
// .Concat(handlerReturnValueFrames)

return Middleware.Concat(container.TryCreateConstructorFrames(Handlers)).Concat(list).Concat(Postprocessors).ToList();
}
Expand Down Expand Up @@ -185,7 +192,7 @@ private void generateForOnlyStartingSaga(IServiceContainer container, IPersisten
{
return;
}

var ifNotCompleted = buildFrameForConditionalInsert(sagaVariable, frameProvider, container);
frames.Add(ifNotCompleted);
}
Expand Down
10 changes: 10 additions & 0 deletions src/Wolverine/Persistence/Sagas/SagaIdentityFromAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Wolverine.Persistence.Sagas;

/// <summary>
/// Marks a public property on a message type handler parameter as the saga state identity
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class SagaIdentityFromAttribute(string propertyName) : Attribute
{
public string PropertyName { get => propertyName; }
}
Loading