2

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

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

在我的上一个问题之后,我有一个关于模型类中的关联的问题。

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

例如:

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

getDriver和应该在哪里getCars

  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 方法在哪里?

4

4 回答 4

1

如果您的项目是 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 类,但一个人可以是司机或所有者(如果是租赁公司,则不是)。正如您所看到的,它在很大程度上取决于域,只有在您清楚地了解域之后,您才能开始考虑如何存储数据以及存储库返回什么对象(实体)。

于 2012-06-29T06:58:27.123 回答
1

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

于 2012-06-29T00:22:06.133 回答
0

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

在这里,AR 是一个Driver模型。请记住,服务可能包含其他服务,并且 Doctrine 实体是POPOs并且不需要是贫血的。此外,尝试将开发思维过程与持久性分离。例如,a$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));
}
于 2014-01-07T21:57:58.617 回答
0

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

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

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

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

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

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

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

于 2015-10-21T14:04:00.137 回答