S#arp架构:如何安排应用服务

发布于 2024-10-20 09:13:18 字数 1189 浏览 9 评论 0原文

对于 S#arp 架构,我的理解是,域逻辑(又名业务逻辑)在多种类型上运行实体的最好由应用程序服务层处理。

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

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 类注入到相关的控制器中。

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

  • 当我需要一个新的应用程序服务方法,该方法使用我还没有的存储库组合时,我必须在更改现有类之一的构造函数以接受新的存储库,或者完全开始一个新的类。向构造函数添加参数会扰乱许多单元测试,但增加新类似乎也不好。

  • 当控制器需要执行简单的存储库操作(如 get)时,将存储库注入控制器以及应用程序服务类中是有意义的。但后来我遇到了同样的“更改构造函数参数”问题。另一种选择似乎是只让应用程序服务层与存储库一起使用,但是随后您会在应用程序服务中添加大量样板代码来执行非常简单的操作。

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

例如,您是否有很多课程,每个课程只执行一项任务?或者您是否沿着实体线将相关任务聚集在一起?您如何处理需要大量存储库的任务?一项任务需要大量存储库是否意味着需要重新开始?

With S#arp Architecture, my understanding is that domain logic (aka business logic) that operates on more than one type of entity is best handled by the Application Services layer.

So the classes in Application Services will need access to the Repositories. Presumably then you inject the Repositories in via the constructors. Because there is one class of repository per entity type, any fairly realistic task is going to need access to several repositories. So you might have an Application Services class looking like this:

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

}

This Tasks class can then be injected into the relevant Controller.

So far, I think I get it. But I am perturbed by the following:

  • When I need a new Application Services method that uses a combination of repositories that I don't have yet, I have to choose between changing the constructor for one of my existing classes to accept the new repositories, or starting a new class altogether. Adding arguments to constructors upsets a lot of the unit tests, but proliferating new classes doesn't seem good either.

  • When Controllers need to do simple Repository operations (like a get) it makes sense to inject the repositories into the Controllers as well as the Application Services classes. But then I get the same 'changing constructor arguments' issue. The other alternative seems to be to only let the Application Services layer play with the Repositories, but then you get a lot of boilerplate code added to the Application Services to do very simple things.

These sorts of things make me think I might be doing it wrong. So how should a good Application Services layer be organised?

e.g. Do you have lots of classes that just do one task each? Or do you clump related tasks together along entity lines? How do you deal with tasks that need a lot of repositories? Does needing a lot of repositories for a task mean its time to go back to the drawing board?

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

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

发布评论

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

评论(2

吃兔兔 2024-10-27 09:13:18

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

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

根据你的例子,狗有一套它已经学会的技巧。当你想给狗添加新的技巧时,你可以这样做:

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

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

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

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

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

例如,你有很多课程吗?
每个人只做一项任务?或者你结成一团
沿着实体将相关任务放在一起
线?您如何处理以下任务
需要很多存储库?做
需要大量的存储库
任务意味着是时候回到
画板?

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

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

根据评论进行编辑:

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

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

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

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

First, I'd like to counter your assumption that each entity needs its own repository. Per, Eric Evans "Domain Driven Design"

Repositories give access to selected aggregate roots. Repositories are prohibited from the interior of an aggregate.

Given your example, a dog has a set of tricks that it has learned. When you want to add a new trick to the dog, you'd do something like this:

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

When I need a new Application Services method that uses a combination of repositories that I don't have yet,

I'm not sure what you mean by this. But I think if you stick to using repositories for aggregate roots, you're not going to run into such messy code.

The other alternative seems to be to
only let the Application Services
layer play with the Repositories, but
then you get a lot of boilerplate code
added to the Application Services to
do very simple things.

Controllers orchestrate. Think of controllers as a part of the UI, they move you from page to page. I will admit that for simple things, it seems simpler to just inject a repository into the controller, but when your project grows the separation will help a lot, especially if you end up having another application hook into your Tasks layer. Keep repositories out of controllers.

e.g. Do you have lots of classes that
just do one task each? Or do you clump
related tasks together along entity
lines? How do you deal with tasks that
need a lot of repositories? Does
needing a lot of repositories for a
task mean its time to go back to the
drawing board?

Again, I think this goes back to defining aggregate roots. Having 4-5 repositories in a task isn't that big of a deal. I usually organize my tasks by what the application is trying to do, with the idea that if the UI changes to, say, an external JSON request, you just need to call the right task.

Hope this answers your question. Feel free to post this on the Sharp mailing list, you might get a better response there.

Edit based on comments:

Check out Who Can Help Me (https://github.com/sharparchitecture/Who-Can-Help-Me) for an example of how to use the ApplicationServices/Tasks layer. They have a fairly small domain model so each entity has its own task.

I think you're confusing terminology a bit, or perhaps I'm being unclear. The idea behind an ApplicationServices layer is to further abstract the UI from the domain layer. Repositories are domain layer entities, and knowledge of them should not be in the controller. If you end up swapping out ORM or even moving to a document-based storage system, you'll see why this abstraction makes it really convenient, you just need to make sure your ApplicationServices contracts are working and don't have to muck about in the controllers.

But, don't confuse the need for ApplicationServices as a way of future proofing. It simply allows for further decoupling between your layers and decoupling is nearly always a good thing.

Again, for a project you're working on solo, all this might seem a bit of overkill. When you're working with other developers, all this abstraction is really, really nice. You can have a team working on upstream domain issues, and a team working on the presentation layer, and have a nice separation of concerns.

是伱的 2024-10-27 09:13:18

您听说过抽象工厂模式吗?它以一种很好的方式解决了这个问题:

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 请求都会创建一个工厂实例。

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

Have you heard about Abstract Factory pattern? It solves this problem in a nice way:

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)
  {
    ...
  }
}

It is up to you how do you implement IDalFacotry. I usually using lazy initialization of repositories. Once repository is created it is internally stored and reused. One factory instance is created per http request.

The cons is that you don't have control over factories exposed to your application service. But that are your choices. Adding new repositories to constructor or using factory.

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