gautema / CQRSlite

A lightweight framework to help creating CQRS and Eventsourcing applications in C#
Other
1.1k stars 266 forks source link

Dependancy Resolver Creates new instance of Singleton class #32

Closed nourBerro closed 7 years ago

nourBerro commented 7 years ago

the reason behind this change is the CQRSLite Dpandancy Resolver creates new instance of Singleton object like IEventStore and IRepository different than the asp.net core injected instances. i mean there is new diffrent instance from a singleton Object in the same Application LifeTime. you can check my theory by debugging the Id property i added in InMemoryEventStore. inject the IEventStore in HomeController and Compare the Id in HomeController instance and the Instance in IRepository when Dependacy resolver inject the IEventStroe when you Add new inventory Item. you will find them diffrent, but after my changes there will be one instance of singlton objects.

i couldnt add new pull so i will put my changes here:

1- UseCQrsLiteMiddleWare

 using System;
using System.Threading.Tasks;
using CQRSlite.Config;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;

namespace CQRSlite
{
    // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
    public class CqrsLite
    {
        private readonly RequestDelegate _next;
        private readonly IServiceProvider _serviceProvider;

        public CqrsLite(RequestDelegate next, IServiceProvider serviceProvider, Type type)
        {
            _next = next;
            _serviceProvider = serviceProvider;

            var registrar = (BusRegistrar)_serviceProvider.GetService(typeof(BusRegistrar));
            registrar.Register(type);
        }

        public Task Invoke(HttpContext httpContext)
        {

            return _next(httpContext);
        }
    }

    // Extension method used to add the middleware to the HTTP request pipeline.
    public static class UseCqrsLiteExtensions
    {
        public static IApplicationBuilder UseCqrsLite(this IApplicationBuilder builder, Type type)
        {
            return builder.UseMiddleware<CqrsLite>(type);
        }
    }
}

2- Startup Class in Web project

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using CQRSlite.Bus;
using CQRSlite.Commands;
using CQRSlite.Events;
using CQRSlite.Domain;
using CQRSCode.WriteModel;
using CQRSlite.Cache;
using CQRSCode.ReadModel;
using CQRSlite.Config;
using CQRSCode.WriteModel.Handlers;
using Scrutor;
using System.Reflection;
using System.Linq;
using CQRSlite;

namespace CQRSWeb
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMemoryCache();

            //Add Cqrs services
            services.AddSingleton<InProcessBus>(new InProcessBus());
            services.AddSingleton<ICommandSender>(y => y.GetService<InProcessBus>());
            services.AddSingleton<IEventPublisher>(y => y.GetService<InProcessBus>());
            services.AddSingleton<IHandlerRegistrar>(y => y.GetService<InProcessBus>());
            services.AddScoped<ISession, Session>();
            services.AddSingleton<IEventStore, InMemoryEventStore>();
            services.AddScoped<ICache, CQRSlite.Cache.MemoryCache>();
            services.AddScoped<IRepository>(y => new CacheRepository(new Repository(y.GetService<IEventStore>()), 
                y.GetService<IEventStore>(), y.GetService<ICache>()));
            services.AddSingleton<IServiceLocator, DependencyResolver>();
            services.AddTransient<IReadModelFacade, ReadModelFacade>();
            services.AddScoped<BusRegistrar>(y => new BusRegistrar(y.GetService<IServiceLocator>()));
            //Scan for commandhandlers and eventhandlers
            services.Scan(scan => scan
                .FromAssemblies(typeof(InventoryCommandHandlers).GetTypeInfo().Assembly)
                    .AddClasses(classes => classes.Where(x => {
                        var allInterfaces = x.GetInterfaces();
                        return 
                            allInterfaces.Any(y => y.GetTypeInfo().IsGenericType && y.GetTypeInfo().GetGenericTypeDefinition() == typeof(ICommandHandler<>)) ||
                            allInterfaces.Any(y => y.GetTypeInfo().IsGenericType && y.GetTypeInfo().GetGenericTypeDefinition() == typeof(IEventHandler<>));
                    }))
                    .AsSelf()
                    .WithTransientLifetime()
            );

            // Add framework services.
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            app.UseDeveloperExceptionPage();
            app.UseStaticFiles();

            app.UseCqrsLite(typeof(InventoryCommandHandlers));

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }

}
gautema commented 7 years ago

I can't recreate your bug. When debugging, only one EventStore is created. I have checked that the constructor of the event store is only run once. Can you please give some more details?

nourBerro commented 7 years ago

1- Add guid id property to inmemoryeventstore and let the constructor assign new Guid to it.

2- Add to HomeController constructor IEventStore prameter and put breakpoint 3 - go to Repository and put breakpoint to the constructor so u can check injected IEventStore And put on Save method a breakPoint

Run the application. Check your EventStore in homeController save the Id Then try to add new inventoryItem so the save breakpoint will puse your debug check again eventStore instanse There you will find new Guid.

gautema commented 7 years ago

Ok. I got to reproduce the bug. Do you have any idea why this happens? I don't want to add a dependency to any asp.net code to the cqrslite dll, so the files needs to be placed in the cqrsweb project. I also like to get a deeper understanding of the problem, so if you have any resources to how you fixed the bug, that would be great.

nourBerro commented 7 years ago

I solved the problem by creating middleware. check the code i posted eailer

nourBerro commented 7 years ago

If u dont want to use asp.net code in Cqrs dll its easy You can register the registrar in configure method in startup file instead of using the middleware

petersondrew commented 7 years ago

I'm curious to know what the source of this bug is as well as I've not experienced it.

nourBerro commented 7 years ago

I think the bug in asp.net dependancy injector. Because when i tried to create an instanse of IEventStore in ConfigureService method the service provider gave me the new instance of a singleton while the instance in the controller was a new one.

petersondrew commented 7 years ago

Are you using outdated versions of the asp.net dependencies by chance?

nourBerro commented 7 years ago

No i cloned your repo as it is.

gautema commented 7 years ago

The problem seems to be that the following code from Startup.cs creates a new and different serviceProvider than the one used to resolve controllers. And for that reason, one singleton is created in each of the different providers. I'll look more into a solution later today.

var serviceProvider = services.BuildServiceProvider();
var registrar = new BusRegistrar(new DependencyResolver(serviceProvider));
registrar.Register(typeof(InventoryCommandHandlers));
gautema commented 7 years ago

Found solution where I changed ConfigureServices signature to return IServiceProvider and returned the one I built. That way only one was created, and the problem was avoided.