pjvds / ncqrs

Ncqrs Framework - The CQRS Framework for .NET
Other
541 stars 162 forks source link

I am working on a CQRS library based heavily on NCQRS that supports .NET 7, AOT, and Source Code Generators #111

Open kadamgreene opened 1 year ago

kadamgreene commented 1 year ago

I realize that NCQRS has not been updated in almost 7 years. So I decided to take it upon myself to re-engineer it. I am making it async by default, moving it to .NET 7, and enabling Source Code Generation so that it works with AOT (It already can process a command, trigger events, and call Event Handlers as native code in AOT, all in 5MB). I'm also working on Roslyn Code fixers / Code Analysis to enforce best practices (and the things that need to be done a certain way to work with Source Code Generation). It is signficantly faster as it completely avoids reflection in major parts of the software (i.e. each Command mapped via Mapping attributes, actually generates a new CommandExecutor that deals with the concrete classes, methods, and properties). I'm a long ways from done, but being able to process commands to event handlers is pretty good 😄 Here is the example output of the source code generator.

This:

[Handler<NameHandler>]
    [Handler<NameHandler2>]
    [Handler<AddressHandler>]
    [Handler<TargetCreatedHandler>]
    [Command<SetName, SetNameExecutor>]
    [Command<CorrectlyMappedCommand>] // Create
    [Command<UpdateFoo>] // Method call
    internal partial class MyBusTypeHandler : GeneratedTypeManager
    {
    }

turns into this at build time

 internal partial class MyBusTypeHandler : GeneratedTypeManager {
    private List<AggregateRoot> aggregateRoots = new ();
        public MyBusTypeHandler() {
            var handler0 = Create<Example.NameHandler>();
            var handler1 = Create<Example.NameHandler2>();
            AddHandlers<Example.Name>(new List<Func<PublishedEvent, Task>>
            {
                (evt) => { handler0.Handle((IPublishedEvent<Example.Name>)evt); return Task.CompletedTask; }, 
                (evt) => handler1.HandleAsync((IPublishedEvent<Example.Name>)evt)
            });
            var handler2 = Create<Example.AddressHandler>();
            AddHandlers<Example.Address>(new List<Func<PublishedEvent, Task>>
            {
                (evt) => { handler2.Handle((IPublishedEvent<Example.Address>)evt); return Task.CompletedTask; }
            });
            var handler5 = Create<Example.TargetCreatedHandler>();
            AddHandlers<Example.TargetCreated>(new List<Func<PublishedEvent, Task>>
            {
                (evt) => { handler5.Handle((IPublishedEvent<Example.TargetCreated>)evt); return Task.CompletedTask; }
            });
            var executor0 = Create<Example.SetNameExecutor>();
            AddCommandExecutor(typeof(Example.SetName), (cmd) => executor0.Execute((Example.SetName)cmd));
            var executor1 = MapperToCommandExecutor<CorrectlyMappedCommandMapper>();
            AddCommandExecutor(typeof(Example.CorrectlyMappedCommand), (cmd) => executor1.Execute((Example.CorrectlyMappedCommand)cmd));
            var executor2 = MapperToCommandExecutor<UpdateFooMapper>();
            AddCommandExecutor(typeof(Example.UpdateFoo), (cmd) => executor2.Execute((Example.UpdateFoo)cmd));
            AddAggregateRoot<Example.TargetAggRoot>(); // Is discovered from the command mappings
        }
        private Dictionary<Type, Func<IPublishableEvent, PublishedEvent>> publishers = new Dictionary<Type, Func<IPublishableEvent, PublishedEvent>> {
            [typeof(Example.Name)] = evt => new PublishedEvent<Example.Name>(evt),
            [typeof(Example.Address)] = evt => new PublishedEvent<Example.Address>(evt),
            [typeof(Example.TargetCreated)] = evt => new PublishedEvent<Example.TargetCreated>(evt)
        };
        public override PublishedEvent GetPublishedEvent(Type evtType, IPublishableEvent evt)
        {
            return publishers[evtType](evt);
        }
    }

    internal class CorrectlyMappedCommandMapper : AbstractAggregateMapper {
        public override async Task Map(ICommand command, IMappedCommandExecutor executor) {
           var commandType = typeof(Example.CorrectlyMappedCommand);
            AggregateRoot Create(ICommand c) {
                var actualCommand = c as Example.CorrectlyMappedCommand;
                return new Example.TargetAggRoot(actualCommand.Id, actualCommand.Foo, actualCommand.Bar);
           }
           var executorAction = () => {
               executor.ExecuteActionCreatingNewInstance(Create);
               return Task.CompletedTask;
           };
           await WrapInTransaction(commandType, executorAction);
        }
     }
    internal class UpdateFooMapper : AbstractAggregateMapper {
        public override async Task Map(ICommand command, IMappedCommandExecutor executor) {
           var commandType = typeof(Example.UpdateFoo);
           void Run(AggregateRoot agg, ICommand c) {
                var actualCommand = c as Example.UpdateFoo;
                var actualAggregate = agg as Example.TargetAggRoot;
                actualAggregate.UpdateFoo(actualCommand.Foo);
           }
           Guid GetAggregateRootId(ICommand cmd) { return (cmd as Example.UpdateFoo).AggregateRootId; }
           var executorAction = () =>
           {
               executor.ExecuteActionOnExistingInstance(GetAggregateRootId, (cmd) => typeof(Example.TargetAggRoot), Run);
               return Task.CompletedTask;
           };
           await WrapInTransaction(commandType, executorAction);
        }
     }
}