我正在设计Web应用程序的基础架构。该项目遵循域驱动设计方法,因为业务模型和逻辑非常复杂。
该项目还旨在成为 SOA 项目(面向服务的体系结构)。所以我正在学习很多关于服务以及如何围绕它构建项目。
在previous question of mine之后,我对模型类中关联有疑问。
我理解模型类不应该知道并执行与持久性相关的任何事情。但是我无法确定模型类之间关联的情况。
例如:
Person
Car
有一个驱动程序(例如) getDriver
和getCars
应该在哪里?
$car->getDriver()
$personService->getPerson($car->getDriverId())
$carService->getDriver($car)
解决方案1.似乎更自然。我正在使用Doctrine 2,因此使用DB映射注释处理模型关联。这样,该模型不会执行与持久性相关的任何事情(即使它实际上通过Doctrine )。这是我最喜欢的解决方案,但是除了加载“汽车”列表之外,服务的重点是什么?
解决方案2.看起来很愚蠢,因为它抛弃了OOP,而Model / Service用户必须知道数据库模型才能获取关联(他必须知道这个ID是“Person”id)。他必须自己做这个协会。
解决方案3.比解决方案2好一点,但仍然其中的OOP ?
所以,对我而言,解决方案1.是最好的。但我已经看到解决方案2和解决方案3.用于实际项目(有时混合在一起),所以我有疑问。
当有其他参数时,问题会变得更加复杂,例如:
$person->getCars($nameFilter, $maxNumberOfResults, $offset);
(在这种情况下,它看起来像SQL查询/持久性查询)
那么,在域驱动设计方法之后的项目中,哪一个应该用于模型/服务架构?使用SOA,我的模型应该只是没有逻辑的“哑”数据容器吗?如果是这样,那么DDD方法在哪里?
答案 0 :(得分:1)
在DDD的上下文中,这是决定实体之间的关系是否通过直接对象关联与存储库表达的问题。这两种方法都是有效的,取决于关系的性质。例如,在您的域中,一个人可能有很多汽车与它们相关联,并且从人员实体直接关联到该组汽车并没有多大意义。请记住,实体的工作,或者更具体地说是聚合根,是保护不变量和实施业务规则。如果人员类别中存在的任何行为不需要与人员相关联的汽车集合,则没有理由将该关联放在人员实体上。此外,如您的示例所示,可能需要过滤汽车查询。为了回答你的问题,我将责任代表人与车之间的关系repository。 SOA与DDD正交,更侧重于如何访问和部署业务功能。从hexagonal architecture也称为onion architecture的角度来考虑DDD和SOA之间的相互作用是有益的。您的域是核心,由一组应用程序服务封装,这些应用程序服务构成了围绕您的域的API外观。这些与SOA中的服务不同,后者是六边形/洋葱架构中的端口/适配器,它们将这些应用程序服务公开为SOA服务。
答案 1 :(得分:1)
如果您的项目是DDD,我不明白为什么您需要模型/服务架构。 IMO创建了一个贫血模型,一切都是程序性的。
现在,作为DDD,这意味着你不关心数据库。你有(至少在逻辑上)2个模型:域和持久性。 Domain模型以最自然的方式处理关联,最适合代表业务案例。 “有一个驱动程序”或者有很多,这是一个以数据库为中心的思维,在DDD中没有位置。持久性模型处理聚合根将存储在数据库中的方式(这里是您定义ORM实体及其关系的所有部分)。
关于您的问题,首先关注的是背景和目的。如果它严格用于查询(显示给用户),则可以使用简单模型,不需要DDD和业务规则。控制器可以直接向专业查询存储库询问数据,作为DTO返回。
如果你想更新Person或car,那么在Application Layer(我通常使用基于命令的方法,所以在我的情况下所有这些都发生在命令处理程序中,但在架构上它仍然是应用程序层的一部分)您可以从(域)存储库中恢复最适合该任务的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类,但是一个人可以是司机或所有者(如果是租赁公司,则不是)。如您所见,它几乎取决于域,只有在您拥有域的清晰图像后,您才可以开始考虑如何存储数据以及存储库返回的对象(实体)。
答案 2 :(得分:0)
在考虑去哪里时,请考虑服务和模型的目的。当模型驻留在域层中时,服务驻留在application layer中。那么,您的应用需要了解Person
?可能不多。用户界面可能会发送一些ID来处理请求的操作。
此处,AR是Driver
模型。请注意,服务可能包含其他服务,而且该条款的引用属于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));
}
答案 3 :(得分:0)
好的,首先我理解我混合了SOA和应用程序服务。
真。您正在将域层(DDD)方法与域对象和服务层(SOA)方法与问题中的数据传输对象混合使用。
域层对象是与服务层对象不同的对象!例如,服务层可能有一个CarDTO
对象而不是Car
对象和DriverDTO
对象而不是Driver
对象。
$car->getDriver()
是一种在域层中访问Driver
的完全正确的方式,也可以在服务层中使用,并且限制在服务使用者请求Car
数据的任何位置,服务始终返回Car
Driver
。
$personService->getPerson($car->getDriverId())
仅在服务层中有效,在域层中无效。此方法的原因是Driver
数据太大且太复杂,无法始终使用Car
返回。因此,Service提供了一种单独的方法来请求Driver
数据。
$carService->getDriver($car)
在域层中无效,在服务层中看起来很奇怪,因为这种结构意味着服务使用者必须将所有Car
数据发送到CarService
才能获得Driver
数据。最好只发送CarID
,也许发送到PersonService
,而不是发送到CarService
(变体2)。
更复杂的示例$person->getCars($nameFilter, $maxNumberOfResults, $offset);
在域层中看起来很奇怪,因为它不包含太多业务逻辑。但是,如果更改为$CarService->getCars($nameFilter, $maxNumberOfResults, $offset);
,它将适用于部分请求的服务层。