领域驱动设计和工厂类的角色

62

我不清楚工厂类的职责和责任是什么。我知道工厂类应该负责创建域对象(聚合根)及其关联的实体和值对象。

但是对我来说不清楚的是,在DDD架构中,工厂“层”应该位于哪里?工厂应该直接调用存储库以获取其数据还是服务库?

在以下框架中,工厂应该放在哪里:
UI > App > Domain > Service > Data

此外,由于工厂是唯一允许进行对象创建的地方,如果您想在数据层和服务层中创建对象,是否会出现循环引用?

如果工厂类的角色是对象创建,那么服务层有什么好处?

我问了很多问题,感谢任何回答。我缺少一个演示如何将DDD项目中所有层组合在一起的示例应用程序...有类似的东西吗?


1
就示例而言,最近的http://dddsample.sourceforge.net/是我找到的最好的示例。非常完整。 - moffdub
6个回答

73
但我不清楚在DDD架构中,工厂“层”位于哪里? 工厂是否应直接调用存储库以获取其数据或服务库? 工厂应该是构建域对象的一站式商店。 需要执行此操作的代码的任何其他部分都应使用工厂。 通常,至少有三个数据源用作域对象构建的工厂的输入:来自UI的输入,持久性查询的结果和具有域含义的请求。 因此,回答您的具体问题,存储库将使用工厂。 这是一个例子。 我在这里使用Holub的Builder模式编辑:请忽略使用此模式。 我已经开始意识到它与DDD工厂混合得不太好
// domain layer
class Order
{
    private Integer ID;
    private Customer owner;
    private List<Product> ordered;

    // can't be null, needs complicated rules to initialize
    private Product featured; 

    // can't be null, needs complicated rules to initialize, not part of Order aggregate
    private Itinerary schedule; 

    void importFrom(Importer importer) { ... }

    void exportTo(Exporter exporter) { ... }

    ... insert business logic methods here ...

    interface Importer
    {
        Integer importID();
        Customer importOwner();
        Product importOrdered();
    }

    interface Exporter
    {
        void exportID(Integer id);
        void exportOwner(Customer owner);
        void exportOrdered(Product ordered);
    }
}

// domain layer
interface OrderEntryScreenExport { ... }

// UI
class UIScreen
{
    public UIScreen(OrderEntryDTO dto) { ... }
}

// App Layer
class OrderEntryDTO implements OrderEntryScreenExport { ... }

这是OrderFactory的样例代码:
interface OrderFactory
{
    Order createWith(Customer owner, Product ordered);
    Order createFrom(OrderEntryScreenExport to);
    Order createFrom(List<String> resultSets);
}

特色产品的逻辑和行程生成都在OrderFactory中。

现在,以下是每个实例中如何使用工厂。

在OrderRepository中:

public List<Order> findAllMatching(Criteria someCriteria)
{
    ResultSet rcds = this.db.execFindOrdersQueryWith(someCriteria.toString());
    List<List<String>> results = convertToStringList(rcds);

    List<Order> returnList = new ArrayList<Order>();

    for(List<String> row : results)
        returnList.add(this.orderFactory.createFrom(row));

    return returnList;
}

在您的应用层中:
public void submitOrder(OrderEntryDTO dto)
{
    Order toBeSubmitted = this.orderFactory.createFrom(dto);

    this.orderRepo.add(toBeSubmitted);

    // do other stuff, raise events, etc
}

在你的领域层中,也许需要进行单元测试:

Customer carl = customerRepo.findByName("Carl");
List<Product> weapons = productRepo.findAllByName("Ruger P-95 9mm");
Order weaponsForCarl = orderFactory.createWith(carl, weapons);

weaponsForCarl.place();

assertTrue(weaponsForCarl.isPlaced());
assertTrue(weaponsForCarl.hasSpecialShippingNeeds());

在以下框架中,工厂类应该放在哪里?UI > App > Domain > Service > Data

Domain层。

同时,由于工厂类是唯一允许对象创建的地方,如果您想在数据层和服务层中创建对象,不会出现循环引用吗?

在我的示例中,所有依赖关系都从上到下流动。我使用依赖反转原则(PDF链接)来避免您所说的问题。

如果工厂类的角色是对象创建,那么服务层有什么好处呢?

当您有逻辑无法适应任何单个领域对象时,或者您有一个涉及协调多个领域对象的算法时,请使用服务。服务将封装任何不适合放在其他位置的逻辑,并在适合的地方委托给领域对象。

在我画的这个例子中,我想为订单制定行程需要涉及多个领域对象。OrderFactory可以委托给这样的服务。

顺便说一句,您描述的层次结构应该是 UI > App > 领域服务 > Domain > 基础设施(Data)

我问了很多问题,感谢您的回答。我缺少的是一个演示如何将领域驱动设计项目中的所有层组合在一起的示例应用程序...有类似的吗?

没有直接相关的完整示例应用程序,但可以找到一些开源的应用程序,例如eShopOnWeb和NopCommerce等。

应用领域驱动设计和模式作者Jimmy Nilsson的书是Eric Evans的领域驱动设计的很好补充。它有很多代码示例,虽然我不知道是否强调分层。分层可能很棘手,几乎是DDD之外的一个主题。

在Evans的书中,有一个非常小的分层示例,您可能想要查看。分层是一种企业模式,Martin Fowler撰写了企业应用架构模式,您也可能会发现它有用。


4
我通常将创建新对象的工厂和从持久化中重新加载实体的构建器分离,以便持久化问题不会出现在工厂的接口中。 - thinkbeforecoding
2
很棒的回答,但我有一个问题。在应用层中使用工厂(从DTO创建对象)的情况下,如何将工厂放在域层中?在我的理解中,DTO对应于用例,并且在UI /应用程序层中使用。我目前正在处理应用程序层,正是这个问题引导我来到这篇文章 :-) - Arcord
Domain 对象的实现中能否引用 Repository?例如,Order 是否可以使用 OrderRepositoryInterface 来检索订单信息?假设我们有一个 Orders 集合,其中有一个 getThoseOfPersonWithName("David") 方法,返回所有拥有者名为 David 的订单列表。在 getThoseOfPersonWithName(String name) 方法中,这个 Orders 集合能否引用 OrderRepository 实例,并使用 criteria = new Criteria(); criteria.add("name", name); return this.orderRepo.findAllMatching(criteria); 这样的代码?或者这种做法是否被认为是 DDD 中的不当行为?谢谢。 - tonix

16

仓库(repository)和工厂(factory)的区别在于,仓库代表了一个抽象的持久化存储,而工厂则负责构建对象。

因此,举个例子,假设我要注册一个用户,我会从工厂获取我的用户对象。

IUser user = userFactory.Create(name, email);

然后将其传递给存储库,该存储库将负责处理它。

userRepository.Insert(user);

在DDD中,工厂可以被看作是一种隐藏new的方式,它抽象了实例化的细节。这样你就可以非常有效地按照接口而不是具体实现来编程。

此外,这使得仓储集中于它们的实体类型,从而使用泛型变得非常强大。


7

也许我会冒险,但我不会强调设计模式,除非我真的卡住了。我只是按照自己的想法构建系统,并重构它,直到第二天有些意义。

以下情况下我会使用工厂模式:

  • 某个层需要经常创建一个对象并且
  • 只有该层知道何时创建对象或如何自定义对象并且
  • 只有另一些层确切地知道要创建什么对象。

2
我同意这可能是最合乎逻辑和“最有可能导致完成项目”的方法...随着时间的推移,让这些模式自行浮现,只要确保能够识别它们的能力存在即可...我自己也在学习这个。 - hanzolo
我猜很多人在设计模式的黄金锤期经历过这个阶段... :-) - inf3rno
就像你所说的,通常会自然而然地使用已知的设计模式。但是了解和应用它们的一个非常好的原因是,它还可以让其他开发人员在加入代码库时快速理解正在发生的事情。如果你有一个工厂类,而不是搜索每个不同的对象实例,那么对于未经培训的程序员来说,这是一个清晰的查看点。 - Mathieson

4

DDD的专家和我可能会有争议,但“工厂”类的基本思想是发出领域对象。然后,领域对象访问数据并成为您正在使用的领域的“模型”的一部分。

应用程序包含UI等内容,它不是继承层次结构。

小心使用“kind of”操作符;很多人认为他们可以使用继承,因此必须使用继承。“组合”(“包含”或“有一个”,而不是“是一个”)通常是更好的选择。

更新

不一定。考虑一下领域告诉您的信息:例如,“我需要一个产品,我知道产品的产品编号。当产品工厂完成时,我希望得到一个完全填充了表示我的产品的有效产品对象。”创建产品对象时,它需要做什么才能成为有效的代表特定产品的产品对象?


我不确定我理解了。你的意思是在我的领域类中有一个方法来获取我的数据,例如Product p = ProductFactory.CreateProduct(); 然后我调用p.GetData()吗? - Th3Fix3r
2
聚合优于继承,这是《设计模式》四人组的重要信息!请点赞支持! - Mathieson

2
工厂应该直接从仓库获取数据还是从服务库获取数据呢?
我认为两者都不应该,如果可能的话,最好直接传递所需信息。
工厂在以下框架中的位置:UI> App> Domain> Service> Data?
不确定这种分层来自哪里,DDD中的层不是固定的,但我认为你最好关注这种风格:
UI> App> Domain
在域内,您有多种类型的对象,并且我会为它们之间的关系设置规则:
工厂应该被传递其需要完成工作的所有内容,因此在大多数情况下,我不会让它们调用其他服务或存储库。
在大多数情况下,实体不应联系存储库,而应该由服务(或其他上层)负责此工作。
实体不应调用服务,服务位于实体/值对象/规范的顶部,并根据需要协调它们。
域内的服务用于协调,它们不包含重要的域/业务行为。
如果工厂类的作用是创建对象,那么服务层有什么好处?
Eric在书中解释得很好,因此我会参考它,但归根结底,如果您具有跨聚合行为或不适合一个聚合的行为(例如书中的帐户示例),则非常好。

工厂应该被传递完成工作所需的一切,因此在大多数情况下,我不希望它们调用其他服务或存储库。如果我有一个名为“public Order GetOrderById(int id)”的工厂方法,那么实现代码中会是什么?它必须调用存储库吗? - Th3Fix3r
2
Th3Fix3r - 我的理解是,你的 GetOrderById 成员应该在你的仓储中,并从一个服务中调用,该服务正在协调 Order 对象的填充。 - Ed Blackburn
我认为仓库是聚合根的唯一有效来源,因此服务也使用仓库而不是工厂。我仍然不理解在这种情况下工厂的目的,我认为仓库包含创建代码。 - inf3rno

1
我认为应用服务层有创建新实体的责任,通过调用工厂来完成。因此,在应用服务模块中定义了实体工厂接口,并且实际实现是基础设施服务,就像持久化一样。关于通用应用程序服务与领域服务与基础设施服务的清晰概念,我通过观看Bob Martin Ruby Midwest 2011获得了很多启示。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接