【问题标题】:Confusion over Implementation of Business Logic in Clean Architecture [closed]对干净架构中业务逻辑实现的困惑[关闭]
【发布时间】: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

我的目标是实现以下目标:

  1. 为每个域对象拥有一个存储库类(从基础/通用存储库类继承)。
  2. 我希望我的存储库只返回域实体,而不是 DTO/ViewModel。
  3. API 项目中的控制器方法不应直接调用存储库。
  4. API 项目中的控制器方法不应包含任何业务逻辑 - 最多它们应该接受 ViewModel 作为请求对象,将其传递给某个代理或中间类,然后它们可以运行任何验证或自定义业务逻辑,并将自定义响应对象(不是域实体)返回给控制器方法,它将简单地显示在响应中。
  5. 业务逻辑服务或代理应驻留在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 是因为我觉得它可以让我有机会在一个事务中处理多个存储库对象,如果需要(它会)出现。

问题:

  1. 我知道这是主观的,但我这样做是对还是错?您建议我改变什么或有什么不同的想法?
  2. 在使用 UnitOfWork 以及将业务逻辑外包给另一个服务/处理程序方面,我是否抽象太多?
  3. 我是否处理了正确的对象,我这样做是否正确?
  4. 上面的CourseListHandler 只处理一个单一 用例,即从存储机制中获取简化的课程列表。看到这是针对 API 的,控制器(端点)中的每个操作都可能有许多用例处理程序,并且需要注入控制器的 UseCaseHandler 类的数量会变得非常大。我知道 SOLID 原则告诉我们,在这种情况下,一个班级应该只负责做一件事,但这不被认为是“过度设计”吗?
  5. 从 4 开始,使用“Service”类而不是 UseCaseHandler 不是更好吗?该类具有多种方法来满足给定控制器中每个潜在操作的需求?

我知道这些都是非常业余的问题,是的,我应该更多地阅读它,但老实说,我阅读的越多,我在实施清洁架构方面遇到的不同看法就越多。作为 StackExchange 成员,您对此的见解对我来说非常宝贵,非常感谢!

这不是 .NET Core 解决方案 - 它使用 .NET 框架 4.7,我计划使用 Unity 进行 DI/IoC。

【问题讨论】:

  • 根据您自己的确认,我将此标记为主要基于意见。话虽如此,你在#5 中提到的服务类是我个人会做的,有点。我见过这样的东西通常在“管理器”类中实现,这些类处理与某种逻辑分组相关的功能。以这种方式编写代码的最简单方法是考虑真实实体。例如,当您去注册办公室时,您会与可以为您做事的代理(或经理)交谈,例如为您提供课程列表并为您注册。然后你可以将这个类注入你的控制器。
  • 你好胡安。感谢您的评论
  • 你可能会在Software Engineering上得到一个很好的答案。
  • 我在这里一般同意@JuanR,但还要补充一点,单一责任原则并不意味着它完全做一件事。想想汽车方向盘的常见例子。它不只是左转。更广泛地说,它通常负责转向,包括向左转、向右转,甚至保持直线行驶。
  • 模式不一定是您必须为其编写代码的东西。这是您必须知道的事情,而且大多数时候,只要能够在计算机迷雾中识别即可。在现代环境(C# 等)中,它通常存在,您可以在没有任何额外工作或抽象的情况下使用它(即:工作单元 => ADO 事务、实体框架等)

标签: c# asp.net-mvc clean-architecture


【解决方案1】:

哇,说了这么多,让我试着按顺序回答。

  1. 总的来说,我认为您的想法比大多数人都好。我会查看您的核心项目并尝试将您的域模型定义为实体或值对象。当然,您可能已经在执行此操作而无需查看我无法评估的代码。 一种。另外,我希望您的 API 拥有您的数据传输对象。我不希望这些会在您的核心项目中一直存在。

  2. 关于是否抽象出工作单元模式的想法是我见过的一个想法,如果你在 C 语言中并且你已经在使用实体框架,那么可能会有自己实现工作单元模式几乎没有什么收获,但如果您决定使用另一个 ORM(对象关系映射器),那么自己抽象它不会将您的项目与实体框架或任何其他 ORM 联系起来。
    一种。抽象到服务可能是正确的,这取决于我经常发现我可以将很多逻辑放在域模型本身中的情况。在这些情况下,领域模型知道他们可以保存一些东西,比如学生可以为自己添加一个类,但实际实现方式的实现并不是领域模型的关注点,它只是在一个事件中广播,说 John Smith 添加了代数级别 3。您的存储库应设置为能够正确拦截和保存该事件。

  3. 我看不到您对一次性用品的实施。所以我不知道你是否做得正确。但总的来说,你的存储库应该确保你的数据库上下文被释放,只是确保在你实现接口时它实际上正在这样做。
  4. 如果您在他的书 clean clode 或他的大部分 YouTube 视频中跟进 Bob 叔叔通常给出的理解,那么您的第 4 个问题与坚实的设计原则有关,那么我认为大多数人在该主题上所教的内容。他的论点不是一个班级只需要做一件事而是只需要对一个人负责所以显然可以有一个学生存储库添加学生带走学生,更新学生和精英学生你不需要为每种情况创建一个单独的类或处理程序,只是不同的方法。他的论点是,企业中最终会有一个人可以更改该代码并确保每个班级基本上都有一个大师。再看看 S 的实体设计,要么 Bob 叔叔讲干净的建筑,要么观看他的许多 YouTube 视频。
  5. 是的,我认为在这种情况下提供服务是正确的。您还可以查看“调解器”工具,它有助于解决这些问题。

所以这里有很多内容要介绍,您也知道这一点。我将深入了解了解对象之间的差异和值对象,这将帮助您指导构建核心应用程序的许多方式。通常也只需考虑您的依赖项在哪里。这是 DDD 设计的核心。并且与您将看到的许多设计模式相反,例如传统的分层或 3 层 Web 应用程序。让您的代码作为项目的核心,尽可能少地使用依赖项,最好是 0。这将防止您的 Web 传输对象或数据库影响您的代码。就像你说的,你仍然需要学习很多关于 DDD 的知识,但很明显你已经掌握了基础知识

【讨论】:

  • 谢谢巫师之锤。今晚请给我一些时间来完成您的回答,如果我有任何问题,我会在这里回复。
  • 要学习的东西很多,它与我们其他人习惯的完全不同。您可能会在 softwareengineering.stackexchange.com 网站上获得更多关注。我发现当人们问这种类型的问题时它会更有帮助。
猜你喜欢
  • 2010-12-07
  • 1970-01-01
  • 2019-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多