DDD 和构造函数爆炸

发布于 2024-11-05 05:15:29 字数 795 浏览 7 评论 0原文

我正在使用 ASP.NET MVC 练习 DDD,遇到这样的情况:我的控制器对不同的服务和存储库有许多依赖项,并且测试变得非常乏味。

一般来说,我为每个聚合根都有一个服务或存储库。考虑一个页面,该页面将列出客户及其订单以及不同套餐卖家的下拉列表。所有这些类型都是聚合根。为此,我需要一个 CustomerServiceOrderServicePackageRepositoryUserRepository。像这样:

public class OrderController {
    public OrderController(Customerservice customerService, 
      OrderService orderService, Repository<Package> packageRepository, 
      Repository<User> userRepository) 
    {
        _customerService = customerService
        ..
    }
}

想象一下渲染更复杂的视图所需的依赖项和构造函数参数的数量。

也许我的服务层处理方式是错误的;我可以有一个 CustomerService 来处理这一切,但我的服务构造函数将会爆炸。我认为我太违反了 SRP。

I'm practicing DDD with ASP.NET MVC and come to a situation where my controllers have many dependencies on different services and repositories, and testing becomes very tedious.

In general, I have a service or repository for each aggregate root. Consider a page which will list a customer, along with it's orders and a dropdown of different packages and sellers. All of those types are aggregate roots. For this to work, I need a CustomerService, OrderService, PackageRepository and a UserRepository. Like this:

public class OrderController {
    public OrderController(Customerservice customerService, 
      OrderService orderService, Repository<Package> packageRepository, 
      Repository<User> userRepository) 
    {
        _customerService = customerService
        ..
    }
}

Imagine the number of dependencies and constructor parameters required to render a more complex view.

Maybe I'm approaching my service layer wrong; I could have a CustomerService which takes care of all this, but my service constructor will then explode. I think I'm violating SRP too much.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

不奢求什么 2024-11-12 05:15:29

我认为我过多地违反了 SRP。

宾果游戏。

我发现使用命令处理层使我的应用程序架构更清晰、更一致。

基本上,每个服务方法都成为一个命令处理程序类(并且方法参数成为一个命令类),并且每个查询也是它自己的类。

这实际上不会减少您的依赖性 - 您的查询可能仍然需要那些相同的服务和存储库来提供正确的数据;然而,当使用像 Ninject 或 Spring 这样的 IoC 框架时,这并不重要,因为它们会注入整个链所需的内容 - 并且测试应该更容易,因为对特定查询的依赖比依赖更容易填充和测试在具有许多边缘相关方法的服务类上。

另外,现在控制器及其依赖项之间的关系很清晰,逻辑已从控制器中删除,查询和命令类更专注于各自的职责。

是的,这确实会导致类和文件的爆炸。采用正确的面向对象编程往往可以做到这一点。但是,坦率地说,更容易查找/组织/管理的是数十个其他半相关函数的文件中的函数或数十个半相关文件的目录中的单个文件。我认为后者毫无疑问。

更好的代码最近有一篇博客文章几乎符合我在 MVC 应用程序中组织控制器和命令的首选方式。

I think I'm violating SRP too much.

Bingo.

I find that using a command processing layer makes my applications architecture cleaner and more consistent.

Basically, each service method becomes a command handler class (and the method parameters become a command class), and every query is also its own class.

This won't actually reduce your dependencies - your query will likely still require those same couple of services and repositories to provide the correct data; however, when using an IoC framework like Ninject or Spring it won't matter because they will inject what is needed up the whole chain - and testing should be much easier as a dependency on a specific query is easier to fill and test than a dependency on a service class with many marginally related methods.

Also, now the relationship between the Controller and its dependencies is clear, logic has been removed from the Controller, and the query and command classes are more focused on their individual responsibilities.

Yes, this does cause a bit of an explosion of classes and files. Employing proper Object Oriented Programming will tend to do that. But, frankly, what's easier to find/organize/manage - a function in a file of dozens of other semi-related functions or a single file in a directory of dozens of semi-related files. I think that latter hands down.

Code Better had a blog post recently that nearly matches my preferred way of organizing controllers and commands in an MVC app.

甜妞爱困 2024-11-12 05:15:29

那么您可以通过使用 RenderAction 轻松解决这个问题。只需创建单独的控制器或在这些控制器中引入子操作即可。现在在主视图中使用所需的参数调用渲染操作。这将为您提供一个很好的合成视图。

Well you can solve this issue easily by using the RenderAction. Just create separate controllers or introduce child actions in those controllers. Now in the main view call render actions with the required parameters. This will give you a nice composite view.

阳光下的泡沫是彩色的 2024-11-12 05:15:29

为什么不为这种情况提供一个服务来为您返回视图模型呢?这样,尽管您的服务可能具有单独的依赖项,但控制器中只有一个依赖项

Why not have a service for this scenario to return a view model for you? That way you only have one dependency in the controller although your service may have the separate dependencies

吻风 2024-11-12 05:15:29

.net 中的依赖注入一书建议引入“外观服务”,如果您觉得构造函数参数太多,您可以将相关服务组合在一起,然后注入外观。

the book dependency injection in .net suggests introducing "facade services" where you'd group related services together then inject the facade instead if you feel like you have too many constructor parameters.

執念 2024-11-12 05:15:29

更新:我终于有了一些空闲时间,所以我最终为我在下面的帖子中讨论的内容创建了一个实现。我的实现是:

public class WindsorServiceFactory : IServiceFactory
{
    protected IWindsorContainer _container;

    public WindsorServiceFactory(IWindsorContainer windsorContainer)
    {
        _container = windsorContainer;
    }

    public ServiceType GetService<ServiceType>() where ServiceType : class
    {
        // Use windsor to resolve the service class.  If the dependency can't be resolved throw an exception
        try { return _container.Resolve<ServiceType>(); }
        catch (ComponentNotFoundException) { throw new ServiceNotFoundException(typeof(ServiceType)); }
    }
}

现在所需要做的就是将我的 IServiceFactory 传递到我的控制器构造函数中,现在我可以保持构造函数干净,同时仍然允许简单(灵活)的单元测试。更多详细信息可以在我的博客 博客 如果您有兴趣。


I have noticed the same issue creeping up in my MVC app, and your question got me thinking of how I want to handle this. As I'm using a command and query approach (where each action or query is a separate service class) my controllers are already getting out of hand, and will probably be even worse later on.

考虑到这一点后,我认为我要考虑的路线是创建一个 SerivceFactory 类,它看起来像:

public class ServiceFactory
{
    public ServiceFactory( UserService userService, CustomerService customerService, etc...)
    {
        // Code to set private service references here
    }

    public T GetService<T>(Type serviceType) where T : IService
    {
        // Determine if serviceType is a valid service type, 
        //  and return the instantiated version of that service class
        //   otherwise throw error
    }
}

请注意,我是在 Notepad++ 中临时写的,所以我很确定我GetService 方法的泛型部分在语法上是错误的,但这是总体思路。那么你的控制器最终将看起来像这样:

public class OrderController {
    public OrderController(ServiceFactory factory) {
        _factory = factory;
    }
}

然后你将让 IoC 实例化你的 ServiceFactory 实例,并且一切都应该按预期工作。

这样做的好处是,如果您意识到必须在控制器中使用 ProductService 类,则根本不必弄乱控制器的构造函数,只需调用 _factory.GetService() 在操作方法中获取您想要的服务。

最后,这种方法仍然允许您通过在测试代码中使用模拟服务创建一个新的 ServiceFactory 来模拟服务(这是使用 IoC 并将它们直接传递到控制器的构造函数的重要原因之一)传入(其余为空)。

我认为这将在灵活性和可测试性的最佳世界中保持良好的平衡,并将服务实例化保持在一个位置。

全部输入后,我真的很高兴回家并在我的应用程序中实现它:)

Update: I finally had some available time, so I ended up finally creating an implementation for what I was talking about in my post below. My implementation is:

public class WindsorServiceFactory : IServiceFactory
{
    protected IWindsorContainer _container;

    public WindsorServiceFactory(IWindsorContainer windsorContainer)
    {
        _container = windsorContainer;
    }

    public ServiceType GetService<ServiceType>() where ServiceType : class
    {
        // Use windsor to resolve the service class.  If the dependency can't be resolved throw an exception
        try { return _container.Resolve<ServiceType>(); }
        catch (ComponentNotFoundException) { throw new ServiceNotFoundException(typeof(ServiceType)); }
    }
}

All that is needed now is to pass my IServiceFactory into my controller constructors, and I am now able to keep my constructors clean while still allowing easy (and flexible) unit tests. More details can be found at my blog blog if you are interested.


I have noticed the same issue creeping up in my MVC app, and your question got me thinking of how I want to handle this. As I'm using a command and query approach (where each action or query is a separate service class) my controllers are already getting out of hand, and will probably be even worse later on.

After thinking about this I think the route I am going to look at going is to create a SerivceFactory class, which would look like:

public class ServiceFactory
{
    public ServiceFactory( UserService userService, CustomerService customerService, etc...)
    {
        // Code to set private service references here
    }

    public T GetService<T>(Type serviceType) where T : IService
    {
        // Determine if serviceType is a valid service type, 
        //  and return the instantiated version of that service class
        //   otherwise throw error
    }
}

Note that I wrote this up in Notepad++ off hand so I am pretty sure I got the generics part of the GetService method syntactically wrong , but that's the general idea. So then your controller will end up looking like this:

public class OrderController {
    public OrderController(ServiceFactory factory) {
        _factory = factory;
    }
}

You would then have IoC instantiate your ServiceFactory instance, and everything should work as expected.

The good part about this is that if you realize that you have to use the ProductService class in your controller, you don't have to mess with controller's constructor at all, you only have to just call _factory.GetService() for your intended service in the action method.

Finally, this approach allows you to still mock services out (one of the big reasons for using IoC and passing them straight into the controller's constructor) by just creating a new ServiceFactory in your test code with the mocked services passed in (the rest left as null).

I think this will keep a good balance out the best world of flexibility and testability, and keeps service instantiation in one spot.

After typing this all out I'm actually excited to go home and implement this in my app :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文