【问题标题】:Refactor Big Blob Factory with SimpleInjector使用 SimpleInjector 重构 Big Blob 工厂
【发布时间】:2014-05-13 09:03:43
【问题描述】:

情况

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

我正在尝试关注SOLID Principles,到目前为止,由于QueryHandlerCommandHandler 抽象以及我最喜欢的依赖注入容器(@987654325),域与我的视图层的连接工作得非常好@)。

现在我想知道如何利用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 在启动时配置它们。 我以为我现在已经了解了依赖注入的方式,但我只是不明白如何在自动布线的帮助下实现这一点。

我已成功注入并使用工厂在我的QueryHandlers/CommandHandlers 中创建域服务,就像我说的:应用程序架构的其余部分似乎运行良好 - 我几乎没有要接触组合根,只需将依赖项放入构造函数并完成。要是我能解决这个 pita 域服务创建问题就好了:-)

帮助非常感谢!

编辑:

我做了 steven 在他的回答中建议的重构,但现在我面临着生活方式问题。在手工布线时,我为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 模拟我的手动接线行为?

再次感谢!

编辑2:

我使用 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&lt;IDomainService&gt; 并且生命周期范围正在为依赖项启动。终于免手动接线了:-)

【问题讨论】:

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


    【解决方案1】:

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

    所以我建议不要这样做:

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

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

    您可能需要进行一些额外的重构才能使其正常工作。

    【讨论】:

    • 有趣的一点,我会调查这个。在这种情况下,IUserContext 服务如何知道当时哪个用户处于活动状态?目前,我正在使用 MVC FilterAttribute 来确定当前用户和控制器上的扩展方法,以便在实际需要时检索它(例如,将其传递给 Query/Command)。谢谢史蒂文!
    • @vanhelgen:那么,FilterAttribute 将如何确定当前用户?您可能可以将大部分代码复制到AspNetUserContext 实现中。
    • 我终于完成了重构……真棒:-)。它现在一直自动接线,但是我在手动接线时隐含地遵守了关于生命周期管理的一个关键点......我对我的问题进行了更新,你会这么好心考虑一下吗?跨度>
    • @vanhelgen:您想要实现的是拥有Per Graph 的生活方式。对此没有 OOTB 支持,但可以使用 Simple Injector 支持的任何Scoped lifestyles 进行模拟。
    • @vanhelgen:您在哪种类型的应用程序中运行,为什么不能让ServiceAServiceB 有一个范围的生活方式?
    猜你喜欢
    • 1970-01-01
    • 2016-01-07
    • 1970-01-01
    • 1970-01-01
    • 2022-01-15
    • 2021-08-06
    • 2018-09-03
    • 2021-11-27
    • 2022-08-05
    相关资源
    最近更新 更多