【问题标题】:S#arp Architecture: How to arrange Application ServicesS#arp 架构:如何安排应用服务
【发布时间】:2011-03-06 01:18:31
【问题描述】:

对于S#arp Architecture,我的理解是,对一种以上类型的实体进行操作的域逻辑(也称为业务逻辑)最好由应用程序服务层处理。

因此,应用程序服务中的类将需要访问存储库。大概然后您通过构造函数注入存储库。因为每个实体类型都有一类存储库,所以任何相当现实的任务都需要访问多个存储库。所以你可能有一个如下所示的应用程序服务类:

public class DogTasks
{
    public DogTasks(IRepository<Dog> dogRepository, 
            IRepository<Trick> trickRepository,
            IRepository<DogTrick> dogTrickRepository,
            IRepository<Lesson> lessonRepository)
    {
        // etc
    }

    public void TeachDogNewTrickAtALesson(int dogID, string trickName, int lessonID)
    {
        // etc
    }

    // other methods, etc

}

然后可以将这个Tasks 类注入到相关的控制器中。

到目前为止,我想我明白了。但我对以下情况感到不安:

    1234563一个全新的班级。向构造函数添加参数会扰乱许多单元测试,但增加新类似乎也不是一件好事。
  • 当控制器需要执行简单的存储库操作(如get)时,将存储库注入控制器以及应用程序服务类是有意义的。但后来我得到了同样的“改变构造函数参数”的问题。另一种选择似乎是只让 Application Services 层与 Repositories 一起玩,但随后您会在 Application Services 中添加大量样板代码来做非常简单的事情。

这些事情让我觉得我可能做错了。那么应该如何组织一个好的应用服务层呢?

例如你有很多班级,每个班级只做一项任务吗?或者你是否沿着实体线将相关任务聚集在一起?您如何处理需要大量存储库的任务?需要大量存储库来完成一项任务是否意味着该回到绘图板了?

【问题讨论】:

    标签: asp.net-mvc business-logic s#arp-architecture


    【解决方案1】:

    首先,我想反驳您的假设,即每个实体都需要自己的存储库。 Per, Eric Evans “领域驱动设计”

    存储库可以访问选定的聚合根。存储库禁止在聚合内部。

    以你的例子为例,一只狗已经学会了一套技巧。当你想给狗添加一个新把戏时,你会做这样的事情:

    var dog = dogRepository.Get(dogId);
    dog.Tricks.Add(newTrick);
    dogRepository.SaveOrUpdate(dog);
    

    当我需要一种新的应用程序服务方法,该方法使用我还没有的存储库组合时,

    我不确定你的意思。但我认为,如果您坚持使用存储库作为聚合根,您就不会遇到如此混乱的代码。

    另一种选择似乎是 只让应用服务 层玩存储库,但是 然后你会得到很多样板代码 添加到应用程序服务以 做非常简单的事情。

    控制器编排。将控制器视为 UI 的一部分,它们将您从一个页面移到另一个页面。我承认,对于简单的事情,将存储库注入控制器似乎更简单,但是当您的项目增长时,分离将有很大帮助,特别是如果您最终将另一个应用程序挂钩到您的任务层。将存储库置于控制器之外。

    例如你有很多课程吗 每人只做一项任务?还是你结块 相关任务一起沿着实体 线?你如何处理那些 需要很多存储库?做 需要很多存储库 任务意味着它的时间回到 画板?

    再次,我认为这可以追溯到定义聚合根。在一个任务中拥有 4-5 个存储库并不是什么大不了的事。我通常按​​照应用程序尝试执行的操作来组织我的任务,我的想法是,如果 UI 更改为外部 JSON 请求,您只需要调用正确的任务即可。

    希望这能回答您的问题。请随意将其发布到 Sharp 邮件列表中,您可能会在那里得到更好的回复。

    基于 cmets 编辑:

    查看 Who Can Help Me (https://github.com/sharparchitecture/Who-Can-Help-Me),了解如何使用 ApplicationServices/Tasks 层的示例。他们有一个相当小的领域模型,所以每个实体都有自己的任务。

    我认为您有点混淆了术语,或者我可能不清楚。 ApplicationServices 层背后的想法是进一步从领域层抽象 UI。存储库是域层实体,它们的知识不应该在控制器中。如果你最终换掉 ORM 甚至迁移到基于文档的存储系统,你会明白为什么这种抽象使它非常方便,你只需要确保你的 ApplicationServices 合同是有效的,而不必在里面捣乱控制器。

    但是,不要将 ApplicationServices 的需求作为一种面向未来的方式来混淆。它只是允许您的层之间进一步解耦,而解耦几乎总是一件好事。

    同样,对于一个您正在单独进行的项目,所有这些似乎都有些矫枉过正。当您与其他开发人员一起工作时,所有这些抽象都非常非常好。您可以让一个团队处理上游域问题,一个团队处理表示层,并且可以很好地分离关注点。

    【讨论】:

    • 我 100% 同意您的 cmets,我还要说您可能会将一些服务注入其他服务,以解决发送电子邮件等额外功能,我相信离开控制器是件好事尽可能小,仅与封装所有逻辑的服务交互。
    • 感谢有关聚合根的提醒。在我的情况下,我似乎将许多聚合根链接在一起(例如,向 Dog 添加 Trick 并记录它从中学到的教训,其中 Lesson 是另一个聚合根)。 Lasislav 关于抽象工厂的回答可能对我有所帮助。虽然我认识到这基本上是在说“所有应用程序服务都可以访问任何存储库”,但有些人可能会对此感到不满。
    • 我很高兴应用程序服务(以及它们下面的核心域对象)应该封装所有逻辑,但我还没有找到任何关于应用程序服务应该是什么样子的好例子(例如,每个任务一个类还是每个实体一个类?)而且我对层的层持怀疑态度。目前我正在为每个聚合根(DogTasks,LessonTasks)做一个类,但我觉得我基本上有在应用程序服务中重建域对象的危险,只是因为允许应用程序服务使用存储库和域对象不是。
    • 简而言之,我想我正在寻找一个很好的例子来说明应用程序服务应该做什么和不应该做什么。如果域对象不应该使用存储库,但它可以用于应用程序服务,为什么会这样以及对应用程序服务中的类结构有什么影响。
    • 添加了一个附录,希望它能解决问题。我要补充一点,如果项目足够小以至于这看起来真的很混乱,请不要担心。我真的强烈推荐前面提到的领域驱动设计书,以及 Larman 的“应用 UML 和模式”,这听起来不像是 DDD 书,但实际上涵盖了很多这些主题。
    【解决方案2】:

    您听说过抽象工厂模式吗?它很好地解决了这个问题:

    public interface IDalFactory
    {
      // One way
      IRepository<Trick> TrickRepository { get; }
      IRepository<Dog> DogRepository { get; }
      ...
    
      // Other way
      IRepository<T> GetRepository<T>();
    }
    
    public DogTasks
    {
      public DogTasks(IDalFactory dalFactory)
      {
        ...
      }
    }
    

    如何实现IDalFacotry 取决于您。我通常使用存储库的延迟初始化。创建存储库后,它会在内部存储和重用。每个 http 请求都会创建一个工厂实例。

    缺点是您无法控制暴露给您的应用程序服务的工厂。但那是你的选择。向构造函数或使用工厂添加新的存储库。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多