kgrzybek / sample-dotnet-core-cqrs-api

Sample .NET Core REST API CQRS implementation with raw SQL and DDD using Clean Architecture.
https://www.kamilgrzybek.com/design/simple-cqrs-implementation-with-raw-sql-and-ddd/
MIT License
2.85k stars 640 forks source link

Should we persist this to the database? #16

Open ghost opened 4 years ago

ghost commented 4 years ago

Hey Kamil, First thanks for the great example! I'm learning a lot from it! While creating a new order I can't see where those are persisted on the database and consequently can't see any processing of outbox item. Is it the case that we should persist the order on the db after the line bellow?

https://github.com/kgrzybek/sample-dotnet-core-cqrs-api/blob/01a1d6517bc88773f004abc0cb9c6d79f537e575/src/SampleProject.Application/Orders/PlaceCustomerOrder/PlaceCustomerOrderCommandHandler.cs#L45

kgrzybek commented 4 years ago

Hi @lisandropacheco,

All command handlers are decorated by UnitOfWork decorator: https://github.com/kgrzybek/sample-dotnet-core-cqrs-api/blob/master/src/SampleProject.Infrastructure/Processing/UnitOfWorkCommandHandlerWithResultDecorator.cs

    public class UnitOfWorkCommandHandlerWithResultDecorator<T, TResult> : ICommandHandler<T, TResult> where T : ICommand<TResult>
    {
        private readonly ICommandHandler<T, TResult> _decorated;
        private readonly IUnitOfWork _unitOfWork;

        public UnitOfWorkCommandHandlerWithResultDecorator(
            ICommandHandler<T, TResult> decorated, 
            IUnitOfWork unitOfWork)
        {
            _decorated = decorated;
            _unitOfWork = unitOfWork;
        }

        public async Task<TResult> Handle(T command, CancellationToken cancellationToken)
        {
            var result = await this._decorated.Handle(command, cancellationToken);

            await this._unitOfWork.CommitAsync(cancellationToken);

            return result;
        }
    }

After the command is handled by CommandHandler, this decorator invokes UnitOfWork.CommitAsync method which publishes all events and saves changes to the database:

    public class UnitOfWork : IUnitOfWork
    {
        private readonly OrdersContext _ordersContext;
        private readonly IDomainEventsDispatcher _domainEventsDispatcher;

        public UnitOfWork(
            OrdersContext ordersContext, 
            IDomainEventsDispatcher domainEventsDispatcher)
        {
            this._ordersContext = ordersContext;
            this._domainEventsDispatcher = domainEventsDispatcher;
        }

        public async Task<int> CommitAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            await this._domainEventsDispatcher.DispatchEventsAsync();
            return await this._ordersContext.SaveChangesAsync(cancellationToken);
        }
    }

This is the implementation of the Chain Of Responsibility pattern when one object is processed by several handlers. Each handler has own responsibility (logging, validating, saving, processing) and a pointer to the next handler.

astrobolidos commented 4 years ago

Hey Kamil, thanks for the quick answer. I'm the same lisandropacheco. The initial question was linked to an older git hub work account that now has been deleted. That's why I didn't get the notification to our answer before! It must be something with running from a mac because even the unit testing PlaceOrder_Test is failing. follows the stack trace: at SampleProject.Application.Orders.GetCustomerOrderDetails.GetCustomerOrderDetailsQueryHandler.Handle(GetCustomerOrderDetailsQuery request, CancellationToken cancellationToken) in /Users/lisandropacheco/Documents/src/sample-dotnet-core-cqrs-api/src/SampleProject.Application/Orders/GetCustomerOrderDetails/GetCustomerOrderDetailsQueryHandler.cs:line 41 at MediatR.Pipeline.RequestPreProcessorBehavior2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate1 next) at MediatR.Pipeline.RequestPostProcessorBehavior2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate1 next) at SampleProject.Infrastructure.Processing.QueriesExecutor.Execute[TResult](IQuery1 query) in /Users/lisandropacheco/Documents/src/sample-dotnet-core-cqrs-api/src/SampleProject.Infrastructure/Processing/QueriesExecutor.cs:line 16 at SampleProject.IntegrationTests.Orders.OrdersTests.PlaceOrder_Test() in /Users/lisandropacheco/Documents/src/sample-dotnet-core-cqrs-api/src/Tests/SampleProject.IntegrationTests/Orders/OrdersTests.cs:line 33 at NUnit.Framework.Internal.TaskAwaitAdapter.GenericAdapter1.BlockUntilCompleted() at NUnit.Framework.Internal.MessagePumpStrategy.NoMessagePumpStrategy.WaitForCompletion(AwaitAdapter awaitable) at NUnit.Framework.Internal.AsyncToSyncAdapter.Await(Func`1 invoke) at NUnit.Framework.Internal.Commands.TestMethodCommand.RunTestMethod(TestExecutionContext context) at NUnit.Framework.Internal.Commands.TestMethodCommand.Execute(TestExecutionContext context) at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.<>c__DisplayClass1_0.b__0() at NUnit.Framework.Internal.Commands.BeforeAndAfterTestCommand.RunTestMethodInThreadAbortSafeZone(TestExecutionContext context, Action action)