在 Symfony 2 中制作胖模型 - 组合或继承以及如何配置我的模型层

cal*_*die 5 configuration domain-driven-design model symfony doctrine-orm

我已经到了 Symfony 2 / Doctrine 2 的重点,我开始意识到我们在应用程序中在服务和控制器中构建了太多的业务逻辑 - 而在模型中构建的业务逻辑还不够。

我们希望向我们的模型引入配置(以修改行为),从而可能使模型直接访问服务以执行其行为。

我注意到以下问题的答案完全错误,并被标记为正确,有 8 票赞成 - 所以我知道我们到目前为止所采取的方法(贫血模型)被许多 Symfony 认为是“正确”的做事方式2 个用户。在阅读了更多领域驱动设计之后,我知道情况并非如此。

Symfony2 MVC:我的代码属于哪里?

我看到很多捆绑包定义了模型中的行为并在实体/文档中扩展了它。这种模式在一定程度上有效——但我认为我们需要引入一个额外的阶段。我们模型的某些行为是可选的,并且具有该行为将取决于我们的应用程序中注册了哪些附加捆绑包(因此包含 X 捆绑包将允许应用程序执行更多操作)。一个例子。

我们有一个订单对象,目前它与快递包中的实体具有双向关系,这意味着存在硬依赖关系。我想将其解耦,并让快递包选择性地向订单添加行为。考虑这个方法调用。

// no courier bundle is registered
$order->getShippingMethods();
// throws NoAvailableShippingMethodsException;

// one bundle registered
$order-getShippingMethods();
// returns an array with one shipping method

etc....
Run Code Online (Sandbox Code Playgroud)

现在我们有一个 OrderProvider 服务,它位于实体管理器之上 - 所以如果你调用

$orderProvider->GetOrder($id);
Run Code Online (Sandbox Code Playgroud)

您只需从数据库“直接”返回实体即可。我的问题是其他人在这里使用什么模式?我正在考虑将所有“业务逻辑”移动到实体扩展的模型类中,让服务层将实体拉出(实体是具有数据库和获取器中的属性的哑记录),然后使用配置(被注入到 OrderProvider 服务中的配置),这将修改模型的行为。对于给出的示例,我可能会做类似的事情(在 OrderProvider 内)..

// trimmed down for example purposes by removing exceptions etc.
public function getOrder($id) 
{
    $order = $this->orderRepository->findOneById($id);
    if ($this->couriers){
       $order->addCouriers($couriers);
    }
    return $order;
}

// this function would be called by the courier bundle automatically using semantic configuration / tags / setter injection
public function addCourier(CourierInterface $courier)
{
    $this->couriers[] = $courier;
}
Run Code Online (Sandbox Code Playgroud)

我的另一个选择是创建一个新类型的对象 - 它装饰基本订单并且已经配置(因为它本身将被定义为 DIC 中的服务)并将订单注入其中。差异很微妙,两种方法都可以工作,但我想知道哪一种是最好的路径。

最后,我对这一切有一个无法解决的问题。如果我的基本订单实体与其他实体有关系并且需要配置这些实体 - 这应该发生在哪里?例如,如果我这样访问我的客户。

$order->getCustomer();
Run Code Online (Sandbox Code Playgroud)

我得到客户(实体)。但我可能也需要向客户对象添加一些配置 - 就像

$customer->getContactMethods();
Run Code Online (Sandbox Code Playgroud)

现在,此方法的行为可能会有所不同,具体取决于我的应用程序是否注册了 twitter 捆绑包、facebook 捆绑包或其他东西。鉴于上面的示例,我不会获得充分配置的客户 - 而是“普通”基本实体。我能看到的唯一方法是切断需要配置的实体之间的关系并从 CustomerProvider 服务中提取实体:

$customerProvider->getCustomerByOrder($order);
Run Code Online (Sandbox Code Playgroud)

在我看来,这似乎是从模型层中删除信息,并重新依赖于使用多个服务来完成简单的任务(我试图摆脱这种情况)。想法和资源链接表示赞赏。

编辑:相关 - 我每天都会看到第一个答案中列出的缺点,这就是我问这个问题的原因 - >贫血领域模型:优点/缺点

eul*_*rfx 1

项目的复杂性似乎在于模块化要求 - 应用程序行为必须可通过捆绑包进行扩展。我不熟悉 Symfony 2 / Doctrine 2,但典型的 DDD 策略是尝试确保订单和客户等域实体不知道捆绑配置。换句话说,周围的服务不应该向实体添加特定于捆绑包的行为。将捆绑意识的责任委托给实体将使它们变得过于复杂。构建实体类层次结构来支持广泛的行为也太复杂。相反,这种可扩展性应该由应用程序服务来管理。应用程序服务将确定加载哪些包并编排适当的实体作为结果。

另一个需要考虑的战略模式是有界上下文。是否可以将您的应用程序划分为与模块对齐的有界上下文?例如,为了解决$order-getShippingMethods()方法,您可以创建两个 BC,一个有包含方法的订单模型,getShippingMethods()另一个没有方法。拥有两个模型可能看起来违反了DRY,但如果模型代表不同的事物(即具有运输数据的订单与没有运输数据的订单),那么实际上不会重复任何内容。