我正在使用ASP.NET MVC练习DDD,并且我的控制器对不同的服务和存储库有很多依赖性,测试变得非常繁琐。
通常,我为每个聚合根提供服务或存储库。考虑一个页面,其中列出客户,以及订单以及不同包和卖家的下拉列表。所有这些类型都是聚合根。为此,我需要 CustomerService , OrderService , PackageRepository 和 UserRepository 。像这样:
public class OrderController {
public OrderController(Customerservice customerService,
OrderService orderService, Repository<Package> packageRepository,
Repository<User> userRepository)
{
_customerService = customerService
..
}
}
想象一下渲染更复杂视图所需的依赖项和构造函数参数的数量。
也许我正在接近我的服务层错误;我可以有一个CustomerService来处理所有这些,但我的服务构造函数将会爆炸。我想我太过违反了SRP。
答案 0 :(得分:4)
我认为我太过违反了SRP。
宾果
我发现使用命令处理层可以使我的应用程序架构更清晰,更一致。
基本上,每个服务方法都成为命令处理程序类(并且方法参数成为命令类),并且每个查询也是它自己的类。
这实际上不会减少您的依赖关系 - 您的查询可能仍然需要相同的服务和存储库来提供正确的数据;然而,当使用像Ninject或Spring这样的IoC框架时,它们无关紧要,因为它们将注入整个链所需的内容 - 并且测试应该更容易,因为对特定查询的依赖比依赖更容易填充和测试在具有许多边缘相关方法的服务类上。
此外,现在Controller与其依赖关系之间的关系是明确的,逻辑已从Controller中删除,查询和命令类更侧重于他们的个人职责。
是的,这确实会引起一些类和文件的爆炸。采用适当的面向对象编程将倾向于这样做。但是,坦率地说,更容易找到/组织/管理 - 在几十个其他半相关函数的文件中的函数或在几十个半相关文件的目录中的单个文件。我认为后者会放弃。
Code Better had a blog post recently几乎与我在MVC应用程序中组织控制器和命令的首选方式相匹配。
答案 1 :(得分:1)
您可以使用RenderAction
轻松解决此问题。只需创建单独的控制器或在这些控制器中引入子操作。现在在主视图中调用具有所需参数的渲染操作。这将为您提供一个很好的复合视图。
答案 2 :(得分:0)
为什么没有为此方案提供服务来为您返回视图模型?这样,您在控制器中只有一个依赖项,尽管您的服务可能具有单独的依赖项
答案 3 :(得分:0)
更新:我终于有了一些可用的时间,所以我最终在下面的帖子中创建了我正在讨论的实现。我的实现是:
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
传递给我的控制器构造函数,现在我能够保持构造函数的清洁,同时仍允许简单(灵活)的单元测试。如果您有兴趣,可以在我的博客blog找到更多详细信息。
<小时/> 我已经注意到我的MVC应用程序中出现了同样的问题,你的问题让我想到了我想如何处理这个问题。由于我正在使用命令和查询方法(其中每个操作或查询都是一个单独的服务类),我的控制器已经失控,以后可能会更糟糕。
在考虑了这一点后,我认为我要看的路线是创建一个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
类,你根本不必弄乱控制器的构造函数,你只需要调用{{ 1}}用于操作方法中的预期服务。
最后,这种方法允许你仍然模拟服务(使用IoC并将它们直接传递给控制器的构造函数的一个重要原因),只需在测试代码中使用模拟服务创建一个新的_factory.GetService()
传入(其余为空)。
我认为这将在灵活性和可测试性的最佳世界中保持良好的平衡,并将服务实例化保持在一个位置。
输入完全后我真的很高兴回家并在我的应用程序中实现:)
答案 4 :(得分:0)