elliotritchie / NES

.NET Event Sourcing
http://getnes.net
Other
129 stars 32 forks source link

Command doesn't send to CommandHandler #40

Closed mojamcpds closed 8 years ago

mojamcpds commented 8 years ago

Dear @marinkobabic:/ @elliotritchie

My Command Class is as follows:

public class RegisterToConference : ICommand, IValidatableObject
    {
        public RegisterToConference()
        {
            this.Id = Guid.NewGuid();
            this.Seats = new Collection<SeatQuantity>();
        }

        public Guid Id { get; set; }

        public Guid OrderId { get; set; }

        public Guid ConferenceId { get; set; }

        public ICollection<SeatQuantity> Seats { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (this.Seats == null || !this.Seats.Any(x => x.Quantity > 0))
            {
                 return new[] { new ValidationResult("One or more items are required.", new[] { "Seats" }) };
            }
            else if (this.Seats.Any(x => x.Quantity < 0))
            {
                return new[] { new ValidationResult("Invalid registration.", new[] { "Seats" }) };
            }

            return Enumerable.Empty<ValidationResult>();
        }
    }

I send above command from my controllers action method as follows:

 [HttpPost]
        public ActionResult StartRegistration(RegisterToConference command, int orderVersion)
        {
            var existingOrder = orderVersion != 0 ? this.orderDao.FindDraftOrder(command.OrderId) : null;
            var viewModel = this.CreateViewModel();
            if (existingOrder != null)
            {
                UpdateViewModel(viewModel, existingOrder);
            }

            viewModel.OrderId = command.OrderId;

            if (!ModelState.IsValid)
            {
                return View(viewModel);
            }

            // checks that there are still enough available seats, and the seat type IDs submitted are valid.
            ModelState.Clear();
            bool needsExtraValidation = false;
            foreach (var seat in command.Seats)
            {
                var modelItem = viewModel.Items.FirstOrDefault(x => x.SeatType.Id == seat.SeatType);
                if (modelItem != null)
                {
                    if (seat.Quantity > modelItem.MaxSelectionQuantity)
                    {
                        modelItem.PartiallyFulfilled = needsExtraValidation = true;
                        modelItem.OrderItem.ReservedSeats = modelItem.MaxSelectionQuantity;
                    }
                }
                else
                {
                    // seat type no longer exists for conference.
                    needsExtraValidation = true;
                }
            }

            if (needsExtraValidation)
            {
                return View(viewModel);
            }

            command.ConferenceId = this.ConferenceAlias.Id;
            this.commandBus.Send(command);
            //var address = new Address(@"RegistrationEndPoint", "localhost");
            //this.commandBus.Send(address, command);

            return RedirectToAction(
                "SpecifyRegistrantAndPaymentDetails",
                new { conferenceCode = this.ConferenceCode, orderId = command.OrderId, orderVersion = orderVersion });
        }

Logically when I click on register button it had to send the command to registered command handler and executes further actions. But it doesn't. For kind consideration I am giving my all related codes with this action.

Note: Messages are saves in Queue properly.

Following is my command handler class:

public class OrderCommandHandler :
        IHandleMessages<RegisterToConference>
    {
        private readonly IRepository repository;
        private readonly IPricingService pricingService;

        public OrderCommandHandler(IRepository repository, IPricingService pricingService)
        {
            this.repository = repository;
            this.pricingService = pricingService;
        }

        public void Handle(RegisterToConference command)
        {
            var items = command.Seats.Select(t => new OrderItem(t.SeatType, t.Quantity)).ToList();
            var order = repository.Get<Registration.Order>(command.OrderId);
            if (order == null)
            {
                order = new Registration.Order(command.OrderId, command.ConferenceId, items, pricingService);
            }
            else
            {
                order.UpdateSeats(items, pricingService);
            }

            repository.Add<Registration.Order>(order);
        }

    }

The aggregate is as follows:

public class Order : AggregateBase<Guid>
    {

        private static readonly TimeSpan ReservationAutoExpiration = TimeSpan.FromMinutes(15);

        private List<SeatQuantity> seats;
        private bool isConfirmed;
        private Guid conferenceId;

        static Order()
        {
            Mapper.CreateMap<OrderPaymentConfirmed, OrderConfirmed>();
        }

        protected Order(Guid id)
        {
            this.Id = id;

        }
        private void Handle(OrderTotalsCalculated @event)
        {
            this.Id = @event.SourceId;
            this.OnOrderTotalsCalculated(@event);
        }
        private void Handle(OrderRegistrantAssigned @event)
        {
            this.Id = @event.SourceId;
            this.OnOrderRegistrantAssigned(@event);
        }

        private void Handle(OrderConfirmed @event)
        {
            this.Id = @event.SourceId;
            this.OnOrderConfirmed(@event);
        }

        private void Handle(OrderPaymentConfirmed @event)
        {
            this.Id = @event.SourceId;
            this.OnOrderConfirmed(Mapper.Map<OrderConfirmed>(@event));
        }

        private void Handle(OrderExpired @event)
        {
            this.Id = @event.SourceId;
            this.OnOrderExpired(@event);
        }

        private void Handle(OrderReservationCompleted @event)
        {
            this.Id = @event.SourceId;
            this.OnOrderReservationCompleted(@event);
        }

        private void Handle(OrderPlaced @event)
        {
            this.Id = @event.SourceId;
            this.OnOrderPlaced(@event);
        }

        private void Handle(OrderUpdated @event)
        {
            this.Id = @event.SourceId;
            this.OnOrderUpdated(@event);
        }

        private void Handle(OrderPartiallyReserved @event)
        {
            this.Id = @event.SourceId;
            this.OnOrderPartiallyReserved(@event);
        }

        public Order(Guid id, Guid conferenceId, IEnumerable<OrderItem> items, IPricingService pricingService)
            : this(id)
        {
            var all = ConvertItems(items);
            var totals = pricingService.CalculateTotal(conferenceId, all.AsReadOnly());

            this.Apply<OrderPlaced>(e =>
            {
                e.ConferenceId = conferenceId;
                e.Seats = all;
                e.ReservationAutoExpiration = DateTime.UtcNow.Add(ReservationAutoExpiration);
                e.AccessCode = HandleGenerator.Generate(6);
            });

            this.Apply<OrderTotalsCalculated>(e =>
            {
                e.Total = totals.Total;
                e.Lines = totals.Lines != null ? totals.Lines.ToArray() : null;
                e.IsFreeOfCharge = totals.Total == 0m;
            });

        }

        public void UpdateSeats(IEnumerable<OrderItem> items, IPricingService pricingService)
        {
            var all = ConvertItems(items);
            var totals = pricingService.CalculateTotal(this.conferenceId, all.AsReadOnly());

            this.Apply<OrderUpdated>(e =>
            {
                e.Seats = all;
            });

            this.Apply<OrderTotalsCalculated>(e =>
            {
                e.Total = totals.Total;
                e.Lines = totals.Lines != null ? totals.Lines.ToArray() : null;
                e.IsFreeOfCharge = totals.Total == 0m;
            });
        }

        public void MarkAsReserved(IPricingService pricingService, DateTime expirationDate, IEnumerable<SeatQuantity> reservedSeats)
        {
            if (this.isConfirmed)
                throw new InvalidOperationException("Cannot modify a confirmed order.");

            var reserved = reservedSeats.ToList();

            // Is there an order item which didn't get an exact reservation?
            if (this.seats.Any(item => item.Quantity != 0 && !reserved.Any(seat => seat.SeatType == item.SeatType && seat.Quantity == item.Quantity)))
            {
                var totals = pricingService.CalculateTotal(this.conferenceId, reserved.AsReadOnly());

                this.Apply<OrderPartiallyReserved>(e =>
                {
                    e.ReservationExpiration = expirationDate;
                    e.Seats = reserved.ToArray();
                });

                this.Apply<OrderTotalsCalculated>(e =>
                {
                    e.Total = totals.Total;
                    e.Lines = totals.Lines != null ? totals.Lines.ToArray() : null;
                    e.IsFreeOfCharge = totals.Total == 0m;
                });
            }
            else
            {
                this.Apply<OrderReservationCompleted>(e =>
                {
                    e.ReservationExpiration = expirationDate;
                    e.Seats = reserved.ToArray();
                });
            }
        }

        public void Expire()
        {
            if (this.isConfirmed)
                throw new InvalidOperationException("Cannot expire a confirmed order.");

            this.Apply<OrderExpired>(e =>
            {
                e.SourceId = this.Id;
            });

        }

        public void Confirm()
        {

            this.Apply<OrderConfirmed>(e =>
            {
                e.SourceId = this.Id;
            });
        }

        public void AssignRegistrant(string firstName, string lastName, string email)
        {

            this.Apply<OrderRegistrantAssigned>(e =>
            {
                e.FirstName = firstName;
                e.LastName = lastName;
                e.Email = email;
            });

        }

        public SeatAssignments CreateSeatAssignments()
        {
            if (!this.isConfirmed)
                throw new InvalidOperationException("Cannot create seat assignments for an order that isn't confirmed yet.");

            return new SeatAssignments(this.Id, this.seats.AsReadOnly());
        }

        private static List<SeatQuantity> ConvertItems(IEnumerable<OrderItem> items)
        {
            return items.Select(x => new SeatQuantity(x.SeatType, x.Quantity)).ToList();
        }

        private void OnOrderPlaced(OrderPlaced e)
        {
            this.conferenceId = e.ConferenceId;
            this.seats = e.Seats.ToList();
        }

        private void OnOrderUpdated(OrderUpdated e)
        {
            this.seats = e.Seats.ToList();
        }

        private void OnOrderPartiallyReserved(OrderPartiallyReserved e)
        {
            this.seats = e.Seats.ToList();
        }

        private void OnOrderReservationCompleted(OrderReservationCompleted e)
        {
            this.seats = e.Seats.ToList();
        }

        private void OnOrderExpired(OrderExpired e)
        {
        }

        private void OnOrderConfirmed(OrderConfirmed e)
        {
            this.isConfirmed = true;
        }

        private void OnOrderRegistrantAssigned(OrderRegistrantAssigned e)
        {
        }

        private void OnOrderTotalsCalculated(OrderTotalsCalculated e)
        {
        }
    }

Events are as follows:

public interface IVersionedEvent : IEvent
    {
        /// <summary>
        /// Gets the version or order of the event in the stream.
        /// </summary>
        Guid SourceId { get; set; }
        int Version { get; set; }
    }

public interface OrderPlaced : IVersionedEvent
    {
        Guid ConferenceId { get; set; }

        IEnumerable<SeatQuantity> Seats { get; set; }

        /// <summary>
        /// The expected expiration time if the reservation is not explicitly confirmed later.
        /// </summary>
        DateTime ReservationAutoExpiration { get; set; }

        string AccessCode { get; set; }
    }

public interface OrderTotalsCalculated : IVersionedEvent
    {
        decimal Total { get; set; }

        OrderLine[] Lines { get; set; }

        bool IsFreeOfCharge { get; set; }
    }

Events are handled as follows:

public class PricedOrderViewModelGenerator :
        IHandleMessages<OrderPlaced>,
        IHandleMessages<OrderTotalsCalculated>
    {
        private readonly Func<ConferenceRegistrationDbContext> contextFactory;
        private readonly ObjectCache seatDescriptionsCache;

        public PricedOrderViewModelGenerator(Func<ConferenceRegistrationDbContext> contextFactory)
        {
            this.contextFactory = contextFactory;
            this.seatDescriptionsCache = MemoryCache.Default;
        }

        public void Handle(OrderPlaced @event)
        {
            using (var context = this.contextFactory.Invoke())
            {
                var dto = new PricedOrder
                {
                    OrderId = @event.SourceId,
                    ReservationExpirationDate = @event.ReservationAutoExpiration,
                    OrderVersion = @event.Version
                };
                context.Set<PricedOrder>().Add(dto);
                try
                {
                    context.SaveChanges();
                }
                catch (DbUpdateException)
                {
                    Trace.TraceWarning(
                        "Ignoring OrderPlaced message with version {1} for order id {0}. This could be caused because the message was already handled and the PricedOrder entity was already created.",
                        dto.OrderId,
                        @event.Version);
                }
            }
        }

        public void Handle(OrderTotalsCalculated @event)
        {
            var seatTypeIds = @event.Lines.OfType<SeatOrderLine>().Select(x => x.SeatType).Distinct().ToArray();
            using (var context = this.contextFactory.Invoke())
            {
                var dto = context.Query<PricedOrder>().Include(x => x.Lines).First(x => x.OrderId == @event.SourceId);
                if (!WasNotAlreadyHandled(dto, @event.Version))
                {
                    // message already handled, skip.
                    return;
                }

                var linesSet = context.Set<PricedOrderLine>();
                foreach (var line in dto.Lines.ToList())
                {
                    linesSet.Remove(line);
                }

                var seatTypeDescriptions = GetSeatTypeDescriptions(seatTypeIds, context);

                for (int i = 0; i < @event.Lines.Length; i++)
                {
                    var orderLine = @event.Lines[i];
                    var line = new PricedOrderLine
                    {
                        LineTotal = orderLine.LineTotal,
                        Position = i,
                    };

                    var seatOrderLine = orderLine as SeatOrderLine;
                    if (seatOrderLine != null)
                    {
                        // should we update the view model to avoid losing the SeatTypeId?
                        line.Description = seatTypeDescriptions.Where(x => x.SeatTypeId == seatOrderLine.SeatType).Select(x => x.Name).FirstOrDefault();
                        line.UnitPrice = seatOrderLine.UnitPrice;
                        line.Quantity = seatOrderLine.Quantity;
                    }

                    dto.Lines.Add(line);
                }

                dto.Total = @event.Total;
                dto.IsFreeOfCharge = @event.IsFreeOfCharge;
                dto.OrderVersion = @event.Version;

                context.SaveChanges();
            }
        }

  }

public class DraftOrderViewModelGenerator :
        IEventHandler<OrderPlaced>
    {
        private readonly Func<ConferenceRegistrationDbContext> contextFactory;

        static DraftOrderViewModelGenerator()
        {
            // Mapping old version of the OrderPaymentConfirmed event to the new version.
            // Currently it is being done explicitly by the consumer, but this one in particular could be done
            // at the deserialization level, as it is just a rename, not a functionality change.
            Mapper.CreateMap<OrderPaymentConfirmed, OrderConfirmed>();
        }

        public DraftOrderViewModelGenerator(Func<ConferenceRegistrationDbContext> contextFactory)
        {
            this.contextFactory = contextFactory;
        }

        public void Handle(OrderPlaced @event)
        {
            using (var context = this.contextFactory.Invoke())
            {
                var dto = new DraftOrder(@event.SourceId, @event.ConferenceId, DraftOrder.States.PendingReservation, @event.Version)
                {
                    AccessCode = @event.AccessCode,
                };
                dto.Lines.AddRange(@event.Seats.Select(seat => new DraftOrderItem(seat.SeatType, seat.Quantity)));

                context.Save(dto);
            }
        }

    }

public class OrderEventHandler :
        IHandleMessages<OrderPlaced>,
        IHandleMessages<OrderTotalsCalculated>
    {
        private Func<ConferenceContext> contextFactory;

        public OrderEventHandler(Func<ConferenceContext> contextFactory)
        {
            this.contextFactory = contextFactory;
        }

        public void Handle(OrderPlaced @event)
        {
            using (var context = this.contextFactory.Invoke())
            {
                context.Orders.Add(new Order(@event.ConferenceId, @event.SourceId, @event.AccessCode));
                context.SaveChanges();
            }
        }

        public void Handle(OrderTotalsCalculated @event)
        {
            if (!ProcessOrder(order => order.Id == @event.SourceId, order => order.TotalAmount = @event.Total))
            {
                Trace.TraceError("Failed to locate the order with id {0} to apply calculated totals", @event.SourceId);
            }
        }

    }

My EndPoint Configuration is as follows:

public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantToRunWhenBusStartsAndStops, IWantToRunWhenConfigurationIsComplete
    {
        public void Init()
        {
            LogManager.Use<Log4NetFactory>();
        }

        public void Start()
        {
            Wireup.Init()
                .UsingInMemoryPersistence()
                .EnlistInAmbientTransaction()
                .NES()
                .Build();
        }

        public void Stop()
        {
        }

        public void Customize(BusConfiguration configuration)
        {
            configuration.UseSerialization<Json>();
            configuration.EnableInstallers();
            configuration.UsePersistence<InMemoryPersistence>();
            configuration.UseTransport<MsmqTransport>();
            configuration.PurgeOnStartup(false);
            configuration.RegisterComponents(c =>
            {
                c.ConfigureComponent<Repository>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<SqlBlobStorage>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<RegistrationProcessManagerRouter>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<RegistrationProcessManager>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<ConferenceViewModelGenerator>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<DraftOrderViewModelGenerator>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<OrderCommandHandler>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<PricedOrderViewModelGenerator>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<SeatAssignmentsHandler>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<SeatAssignmentsViewModelGenerator>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<SeatsAvailabilityHandler>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<ValidationService>(DependencyLifecycle.SingleInstance);
                c.ConfigureComponent<ValidateIncomingMessages>(DependencyLifecycle.InstancePerCall);
            });
        }

        public void Run(Configure config)
        {
            config.NES();
        }
    }

Inside my web.config I configured as follows:

<configSections>
    <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="TransportConfig" type="NServiceBus.Config.TransportConfig, NServiceBus.Core" />
    <section name="UnicastBusConfig" type="NServiceBus.Config.UnicastBusConfig, NServiceBus.Core" />
    <section name="MessageForwardingInCaseOfFaultConfig" type="NServiceBus.Config.MessageForwardingInCaseOfFaultConfig, NServiceBus.Core" />
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>

 <TransportConfig MaxRetries="5" MaximumConcurrencyLevel="1" MaximumMessageThroughputPerSecond="0" />
  <UnicastBusConfig>
    <MessageEndpointMappings>
      <add Messages="Registration" Endpoint="Registration" />
    </MessageEndpointMappings>
  </UnicastBusConfig>
  <MessageForwardingInCaseOfFaultConfig ErrorQueue="RegistrationEndPoint.Errors" />

Inside Global.asax I registered Bus as follows:

 public static ISendOnlyBus Bus { get; private set; }
        private void RegisterBus()
        {
            var busConfiguration = new BusConfiguration();
            busConfiguration.UseSerialization<JsonSerializer>();
            busConfiguration.UseTransport<MsmqTransport>();
            busConfiguration.EndpointName("Conference.Web.Public");
            busConfiguration.Transactions().Disable();
            busConfiguration.PurgeOnStartup(false);

            LogManager.Use<NServiceBus.Log4Net.Log4NetFactory>();

            Bus = NServiceBus.Bus.CreateSendOnly(busConfiguration);
        }

Note: I am using Unity Container for Resolve Dependencies as follows:

private static UnityContainer CreateContainer()
        {
            var container = new UnityContainer();
            try
            {
                // repositories used by the application

                container.RegisterType<ConferenceRegistrationDbContext>(new TransientLifetimeManager(), new InjectionConstructor("ConferenceRegistration"));
                container.RegisterType<PaymentsReadDbContext>(new TransientLifetimeManager(), new InjectionConstructor("Payments"));

                var cache = new MemoryCache("ReadModel");
                container.RegisterType<IOrderDao, OrderDao>();
                container.RegisterType<IConferenceDao, CachingConferenceDao>(
                    new ContainerControlledLifetimeManager(),
                    new InjectionConstructor(new ResolvedParameter<ConferenceDao>(), cache));
                container.RegisterType<IPaymentDao, PaymentDao>();

                // configuration specific settings

                OnCreateContainer(container);

                return container;
            }
            catch
            {
                container.Dispose();
                throw;
            }
        }

Now I am really confused where I am making mistake.

Please help me to fix the issue.

marinkobabic commented 8 years ago

So you have two endpoints

The first endpoint has the SendOnly bus and you are totally sure that the seconds bus for the Registrations is not SendOnly?

When you have sent the command, the command will go into the queue of the Registrations endpoint. Please can you shut down the Registrations Application and send the command? This way you can ensure that the command is really in the inbox queue of the Registrations endpoint.

You have checked the error queue, that there is nothing?

Please provide the log file of the Registrations endpoint.

mojamcpds commented 8 years ago

Dear @marinkobabic According to your instruction I removed Endpoints and checked but yet not working.

Here is my Bus Registration code:

public static ISendOnlyBus Bus { get; private set; }
        private void RegisterBus()
        {
            var busConfiguration = new BusConfiguration();
            busConfiguration.UseSerialization<JsonSerializer>();
            busConfiguration.UseTransport<MsmqTransport>();
            busConfiguration.Transactions().Disable();
            busConfiguration.PurgeOnStartup(false);

            LogManager.Use<NServiceBus.Log4Net.Log4NetFactory>();

            Bus = NServiceBus.Bus.CreateSendOnly(busConfiguration);
        }

And my Endpoint configuration is as follows:

public class EndpointConfig : IConfigureThisEndpoint, AsA_Server, IWantToRunWhenBusStartsAndStops, IWantToRunWhenConfigurationIsComplete
    {
        public void Init()
        {
            LogManager.Use<Log4NetFactory>();
        }

        public void Start()
        {
            Wireup.Init()
                .UsingInMemoryPersistence()
                .EnlistInAmbientTransaction()
                .NES()
                .Build();
        }

        public void Stop()
        {
        }

        public void Customize(BusConfiguration configuration)
        {
            configuration.UseSerialization<Json>();
            configuration.EnableInstallers();
            configuration.UsePersistence<InMemoryPersistence>();
            configuration.UseTransport<MsmqTransport>();
            configuration.PurgeOnStartup(false);
            configuration.RegisterComponents(c =>
            {
                c.ConfigureComponent<Repository>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<SqlBlobStorage>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<RegistrationProcessManagerRouter>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<RegistrationProcessManager>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<ConferenceViewModelGenerator>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<DraftOrderViewModelGenerator>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<OrderCommandHandler>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<PricedOrderViewModelGenerator>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<SeatAssignmentsHandler>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<SeatAssignmentsViewModelGenerator>(DependencyLifecycle.InstancePerUnitOfWork);
                c.ConfigureComponent<SeatsAvailabilityHandler>(DependencyLifecycle.InstancePerUnitOfWork);

            });
        }

        public void Run(Configure config)
        {
            config.NES();
        }
    }
marinkobabic commented 8 years ago

I did not tell you to remove the endpoint. I just said that you have two NServiceBus services running. You should just stop one of the instances to be sure that it does not process any commands. When your controller sends the command and the transaction is commited you should go into the message queue and check if the message is in the "Registrations" inbox queue. If this is the case the command was sent successfully. We can from that point on investigate what is going on with the "Registrations" endpoint. For that purpose you can enable logging and start the instance of the "Registrations" endpoint. If there is a message in the inbox queue it will take the message and try to process it.

mojamcpds commented 8 years ago

Yes. It goes to Registration queue but still doesn't invoke Command handler.

marinkobabic commented 8 years ago

So the process which hosts the targed queue must be named as "Registrations" endpoint. When you startup this process then you can see the mappings in the log of message -> handler. When the process is started the message should disappear from inbox queue. Does this happen? How does the log look like?

mojamcpds commented 8 years ago

Following message is given when I run EndPoint Server. Is it related to above problem? faultmessage

Actually I am very new in Nservicebus so it takes time for me to understand the problem.

marinkobabic commented 8 years ago

Yes. It tried to process your message but it failed. You will find the message in the error queue. The header of the message contains the reason resp. the sracktrace.

You can do a simple check. In your command handler remove the constructor which gets parameters and create a parameter less constructor. Set a break point inside of the handle method to check if the point is reached.

If the point is reached but when your constructor has parameters not, then you know the source of the problem.

marinkobabic commented 8 years ago

In your case I can imagine that IRepository is not registered in the unity container. Please verify ny statement.

marinkobabic commented 8 years ago

First step is to create root unitycontainer. Register your classes and IRepository. Pass the container to nservicebus during bootstrapping so that nservicebus uses your container.

In case if unity the hierarchicallifetime = unitofwork.

Whenever a command comes in, nservicebus creates a child container as unitofwork container and removes this when transaction is completed.

mojamcpds commented 8 years ago

Thanks for your reply. According to your instruction I configured as follows:

private void RegisterBus(IUnityContainer container)
        {
            var busConfiguration = new BusConfiguration();
            busConfiguration.UseSerialization<JsonSerializer>();
            busConfiguration.UseTransport<MsmqTransport>();
            busConfiguration.Transactions().Disable();
            busConfiguration.PurgeOnStartup(false);
            busConfiguration.UseContainer<UnityBuilder>();
            busConfiguration.UseContainer<UnityBuilder>(c => c.UseExistingContainer(container));
            LogManager.Use<NServiceBus.Log4Net.Log4NetFactory>();

            Bus = NServiceBus.Bus.CreateSendOnly(busConfiguration);
        }

And Resolved my dependencies as follows:

private static UnityContainer CreateContainer()
        {
            var container = new UnityContainer();
            try
            {
                container.RegisterInstance<ITextSerializer>(new JsonTextSerializer());

                container.RegisterType<DbContext, RegistrationProcessManagerDbContext>("registration", new TransientLifetimeManager(), new InjectionConstructor("ConferenceRegistrationProcesses"));
                container.RegisterType<IProcessManagerDataContext<RegistrationProcessManager>, SqlProcessManagerDataContext<RegistrationProcessManager>>(
                    new TransientLifetimeManager(),
                    new InjectionConstructor(new ResolvedParameter<Func<DbContext>>("registration"), typeof(IBus), typeof(ITextSerializer)));

                container.RegisterType<DbContext, PaymentsDbContext>("payments", new TransientLifetimeManager(), new InjectionConstructor("Payments"));
                container.RegisterType<IDataContext<ThirdPartyProcessorPayment>, SqlDataContext<ThirdPartyProcessorPayment>>(
                    new TransientLifetimeManager(),
                    new InjectionConstructor(new ResolvedParameter<Func<DbContext>>("payments"), typeof(IBus)));

                container.RegisterType<ConferenceRegistrationDbContext>(new TransientLifetimeManager(), new InjectionConstructor("ConferenceRegistration"));
                container.RegisterType<IConferenceDao, ConferenceDao>(new ContainerControlledLifetimeManager());
                container.RegisterType<IOrderDao, OrderDao>(new ContainerControlledLifetimeManager());
                container.RegisterType<IPricingService, PricingService>(new ContainerControlledLifetimeManager());
                container.RegisterType<global::Conference.ConferenceContext>(new TransientLifetimeManager(), new InjectionConstructor("ConferenceManagement"));
                container.RegisterType<ConferenceRegistrationDbContext>(new TransientLifetimeManager(), new InjectionConstructor("ConferenceRegistration"));
                container.RegisterType<PaymentsReadDbContext>(new TransientLifetimeManager(), new InjectionConstructor("Payments"));

                container.RegisterType<IRepository,Repository>(new ContainerControlledLifetimeManager());

                var cache = new MemoryCache("ReadModel");
                container.RegisterType<IOrderDao, OrderDao>();
                container.RegisterType<IConferenceDao, CachingConferenceDao>(
                    new ContainerControlledLifetimeManager(),
                    new InjectionConstructor(new ResolvedParameter<ConferenceDao>(), cache));
                container.RegisterType<IPaymentDao, PaymentDao>();

                OnCreateContainer(container);

                return container;
            }
            catch
            {
                container.Dispose();
                throw;
            }
        }

        static partial void OnCreateContainer(UnityContainer container);

Finally called them inside Application_Start() method of Global.asax as follows:

private IUnityContainer container;
protected void Application_Start()
        {

            DatabaseSetup.Initialize();

            this.container = CreateContainer();

            DependencyResolver.SetResolver(new UnityServiceLocator(this.container));
            RegisterBus(this.container);

            RegisterGlobalFilters(GlobalFilters.Filters);
            RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            AreaRegistration.RegisterAllAreas();
            AppRoutes.RegisterRoutes(RouteTable.Routes);

            this.OnStart();
        }

But the behavior is still unchanged. If I remove constructors from OrderHandler class then it fires Command Handler. But with dependency resolver it doesn't work.

Don't know where I am making the mistake.

marinkobabic commented 8 years ago

Looks good now. So please try just to have IRepository in your OrderCommandHandler constructor. Does it still work? Now remove the IRepository and use just the IPricingService. Does it now work?

To make it even simpler in the last line of Application_Start please add the following lines just as test:

var rep = this.container.Resolve < IRepository >(); var pr = this.container.Resolve < IPricingService >(); var handler = this.container.Resolve < OrderCommandHandler >();

Do all the three resolving work?

marinkobabic commented 8 years ago

By the way please remove the first line here

busConfiguration.UseContainer(); busConfiguration.UseContainer(c => c.UseExistingContainer(container));

mojamcpds commented 8 years ago

I tried according to your instruction as follows.

Then I registered & resolved all command handlers and event handlers:

 private static void RegisterEventHandlers(IUnityContainer container)
        {
            container.RegisterType<RegistrationProcessManagerRouter>();
            container.RegisterType<DraftOrderViewModelGenerator>();
            container.RegisterType<PricedOrderViewModelGenerator>();
            container.RegisterType<ConferenceViewModelGenerator>();
            container.RegisterType<SeatAssignmentsViewModelGenerator>();
            container.RegisterType<SeatAssignmentsHandler>();
            container.RegisterType<global::Conference.OrderEventHandler>();
        }

        private static void RegisterCommandHanlders(IUnityContainer container)
        {
            container.RegisterType<OrderCommandHandler>();
            container.RegisterType<SeatsAvailabilityHandler>();
            container.RegisterType<ThirdPartyProcessorPaymentCommandHandler>();
        }

        private void ResolveEventHandlers(IUnityContainer container)
        {

            container.Resolve<RegistrationProcessManagerRouter>();
            container.Resolve<DraftOrderViewModelGenerator>();
            container.Resolve<PricedOrderViewModelGenerator>();
            container.Resolve<ConferenceViewModelGenerator>();
            container.Resolve<SeatAssignmentsViewModelGenerator>();
            container.Resolve<SeatAssignmentsHandler>();
            container.Resolve<global::Conference.OrderEventHandler>();
        }

        private void ResolveCommandHanlders(IUnityContainer container)
        {
            container.Resolve<IRepository>();
            container.Resolve<IPricingService>();
            container.Resolve<OrderCommandHandler>();
            container.Resolve<SeatsAvailabilityHandler>();
            container.Resolve<ThirdPartyProcessorPaymentCommandHandler>();
        }

Though my PricingService(concrete implementation of IPricingService) takes IConferenceDaoas constructor parameter so I registered it as follows:

container.RegisterType<IPricingService, PricingService>(new TransientLifetimeManager(),
                    new InjectionConstructor(new ResolvedParameter<ConferenceDao>())
                    );

Then I called them from Application_Start() method of Global.asax.cs as follows:

protected void Application_Start()
        {

            DatabaseSetup.Initialize();

            this.container = CreateContainer();

            DependencyResolver.SetResolver(new UnityServiceLocator(this.container));
            RegisterBus(this.container);

            RegisterGlobalFilters(GlobalFilters.Filters);
            RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            AreaRegistration.RegisterAllAreas();
            AppRoutes.RegisterRoutes(RouteTable.Routes);

           ResolveCommandHanlders(this.container);
            ResolveEventHandlers(this.container);

            this.OnStart();
        }

Also called Registered methods inside Ioc configuration method as follows:

private static UnityContainer CreateContainer()
        {
            var container = new UnityContainer();
            try
            {

                RegisterCommandHanlders(container);
                RegisterEventHandlers(container);

                container.RegisterInstance<ITextSerializer>(new JsonTextSerializer());

                container.RegisterType<DbContext, RegistrationProcessManagerDbContext>("registration", new TransientLifetimeManager(), new InjectionConstructor("ConferenceRegistrationProcesses"));
                container.RegisterType<IProcessManagerDataContext<RegistrationProcessManager>, SqlProcessManagerDataContext<RegistrationProcessManager>>(
                    new TransientLifetimeManager(),
                    new InjectionConstructor(new ResolvedParameter<Func<DbContext>>("registration"), typeof(IBus), typeof(ITextSerializer)));

                container.RegisterType<DbContext, PaymentsDbContext>("payments", new TransientLifetimeManager(), new InjectionConstructor("Payments"));
                container.RegisterType<IDataContext<ThirdPartyProcessorPayment>, SqlDataContext<ThirdPartyProcessorPayment>>(
                    new TransientLifetimeManager(),
                    new InjectionConstructor(new ResolvedParameter<Func<DbContext>>("payments"), typeof(IBus)));
                container.RegisterType<ConferenceRegistrationDbContext>(new TransientLifetimeManager(), new InjectionConstructor("ConferenceRegistration"));
                container.RegisterType<IConferenceDao, ConferenceDao>(new ContainerControlledLifetimeManager());
                container.RegisterType<IOrderDao, OrderDao>(new ContainerControlledLifetimeManager());

              container.RegisterType<IPricingService, PricingService>(new TransientLifetimeManager(),
                    new InjectionConstructor(new ResolvedParameter<ConferenceDao>())
                    );

                container.RegisterType<global::Conference.ConferenceContext>(new TransientLifetimeManager(), new InjectionConstructor("ConferenceManagement"));
                container.RegisterType<ConferenceRegistrationDbContext>(new TransientLifetimeManager(), new InjectionConstructor("ConferenceRegistration"));
                container.RegisterType<PaymentsReadDbContext>(new TransientLifetimeManager(), new InjectionConstructor("Payments"));

                container.RegisterType<IRepository,Repository>(new ContainerControlledLifetimeManager());

                var cache = new MemoryCache("ReadModel");
                container.RegisterType<IOrderDao, OrderDao>();
                container.RegisterType<IConferenceDao, CachingConferenceDao>(
                    new ContainerControlledLifetimeManager(),
                    new InjectionConstructor(new ResolvedParameter<ConferenceDao>(), cache));
                container.RegisterType<IPaymentDao, PaymentDao>();

                OnCreateContainer(container);

                return container;
            }
            catch
            {
                container.Dispose();
                throw;
            }
        }

I also removed the linebusConfiguration.UseContainer<UnityBuilder>(); from BusRegistration()method as follows:

private void RegisterBus(IUnityContainer container)
        {
            var busConfiguration = new BusConfiguration();
            busConfiguration.UseSerialization<JsonSerializer>();
            busConfiguration.UseTransport<MsmqTransport>();
            busConfiguration.Transactions().Disable();
            busConfiguration.PurgeOnStartup(false);
            busConfiguration.UseContainer<UnityBuilder>(c => c.UseExistingContainer(container));
            LogManager.Use<NServiceBus.Log4Net.Log4NetFactory>();

            Bus = NServiceBus.Bus.CreateSendOnly(busConfiguration);
        }

But yet it doesn't work. If I remove IPricingServicefrom the constructor of OrderCommandHandlerthen it fire the Handle method. I think something wrong with the registration of IPricingService. For your kind consideration I am giving the entire dependencies of PricingServiceclass.

Constructor of OrderCommandHandler.cs:

private readonly IRepository repository;
private readonly IPricingService pricingService;

        public OrderCommandHandler(IRepository repository, IPricingService pricingService)
        {
            this.repository = repository;
            this.pricingService = pricingService;
        }

Constructor of PriceService.cs

 private readonly IConferenceDao conferenceDao;

        public PricingService(IConferenceDao conferenceDao)
        {
            if (conferenceDao == null) throw new ArgumentNullException("conferenceDao");

            this.conferenceDao = conferenceDao;
        }

_Constructor of ConferenceDao.cs _

private readonly Func<ConferenceRegistrationDbContext> contextFactory;

        public ConferenceDao(Func<ConferenceRegistrationDbContext> contextFactory)
        {
            this.contextFactory = contextFactory;
        }

Considering above constructors is it right to register dependency of IPricingService and PricingService that defined as follows:

container.RegisterType<IPricingService, PricingService>(new TransientLifetimeManager(),
                    new InjectionConstructor(new ResolvedParameter<ConferenceDao>())
                    );

I really struggling with this issue.

marinkobabic commented 8 years ago

To register the dependecies do the following

container.Register < IPricingService, PricingService >(); container.Register < IConferenceDao, ConferenceDao >();

and the problem you have is the constructor of ConferenceDao. It can't be a Func. Please make a new interface likeConferenceRegistrationDbContextFactory and pass it as parameter to ConferenceDao. Don't forget to register the new interface in container. After you have done all of this it will work :smiley:

mojamcpds commented 8 years ago

Tried according to your instruction but yet doesn't work.

What I did is just instead of passing Func I just sendConferenceRegistrationDbContext through the constructor of ConferenceDao as follows:

 private readonly ConferenceRegistrationDbContext context;

        public ConferenceDao(ConferenceRegistrationDbContext context)
        {
            this.context = context;
        }

And dependency resolved as follows:

container.RegisterType<ConferenceRegistrationDbContext>(new TransientLifetimeManager(), new InjectionConstructor("ConferenceRegistration"));
              container.RegisterType<IConferenceDao, ConferenceDao>(new ContainerControlledLifetimeManager());
               container.RegisterType<IOrderDao, OrderDao>(new ContainerControlledLifetimeManager());
               container.RegisterType<IPricingService, PricingService>(new ContainerControlledLifetimeManager());

But result is as previous

marinkobabic commented 8 years ago

You don't need to tell unity that it will be used as parameter to constructor.

container.RegisterType< ConferenceRegistrationDbContext >();

is enough. Can you resolve

container.Resolve< IConferenceDao > ();

mojamcpds commented 8 years ago

If not pass the constructor return following error: The type String cannot be constructed. You must configure the container to supply this value.

However I resolved IConferenceDao as follows:


container.RegisterType<IConferenceDao, ConferenceDao>(new ContainerControlledLifetimeManager());
               container.Resolve<IConferenceDao>();
               container.RegisterType<IOrderDao, OrderDao>(new ContainerControlledLifetimeManager());
               container.RegisterType<IPricingService, PricingService>(new ContainerControlledLifetimeManager());
marinkobabic commented 8 years ago

ConferenceDao gets the ConferenceRegistrationDbContext and not a string. Please can you copy your few classes in a console application and demonstrate the problem. So that I can download it and make it work in few minutes.

mojamcpds commented 8 years ago

Actually we are going to develop a Web based ERP System where we'll use following architectures and technologies: Database : Microsoft SQL Server Framework : ASP.NET MVC Front-End : KendoUI, RAZOR CSHTML, Bootstrap, and HTML-5 Server-Side : C#, Node.js API : ASP.NET Web API, Node.js API ORM : Entity Framework Unit Testing : X-Unit Methodology : Domain Driven Design, Domain Model Architectures : Onion Architecture (DDD), CQRS & Event Sourcing. Enterprise Service Bus : NServiceBus. EventStorage : RavenDB (Though NEventStore provide RavenDB usage flexibility so we’ll use it) Messaging : MSMQ Dependency Injection : Unity Mocking : Moq Mapping : Automapper Continuous Integration: Teamcity Refactoring : Resharper Source control : Git

Though NES.Cqrs gives us the built-in flexibility to work with CQRS architecture including NServiceBus, NEventStore (with RavenDB), MSMQ so we decided to use it.

Before starting the project we have to R&D on some concepts and* NES.CQRS* is one of them. So we started R&D on NES.CQRS and trying to convert Conference project into _NES _ framework for learning purpose.

However I am uploading my whole project. Please check and help me where I am making the mistake.

Thanks for your kind help.

marinkobabic commented 8 years ago

When I start your project the following interfaces are resolved properly

private void ResolveCommandHanlders(IUnityContainer container)
        {
            var rep = container.Resolve<IRepository>();
            var pric = container.Resolve<IPricingService>();
            var ord = container.Resolve<OrderCommandHandler>();
            var seat = container.Resolve<SeatsAvailabilityHandler>();
            var pay = container.Resolve<ThirdPartyProcessorPaymentCommandHandler>();
        }

Is this also the case on your machine?

mojamcpds commented 8 years ago

Then where is the problem? Why not it works properly? Any other configuration related issue?

marinkobabic commented 8 years ago

Will run the whole solution in the evening and let you know about the results ;-)

mojamcpds commented 8 years ago

Ok. Thanks. Waiting for your kind response. Shall I give you database script?

marinkobabic commented 8 years ago

No it's ok :-) thanks

mojamcpds commented 8 years ago

Any update regarding the issue? I debugged it line by line. To do so when I Step-in i.e. press F11 on commandbus.Send(command) method it shows me unicast.cs not found

unicastbus cs

Then I removed the constructor injection from OrderCommandHandler class by configuring as follows:

private readonly IRepository repository;
private readonly IPricingService pricingService;
public OrderCommandHandler()
{
            var container = new UnityContainer();
            container.RegisterType<IRepository, Repository>();
            container.RegisterType<ConferenceRegistrationDbContext>(new TransientLifetimeManager(), new InjectionConstructor("ConferenceRegistration"));
            container.RegisterType<IConferenceDao, ConferenceDao>(new ContainerControlledLifetimeManager());
            container.RegisterType<IPricingService, PricingService>();

            this.repository = container.Resolve<IRepository>();
            this.pricingService = container.Resolve<IPricingService>();
        }

After remove the constructor injection now when commandbus.Send() method executes it invoke OrderCommandHandler class and execute proper handle method. But the problem is when it tries to execute repository.Get<Registration.Order>(command.OrderId);this line it shows me behaviorchain.cs not found. behaviorchain

For your kind consideration I am giving you the Handle methods code:

public void Handle(RegisterToConference command)
        {
            var items = command.Seats.Select(t => new OrderItem(t.SeatType, t.Quantity)).ToList();
            var order = repository.Get<Registration.Order>(command.OrderId);
            if (order == null)
            {
                order = new Registration.Order(command.OrderId, command.ConferenceId, items, pricingService);
            }
            else
            {
                order.UpdateSeats(items, pricingService);
            }

            repository.Add<Registration.Order>(order);
        }
mojamcpds commented 8 years ago

Thanks. Solved that issue. :)

marinkobabic commented 8 years ago

Is now everything working properly? In your backed you have to use the unity and to register all dependencies. Actually there is nothing related to unity.

mojamcpds commented 8 years ago

Yes. Got the issue. :) If needed I will contact you :) Thanks for your kind effort :)