【发布时间】:2019-11-05 21:47:39
【问题描述】:
在过去的 2 周里,我一直在尝试在一个新应用中理解和实施 Clean Architecture。我已经阅读了 Robert Martin 关于该主题的博客文章以及许多其他文章 - 每篇文章都有自己的看法,这让我有点困惑。
请记住,我还没有掌握更深层次的 DDD 原则,例如有界上下文、值对象等,所以我试图让这个设计(以及我最初的理解)尽可能简单。我尝试使用 Repository/UnitOfWork 模式,这就是我的 Visual Studio 解决方案目前的样子(为简洁起见,我对其进行了一些简化):
[MyApp.Core]
[Entities]
Student.cs
Course.cs
[Exceptions]
[Interfaces]
[Repositories]
IStudentRepository.cs
ICourseRepository.cs
IGenericRepository.cs
IUnitOfWork.cs
[DTOs]
CourseInputModel.cs
CourseNameResponseModel.cs
StudentsInCoursesResponseModel.cs
[Services/UsesCases] <=== ??? not sure how to implement this
[MyApp.Infrastructure]
[Repositories]
GenericRepository.cs
CourseRepository.cs
StudentRepository.cs
MyApplicationDbContext.cs
UnitOfWork.cs
[MyApp.Api]
[Controllers]
IndexController.cs
我的目标是实现以下目标:
- 为每个域对象拥有一个存储库类(从基础/通用存储库类继承)。
- 我希望我的存储库只返回域实体,而不是 DTO/ViewModel。
- API 项目中的控制器方法不应直接调用存储库。
- API 项目中的控制器方法不应包含任何业务逻辑 - 最多它们应该接受 ViewModel 作为请求对象,将其传递给某个代理或中间类,然后它们可以运行任何验证或自定义业务逻辑,并将自定义响应对象(不是域实体)返回给控制器方法,它将简单地显示在响应中。
- 业务逻辑服务或代理应驻留在
Core项目中,因为它是业务应用程序规则的核心。因此Infrastructure将只包含在Core中创建的合约的实现。
IUnitOfWork 看起来像这样:
public interface IUnitOfWork : IDisposable
{
ICourseRepository Courses { get; }
IStudentRepository Students { get; }
int Complete();
}
我曾想过在我的Core 项目中实现UseCaseHandlers:
public class CourseListHandler
{
private readonly IUnitOfWork _uow;
public CourseListHandler(IUnitOfWork uow)
{
_uow = uow;
}
public List<CourseNameResponseModel> Execute()
{
using (_uow)
{
return _uow.Courses
.GetAll()
.Select(s => new CourseNameResponseModel() {
Id = s.Id,
Name = s.Name,
Level = s.Level
})
.OrderBy(o => o.Name)
.ToList();
}
}
}
然后将其注入相关的控制器类,并在其中一种操作方法中调用。我选择使用 UnitOfWork 是因为我觉得它可以让我有机会在一个事务中处理多个存储库对象,如果需要(它会)出现。
问题:
- 我知道这是主观的,但我这样做是对还是错?您建议我改变什么或有什么不同的想法?
- 在使用 UnitOfWork 以及将业务逻辑外包给另一个服务/处理程序方面,我是否抽象太多?
- 我是否处理了正确的对象,我这样做是否正确?
- 上面的
CourseListHandler只处理一个单一 用例,即从存储机制中获取简化的课程列表。看到这是针对 API 的,控制器(端点)中的每个操作都可能有许多用例处理程序,并且需要注入控制器的 UseCaseHandler 类的数量会变得非常大。我知道 SOLID 原则告诉我们,在这种情况下,一个班级应该只负责做一件事,但这不被认为是“过度设计”吗? - 从 4 开始,使用“Service”类而不是 UseCaseHandler 不是更好吗?该类具有多种方法来满足给定控制器中每个潜在操作的需求?
我知道这些都是非常业余的问题,是的,我应该更多地阅读它,但老实说,我阅读的越多,我在实施清洁架构方面遇到的不同看法就越多。作为 StackExchange 成员,您对此的见解对我来说非常宝贵,非常感谢!
这不是 .NET Core 解决方案 - 它使用 .NET 框架 4.7,我计划使用 Unity 进行 DI/IoC。
【问题讨论】:
-
根据您自己的确认,我将此标记为主要基于意见。话虽如此,你在#5 中提到的服务类是我个人会做的,有点。我见过这样的东西通常在“管理器”类中实现,这些类处理与某种逻辑分组相关的功能。以这种方式编写代码的最简单方法是考虑真实实体。例如,当您去注册办公室时,您会与可以为您做事的代理(或经理)交谈,例如为您提供课程列表并为您注册。然后你可以将这个类注入你的控制器。
-
你好胡安。感谢您的评论
-
你可能会在Software Engineering上得到一个很好的答案。
-
我在这里一般同意@JuanR,但还要补充一点,单一责任原则并不意味着它完全做一件事。想想汽车方向盘的常见例子。它不只是左转。更广泛地说,它通常负责转向,包括向左转、向右转,甚至保持直线行驶。
-
模式不一定是您必须为其编写代码的东西。这是您必须知道的事情,而且大多数时候,只要能够在计算机迷雾中识别即可。在现代环境(C# 等)中,它通常存在,您可以在没有任何额外工作或抽象的情况下使用它(即:工作单元 => ADO 事务、实体框架等)
标签: c# asp.net-mvc clean-architecture