Skip to content

Commit

Permalink
Moved most of the projection codegen work to JasperFx
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremydmiller committed Nov 12, 2024
1 parent 61177ca commit adf72ac
Show file tree
Hide file tree
Showing 21 changed files with 1,419 additions and 20 deletions.
57 changes: 57 additions & 0 deletions src/JasperFx/Events/CodeGeneration/AggregateConstructorFrame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using JasperFx.Core.Reflection;

namespace JasperFx.Events.CodeGeneration;

/// <summary>
/// Calls the aggregate's single argument constructor for a specific
/// event type
/// </summary>
public class AggregateConstructorFrame: SyncFrame, IEventHandlingFrame
{
private readonly Type _argType;
private readonly MethodSlot _slot;
private Variable _aggregate;
private Variable _arg;

public AggregateConstructorFrame(MethodSlot slot)
{
_slot = slot;
_argType = slot.Method.GetParameters().Single().ParameterType;
}

public void Configure(EventProcessingFrame parent)
{
_arg = parent.SpecificEvent.VariableType == _argType ? parent.SpecificEvent : parent.DataOnly;
_aggregate = parent.Aggregate;
}

public Type EventType => _slot.EventType;

public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
{
if (_aggregate != null)
{
yield return _aggregate;
}

yield return _arg;
}

public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
if (_aggregate == null)
{
writer.WriteLine($"return new {_slot.ReturnType.FullNameInCode()}({_arg.Usage});");
}
else
{
writer.WriteLine($"{_aggregate.Usage} ??= new {_slot.ReturnType.FullNameInCode()}({_arg.Usage});");
}


Next?.GenerateCode(method, writer);
}
}
109 changes: 109 additions & 0 deletions src/JasperFx/Events/CodeGeneration/AggregateEventProcessingFrame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.Core.Reflection;

namespace JasperFx.Events.CodeGeneration;

public class AggregateEventProcessingFrame: EventProcessingFrame
{
private ApplyMethodCall _apply;

private Frame _creation;
private ShouldDeleteFrame _deletion;

public AggregateEventProcessingFrame(Type aggregateType, Type eventType): base(true, aggregateType, eventType)
{
}

public bool AlwaysDeletes { get; set; }

public Frame CreationFrame
{
get => _creation;
set
{
if (value is not IEventHandlingFrame)
{
throw new ArgumentOutOfRangeException(
$"The CreationFrame must implement {nameof(IEventHandlingFrame)}");
}

_inner.Add(value);
_creation = value;
}
}

public ShouldDeleteFrame Deletion
{
get => _deletion;
set
{
_deletion = value;
_inner.Add(value);
}
}

public ApplyMethodCall Apply
{
get => _apply;
set
{
_apply = value;
_inner.Add(value);
}
}

public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
writer.Write($"case {SpecificEvent.VariableType.FullNameInCode()} {SpecificEvent.Usage}:");

writer.IndentionLevel++;


if (AlwaysDeletes)
{
writer.Write("return null;");
}

if (Apply == null)
{
if (CreationFrame != null)
{
CreationFrame.GenerateCode(method, writer);
}
else
{
writer.Write($"{Aggregate.Usage} ??= CreateDefault(evt);");
}
}
else if (CreationFrame != null)
{
writer.Write($"BLOCK:if ({Aggregate.Usage} == null)");

CreationFrame.GenerateCode(method, writer);

writer.FinishBlock();
writer.WriteElse();
Apply.GenerateCode(method, writer);
writer.FinishBlock();
}
else // Have an Apply() method and no Create()
{
writer.Write($"{Aggregate.Usage} ??= CreateDefault(evt);");
Apply.GenerateCode(method, writer);
}

if (Deletion != null)
{
writer.Write($"if ({Aggregate.Usage} == null) return null;");

Deletion.GenerateCode(method, writer);
}

writer.Write($"return {Aggregate.Usage};");

writer.IndentionLevel--;

Next?.GenerateCode(method, writer);
}
}
37 changes: 37 additions & 0 deletions src/JasperFx/Events/CodeGeneration/ApplyMethodCall.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Reflection;
using JasperFx.CodeGeneration.Frames;

namespace JasperFx.Events.CodeGeneration;

public class ApplyMethodCall: MethodCall, IEventHandlingFrame
{
public ApplyMethodCall(Type handlerType, string methodName, Type aggregateType): base(handlerType, methodName)
{
EventType = Method.GetEventType(aggregateType);
}

public ApplyMethodCall(MethodSlot slot): base(slot.HandlerType, (MethodInfo)slot.Method)
{
EventType = slot.EventType;
if (slot.Setter != null)
{
Target = slot.Setter;
}
}

public Type EventType { get; }

public void Configure(EventProcessingFrame parent)
{
// Replace any arguments to IEvent<T>
TrySetArgument(parent.SpecificEvent);

// Replace any arguments to the specific T event type
TrySetArgument(parent.DataOnly);

if (ReturnType == parent.AggregateType)
{
AssignResultTo(parent.Aggregate);
}
}
}
68 changes: 68 additions & 0 deletions src/JasperFx/Events/CodeGeneration/ApplyMethodCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using JasperFx.Core.Reflection;

namespace JasperFx.Events.CodeGeneration;

public class ApplyMethodCollection: MethodCollection
{
public static readonly string MethodName = "Apply";

public ApplyMethodCollection(Type projectionType, Type aggregateType, Type querySessionType): base(MethodName, projectionType,
aggregateType)
{
QuerySessionType = querySessionType;
LambdaName = "ProjectEvent";
_validArgumentTypes.Add(querySessionType);
_validArgumentTypes.Add(aggregateType);

_validReturnTypes.Add(typeof(Task));
_validReturnTypes.Add(typeof(void));
_validReturnTypes.Add(aggregateType);
_validReturnTypes.Add(typeof(Task<>).MakeGenericType(aggregateType));
}

public Type QuerySessionType { get; }

protected override void validateMethod(MethodSlot method)
{
if (!method.DeclaredByAggregate && method.Method.GetParameters().All(x => x.ParameterType != AggregateType))
{
method.AddError($"Aggregate type '{AggregateType.FullNameInCode()}' is required as a parameter");
}
}

public override IEventHandlingFrame CreateEventTypeHandler(Type aggregateType,
IStorageMapping aggregateMapping, MethodSlot slot)
{
return new ApplyMethodCall(slot);
}

public void BuildApplyMethod(GeneratedType generatedType, IStorageMapping aggregateMapping)
{
var returnType = IsAsync
? typeof(ValueTask<>).MakeGenericType(AggregateType)
: AggregateType;

var args = new[]
{
new Argument(typeof(IEvent), "@event"), new Argument(AggregateType, "aggregate"),
new Argument(QuerySessionType, "session")
};

if (IsAsync)
{
args = args.Concat(new[] { new Argument(typeof(CancellationToken), "cancellation") }).ToArray();
}

var method = new GeneratedMethod(MethodName, returnType, args);
generatedType.AddMethod(method);

var eventHandling = AddEventHandling(AggregateType, aggregateMapping, this);
method.Frames.Add(eventHandling);


method.Frames.Code("return {0};", new Use(AggregateType));
}
}
78 changes: 78 additions & 0 deletions src/JasperFx/Events/CodeGeneration/CallApplyAggregateFrame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using JasperFx.Core.Reflection;

namespace JasperFx.Events.CodeGeneration;

public class CallApplyAggregateFrame: Frame
{
private readonly Type _fullSessionType;
private Variable _aggregate;
private Variable _cancellation;
private Variable _session;
private Variable _usedEventOnCreate;
private readonly Type _querySessionType;

/// <summary>
///
/// </summary>
/// <param name="methods"></param>
/// <param name="fullSessionType">"Fuller" session type just in case we need that</param>
public CallApplyAggregateFrame(ApplyMethodCollection methods, Type fullSessionType): base(methods.IsAsync)
{
_fullSessionType = fullSessionType;
AggregateType = methods.AggregateType;
_querySessionType = methods.QuerySessionType;
}

public Type AggregateType { get; }
public bool InsideForEach { get; set; }

public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
{
_aggregate = chain.FindVariable(AggregateType);

_session = chain.TryFindVariable(_querySessionType, VariableSource.All) ??
chain.FindVariable(_fullSessionType);

_usedEventOnCreate = chain.FindVariableByName(typeof(bool), CallCreateAggregateFrame.UsedEventOnCreateName);

yield return _session;

if (IsAsync)
{
_cancellation = chain.TryFindVariable(typeof(CancellationToken), VariableSource.All) ??
new Variable(typeof(CancellationToken),
$"{typeof(CancellationToken).FullNameInCode()}.None");

yield return _cancellation;
}
}

public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
if (InsideForEach)
{
writer.Write($"BLOCK:foreach (var @event in events.Skip({_usedEventOnCreate.Usage} ? 1 : 0))");
}

if (IsAsync)
{
writer.WriteLine(
$"{_aggregate.Usage} = await {ApplyMethodCollection.MethodName}(@event, {_aggregate.Usage}, {_session.Usage}, {_cancellation.Usage});");
}
else
{
writer.WriteLine(
$"{_aggregate.Usage} = {ApplyMethodCollection.MethodName}(@event, {_aggregate.Usage}, {_session.Usage});");
}

if (InsideForEach)
{
writer.FinishBlock();
}

Next?.GenerateCode(method, writer);
}
}
Loading

0 comments on commit adf72ac

Please sign in to comment.