【问题标题】:Service layer and model associations with Domain-Driven Design服务层和模型与领域驱动设计的关联
【发布时间】:2012-06-28 16:51:46
【问题描述】:

我正在设计 Web 应用程序的基础架构。该项目遵循领域驱动设计方法,因为业务模型和逻辑非常复杂。

该项目还旨在成为一个SOA项目(面向服务的架构)。所以我学到了很多关于服务以及如何围绕它构建项目的知识。

previous question of mine 之后,我有一个关于模型类中的关联的问题。

我知道模型类不应该知道和做任何与持久性相关的事情。但是,我很难确定模型类之间的关联情况。

例如:

  • Person
  • Car 类有一个驱动程序(例如)

getDrivergetCars 应该在哪里?

  1. 在模型类中:$car->getDriver()
  2. 在原始类型的服务层中:$personService->getPerson($car->getDriverId())
  3. 在服务层使用OOP:$carService->getDriver($car)

解决方案 1. 似乎更自然。我正在使用 Doctrine 2,因此模型关联是使用 DB 映射注释处理的。这样,模型就不会做任何与持久性相关的事情(即使它实际上是通过 Doctrine )。这是我最喜欢的解决方案,但是除了加载“汽车”列表之外,服务还有什么意义呢?

解决方案 2. 似乎很愚蠢,因为它抛弃了 OOP,并且模型/服务用户必须了解数据库模型才能获取关联(他必须知道此 ID 是“人员”ID)。而且他必须自己做关联。

解决方案 3. 比解决方案 2 稍微好一点,但仍然OOP 在哪里

所以,对我来说,解决方案 1. 是最好的。但是我在实际项目中看到了解决方案 2. 和解决方案 3. (有时混合在一起),所以我有疑问。

而当有附加参数时,问题就变得更复杂了,例如:

$person->getCars($nameFilter, $maxNumberOfResults, $offset);

(在这种情况下,它看起来真的很像 SQL 查询/持久性查询)

那么,在遵循领域驱动设计方法的项目中,模型/服务架构应该使用哪一个?使用 SOA,我的模型应该只是没有逻辑的“哑”数据容器吗?如果是这样,那么 DDD 方法在哪里?

【问题讨论】:

    标签: php model doctrine-orm domain-driven-design soa


    【解决方案1】:

    在 DDD 的上下文中,这是一个决定实体之间的关系是否通过直接对象关联与存储库来表达的问题。这两种方法都是有效的,并且取决于关系的性质。例如,在您的域中,一个人可能有很多汽车与他们相关联,并且从人员实体直接关联到汽车集实际上没有任何意义。请记住,实体的工作,或者更具体地说是聚合根,是保护不变量和执行业务规则。如果人员类中存在的任何行为都不需要与人员关联的汽车集,则没有理由将关联放在人员实体上。此外,如您的示例所示,可能需要过滤汽车查询。为了回答您的问题,我将代表人与汽车关系的责任放在repository 中。 SOA 与 DDD 正交,更侧重于如何访问和部署业务功能。从hexagonal architecture 也称为onion architecture 的角度考虑DDD 和SOA 之间的相互作用是有益的。您的域是核心,由一组应用程序服务封装,这些应用程序服务在您的域周围形成一个 API 外观。这些与 SOA 中的服务不同,后者是六边形/洋葱架构中的端口/适配器,它们将这些应用程序服务公开为 SOA 服务。

    【讨论】:

    • 好的,首先我明白我混合了 SOA 和应用程序服务。实际上,所有关于关联的问题似乎都依赖于一个实体是否是一个聚合根。如果是,我应该使用存储库。如果它是聚合的子实体,那么我应该使用对象关联。我说的对吗?
    【解决方案2】:

    如果您的项目是 DDD,我不明白您为什么需要模型/服务架构。 IMO 这会创建一个贫血模型,并且一切都是程序化的。

    现在,作为 DDD 意味着您不关心数据库。尽管(至少在逻辑上)您有 2 个模型:域和持久性。域模型以最自然的方式处理关联,最适合表示业务案例。 “有一个驱动程序”或有很多驱动程序是一种以 db 为中心的思想,在 DDD 中没有位置。持久性模型处理聚合根存储在数据库中的方式(这里是您定义 ORM 实体及其关系等的地方)。

    关于您的问题,首先重要的是上下文和目的。如果它严格用于查询(显示给用户),那么可以使用简单的模型,不需要 DDD 和业务规则。控制器可以直接向专用查询存储库请求数据,以 DTO 形式返回。

    如果你想更新人或汽车,那么在应用层(我通常使用基于命令的方法,所以在我的例子中,所有这些都发生在命令处理程序中,但在架构上它仍然是应用层的一部分) 您可以从(域)存储库中检索最适合该任务的 AR。域存储库知道 getPerson($id) 应该返回域实体,而不是返回简单 DTO 的查询存储库。

    $person=$repo->getPerson($id);
    //do stuff
     $repo->save($person);
     //optionally raise event (if you're using the domain events apprach) 
    

    但棘手的是在什么情况下决定什么是 AR。一辆车在什么情况下只有一个司机?司机真的属于汽车吗?有楼主的概念吗?您有 Person 类,但一个人可以是司机或所有者(如果是租赁公司,则不是)。如您所见,它在很大程度上取决于域,只有在您清楚地了解域之后,您才能开始考虑如何存储数据以及存储库返回什么对象(实体)。

    【讨论】:

    • 你提出了很好的观点。首先,我绝对想避免“贫血模型”,这实际上是我质疑的根源。这就是模型/服务架构必须的吗?我很困惑:我应该丢弃服务层以将逻辑保留在模型中吗? (是的,领域模型非常复杂并且证明了 DDD 的合理性,我给出的示例只是一个非常基本的示例)。
    • 将相关逻辑保留在域中,它属于那里。将域服务用于属于域但不适合域对象的操作。使用其他层中的服务来解决基础设施问题(例如授权)。
    【解决方案3】:

    在考虑去往何处时,请同时考虑服务和模型的目的。服务位于application layer,而模型位于领域层。那么,您的应用程序需要了解关于Person 的哪些信息?不多,大概吧。 UI 可能会发送一些 id 来处理请求的操作。

    在这里,AR 是Driver 模型。请记住,服务可能包含其他服务,并且 Doctrine 实体是 POPOs 并且不需要是 anemic。此外,尝试将开发思维过程与持久性分离。例如,$driverId 不需要是整数,它可以是与域相关的任何唯一标识符。

    // DriverService
    // If more parameters are needed, consider passing in a command object
    public function beginTrip($driverId, $carId, $fromLocationId, $toLocationId)
    {
        $driver        = $this->repository->find($driverId);
        $car           = $this->carService->getAvailableCar($carId, $driverId);
        $withItenerary = $this->locationService->buildItenerary(
            [$fromLocationId, $toLocationId]
        );
    
        $driver->drive($car, $withItenerary); // actual 'driving' logic goes here
        $this->eventService->publish(new BeginTripEvent($driver, $car, $withItenerary));
    }
    

    【讨论】:

    • 注意:我知道这是一个死灵线程,但还没有回答。因此,我发布我的回复以防对其他人有帮助。
    【解决方案4】:

    好的,首先我明白我混合了 SOA 和应用程序服务。

    没错。您正在将域层 (DDD) 方法与域对象和服务层 (SOA) 方法与问题中的数据传输对象混合。

    域层对象与服务层对象是不同的对象!例如,服务层可能有一个CarDTO 对象而不是Car 对象和DriverDTO 对象而不是Driver 对象。

    1. $car->getDriver() 是在域层中访问Driver 的一种完全正确的方式,也可以在服务层中使用,但有一个限制,即无论服务消费者请求Car 数据,服务总是返回一个@ 987654328@ 和 Driver

    2. $personService->getPerson($car->getDriverId()) 仅在服务层有效,在域层无效。这种方法的原因是Driver 数据太大太复杂而不能总是用Car 返回。因此Service提供了一个单独的方法来请求Driver数据。

    3. $carService->getDriver($car) 在 Domain Layer 中无效,在 Service Layer 中看起来很奇怪,因为这种构造意味着 Service 使用者必须将所有 Car 数据发送到 CarService 才能获得 Driver 数据。最好只发送CarID,也可以发送给PersonService,而不是CarService(变体2)。

    一个更复杂的例子$person->getCars($nameFilter, $maxNumberOfResults, $offset); 在领域层看起来很奇怪,因为它不包含太多的业务逻辑。但是如果改成$CarService->getCars($nameFilter, $maxNumberOfResults, $offset);,它就适合在服务层处理部分请求了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-25
      • 1970-01-01
      • 2015-12-13
      • 1970-01-01
      相关资源
      最近更新 更多