使用SimpleInjector重构Big Blob Factory

时间:2014-05-13 09:03:43

标签: c# dependency-injection refactoring factory simple-injector

情况

我最近将我的主要Big Blob域类型重构为一个外观,因为它的各个方面。到目前为止,外观和各个方面的服务工作就像一个魅力,但创建整个事物并将所有内容连接在一起,在一个带有Poor Man's Dependency Injection类型的Big Blob Factory中完成。真的很烦人,不得不把整个东西放在一起,所以我觉得我已经为Big Blog Domain类型工厂交换了Big Blob Domain类型: - /。

我正在尝试关注SOLID Principles,到目前为止,由于QueryHandlerCommandHandler抽象以及我的最爱,域与我的视图层的连接确实非常有效依赖注入容器(SimpleInjector to the rescue)。

现在我想知道如何利用SimpleInjector来完成比生命工厂更大的工作。

一些示例代码来说明我的案例

请注意,实际的IUser是由其他工厂创建的,并作为Command / Query参数对象的属性传递,所以在我的书中它不是被视为服务(例如由DI容器提交),而不是数据。

在重构之前

class BigDomainBlob : IDomainService
{
    public BigDomainBlob(Config config)
    {
        // Set up all the big blog business with the help of the
        // given configuration ...
    }

    // Lots of code interleaving the various aspects of the domain
}

interface IUser : System.Security.Principal.IPrincipal
{
    // Session information, ip address, etc.
}

class BigDomainBlobFactory : IFactory<IUser, IDomainService>
{
    // To this point the DI container can help me autowire the
    // factory's dependencies
    public BigDomainBlobFactory(/* ... */)
    {
        // Injecting persistence strategies and client specific
        // plugins to customize the service creation
    }

    public IDomainService Create(IUser user)
    {
        return new BigDomainBlob(new BigDomainBlob.Config
        {
            // Plenty of configuration code to tailor
            // the service to the needs of the given user
        });
    }
}

重构后

class NeatDomainFacade : IDomainService
{
    private readonly IDataHandlingService service1;
    private readonly IMetaDataService service2;

    public NeatDomainFacade(
        IDataHandlingService service1,
        IMetaDataService service2)
    {
        this.service1 = service1;
        this.service2 = service2;
    }

    // Forwarding the IDomainService functionality
    // to the different aspects of the domain
}

class NeatDomainFacadeFactory : IFactory<IUser, IDomainService>
{
    // To this point the DI container can still help me autowire the
    // factory's dependencies
    public NeatDomainFacadeFactory(/* ... */)
    {
        // Injecting persistence strategies and client specific
        // plugins to customize the service creation
    }

    // I still have to tailor every aspect of the domain to the
    // specific needs of a user by hand so the factory is going nuts
    public IDomainService Create(IUser user) 
    {
        // From here on I'm on my own hand wiring everything together ...

        // ...
        // Lots of code to set up user role, tailor SQL queries, etc.
        // ...

        // Handwiring my aspect dependencies and handing them in
        // to my fresh domain service
        var handWiredServiceForDataHandling = new Internal.DataService(
            someServiceDependency,
            anotherServiceDependency,
            new Internal.DataService.Config
            {
                SomeUserSpecificDbQueryString = "..."
            });

        var handWiredServiceThatAlsoUsesDataHandling = new Internal.MetaDataService(
            handWiredServiceForDataHandling,
            yetAnotherServiceDependency,
            new Internal.MetaDataService.Config
            {
                CustomizedMetaDataRetrievalString = "..."
            });

        // Even more code to hand wire and configure other dependencies ...

        // Finally handing in the service dependencies and creating the domain service
        return NeatDomainFacade(
            handWiredServiceForDataHandling, 
            handWiredServiceThatAlsoUsesDataHandling);
    }
}

问题

所以你看到:所有不同的方面不仅需要其他服务,还需要一些数据配置来定制用户的行为,因此所有这些都必须在用户信息可用的阶段进行配置 - 这意味着我可以' t在启动时配置它们。 我以为我现在已经开始依赖依赖注入方式了,但我不知道如何在自动布线的帮助下实现这一目标。

我已成功注入并使用工厂在我的QueryHandler s / CommandHandler中创建域服务,就像我说的那样:应用程序架构的其余部分似乎运行良好 - 我几乎不必触及组合根,只需将依赖项放入构造函数中并完成。如果我能解决这个皮塔域名服务的创建: - )

非常感谢帮助!

编辑:

我在他的回答中提到了重构史蒂文,但现在我面临生活方式问题。虽然手工布线我为IDomainService的每个实例创建了一次完全依赖关系,并重复使用这些依赖关系进行交叉依赖。一些代码:

public IDomainService Create(IUser user)
{
    // All dependencies are created exactly once and are
    // being reused throughout this method
    var serviceA = new ServiceA();
    var serviceB = new ServiceB(serviceA);
    var serviceC = new ServiceC(serviceA, serviceB)

    return new DomainService(serviceA, serviceB, serviceC);
}

对于默认的Lifestyle.Transient,情况不再如此,其中每个服务都会获得所请求依赖项的新实例。是否可以让SimpleInjector模拟我的手工布线行为?

再次感谢!

EDIT2:

我选择了SimpleInjector LifetimeScope,如下所示:

public class CompositionRoot
{
    public static Container container;

    public sealed class DomainServiceFactory : IFactory<IDomainService>
    {
        public IDomainService Create()
        {
            var container = CompositionRoot.container;
            using (container.BeginLifetimeScope())
            {
                return container.GetInstance<IDomainService>();
            }
        }
    }

    public static void Setup(Container container)
    {
        CompositionRoot.container = container;

        container.Register<IDomainService, NeatDomainFacade>(); 
        container.Register<IFactory<IDomainService>, DomainServiceFactory>();
        container.RegisterLifetimeScope<ServiceA>();
        container.RegisterLifetimeScope<ServiceB>();
        container.RegisterLifetimeScope<ServiceC>();
    }
}

现在我可以请求IFactory<IDomainService>,并且生命周期范围正在为依赖项启动。最后没有手工布线: - )

1 个答案:

答案 0 :(得分:2)

我认为问题在于您正在使用用户的上下文信息构建对象图。

因此,我建议不要这样做:

  • 阻止通过命令和NeatDomainFacadeFactory.Create方法传递用户信息,而是注入一个IUserContext服务,允许在构建对象图之后检索IUser
  • 注入使用IUserContext的额外服务,以便在运行时检索所需对象的正确信息。

因此,不是为一个特定用户创建服务实例,而是创建一个可用于您正在运行的任何用户的实例,并且仅在您通过对象图实际发送runtim数据时根据用户做出决策(所以之后构造)。

你可能需要一些附加的重构来实现这个目的。