using ResultBoxes; namespace OpenAttendanceManagement.Common.EventSourcing; public interface IAggregatePayload; public record EmptyAggregatePayload : IAggregatePayload; public record Aggregate(Guid AggregateId, int Version, TAggregatePayload Payload) where TAggregatePayload : IAggregatePayload; public interface IAggregateProjector { public static abstract Func Projector(); } public interface IEventPayload; public interface IEvent { public IEventPayload GetPayload(); } public record Event(Guid AggregateId, int Version, TPayload Payload) : IEvent where TPayload : IEventPayload { public IEventPayload GetPayload() => Payload; } public record UnconfirmedUser(string Name, string Email) : IAggregatePayload; public record ConfirmedUser(string Name, string Email) : IAggregatePayload; public record UserRegistered(string Name, string Email) : IEventPayload; public record UserConfirmed : IEventPayload; public record UserUnconfirmed : IEventPayload; public class UserProjector : IAggregateProjector { public static Func Projector() => (payload, @event) => payload switch { EmptyAggregatePayload => @event.GetPayload() switch { UserRegistered registered => new UnconfirmedUser(registered.Name, registered.Email), _ => payload }, UnconfirmedUser unconfirmedUser => @event.GetPayload() switch { UserConfirmed => new ConfirmedUser(unconfirmedUser.Name, unconfirmedUser.Email), _ => payload }, ConfirmedUser confirmedUser => @event.GetPayload() switch { UserUnconfirmed => new UnconfirmedUser(confirmedUser.Name, confirmedUser.Email), _ => payload } }; } public record Branch(string Name) : IAggregatePayload; public record BranchCreated(string Name) : IEventPayload; public class BranchProjector : IAggregateProjector { public static Func Projector() => (payload, @event) => payload switch { EmptyAggregatePayload => @event.GetPayload() switch { BranchCreated created => new Branch(created.Name), _ => payload }, _ => payload }; } public record PartitionKeys(Guid AggregateId, string Group, string RootPartitionKey) { public static PartitionKeys Generate(string group = "default", string rootPartitionKey = "default") => new(Guid.NewGuid(), group, rootPartitionKey); public static PartitionKeys Existing( Guid aggregateId, string group = "default", string rootPartitionKey = "default") => new(aggregateId, group, rootPartitionKey); } public record EventOrNone(IEventPayload? EventPayload, bool HasEvent) { public static EventOrNone Empty => new(default, false); public static ResultBox None => Empty; public static EventOrNone FromValue(IEventPayload value) => new(value, true); public static ResultBox Event(IEventPayload value) => ResultBox.FromValue(FromValue(value)); public IEventPayload GetValue() => HasEvent && EventPayload is not null ? EventPayload : throw new ResultsInvalidOperationException("no value"); public static implicit operator EventOrNone(UnitValue value) => Empty; } public interface IPureCommandCommon where TCommand : IPureCommandCommon, IEquatable where TProjector : IAggregateProjector where TAggregatePayload : IAggregatePayload; public interface IPureCommand : IPureCommandCommon where TCommand : IPureCommand, IEquatable where TProjector : IAggregateProjector where TAggregatePayload : IAggregatePayload { public static abstract PartitionKeys SpecifyPartitionKeys(TCommand input); public static abstract ResultBox Handle(TCommand input, Func> stateFunc); } public interface IPureCommandWithInjection : IPureCommandCommon where TCommand : IPureCommandWithInjection, IEquatable where TProjector : IAggregateProjector where TAggregatePayload : IAggregatePayload { public static abstract PartitionKeys SpecifyPartitionKeys(TCommand input); public static abstract ResultBox Handle( TCommand input, Func> stateFunc, TInjections injections); } public record RegisterClient1(string Name, string Email) : IPureCommand { public static PartitionKeys SpecifyPartitionKeys(RegisterClient1 input) => PartitionKeys.Generate(); public static ResultBox Handle( RegisterClient1 input, Func> stateFunc) => EventOrNone.Event(new UserRegistered(input.Name, input.Email)); } public record RegisterClientWithInjection(string Name, string Email) : IPureCommandWithInjection { public static PartitionKeys SpecifyPartitionKeys(RegisterClientWithInjection input) => PartitionKeys.Generate(); public static ResultBox Handle( RegisterClientWithInjection input, Func> stateFunc, Injections injections) => injections.EmailAlreadyExists(input.Email) ? EventOrNone.None : EventOrNone.Event(new UserRegistered(input.Name, input.Email)); public class Injections { public Func EmailAlreadyExists { get; init; } } } public record RegisterBranch(string Name) : IPureCommand { public static PartitionKeys SpecifyPartitionKeys(RegisterBranch input) => PartitionKeys.Generate(); public static ResultBox Handle( RegisterBranch input, Func> stateFunc) => EventOrNone.Event(new BranchCreated(input.Name)); } public interface ISekibanUsecase where TIn : class, ISekibanUsecase, IEquatable where TOut : notnull { public static abstract ResultBox Execute(TIn input); } public interface ISekibanUsecaseWithInjection where TIn : class, ISekibanUsecaseWithInjection, IEquatable where TOut : notnull { public static abstract Task> Execute(TIn input, TInjection injection); } public record CommandResponse(Guid AggregateId); public record RegisterBranchAndClient(string BranchName, string ClientName, string ClientEmail) : ISekibanUsecaseWithInjection { public static Task> Execute(RegisterBranchAndClient input, Injections injection) => ResultBox .FromValue(new RegisterBranch(input.BranchName)) .Conveyor(injection.RegisterBranch) .Remap(result => new RegisterClientWithInjection(input.ClientName, input.ClientEmail)) .Combine( _ => ResultBox.FromValue( new RegisterClientWithInjection.Injections { EmailAlreadyExists = injection.EmailAlreadyExists })) .Conveyor(injection.RegisterClient) .Conveyor(_ => ResultBox.FromValue(true)); public class Injections { public Func>> RegisterBranch { get; init; } public Func>> RegisterClient { get; init; } public Func EmailAlreadyExists { get; init; } } }