业务 operations/workflow 设计模式
Business operations/workflow design pattern
假设我要执行涉及更新多个对象中的数据的操作。
这是一个例子:
void SomeOperation()
{
order.SetStatus(OrderStatus.ReadyForShipping);
order.Save();
email = emailFactory.CreateOrderIsReadyEmail(order);
mailService.QueueEmail(email);
mailService.Save();
shippingHandler = shippingHandlerSelectionStrategy.select(order.Location);
shippingHandler.addItem(order);
shippingHandler.Save();
}
在哪里放置这样的代码比较合适?
在它自己的 class?
class ReadyForShippingOperation : Operation
{
override void Execute()
{
// ...
}
}
但是,如果此操作对于何时可以执行有非常具体的业务规则,这种规则需要对这些对象有深入的了解怎么办?
比如说我们只能在某个订单处于某个状态时才执行这个操作。但是这个状态不是订单上的某个值,它是一组非常具体的要求,只与这个操作相关。
我真的要在我的订单对象中添加这样的东西吗?
class Order
{
bool IsReadyForThatShippingOperation();
}
此方法只涉及订单,因为它的实现与订单数据高度耦合。但从概念上讲,它并不是真正的命令的一部分。它仅与我们的一项操作相关。
另一种选择是制作订单的详细信息public。这也感觉不对。例如,我们的操作 class 可能看起来像:
class ReadyForShippingOperation : Operation
{
override void Execute()
{
if (!OrderIsReady(order)) {
// Handle error
}
// ...
}
bool OrderIsReady(order)
{
if (order.CreatedDate > someValue) return false;
if (order.LastUpdatedDate > someValue) return false;
if (!order.IsValid) return false;
if (!order.chachingState == InProgress) return false;
return true;
}
}
在这种情况下,我发现自己被迫扩展订单 API,其唯一目的是授予 OrderIsReady() 方法权限,以掌握它周围的脏手。
也许这个例子太具体了。总的来说,我真正想知道的是如何最好地组织需要来自许多对象但似乎不属于任何一个对象的私密数据的业务操作。
您可以在一些专用的 API class 中实施业务操作。例如,API class 主要与涉及订单的操作有关,可以称为 OrdersAPI
:
class OrderOperationsAPI {
void someOperation() {} // implement your SomeOperation here
void someOtherOperation() {} // implement something else over orders, shippings, etc..
}
流行的方法是将此类业务操作传递给 "Unit of work" 对象。
这个工作单元通常作为数据库或其他持久存储上的事务来实现。
这里的想法是跟踪在事务期间修改了哪些对象,然后在提交时保留更改的对象(或在回滚时丢弃所有内容)。
一个重要的特征是为此目的有一个特殊的会计对象,它与它监督的业务实体分离。
使业务 API 调用通过工作单元对象的常用方法是采用某种形式的 AOP(面向方面的编程)。这用入口代码包装了对业务 API (在你的情况下为 someOperation()
)的调用,它用所需的数据源和最终代码填充业务对象的上下文,它检查是否要提交事务,如果是, 收集所有修改过的对象并更新持久存储中的相应条目。
当某些操作的实现需要访问多个对象的私密数据时,这表明您的模型设计不当。尽管如此,某些操作并没有自然的 "home"。在这种情况下,您可以将它们分组为专用服务 classes(参见示例 http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/)。
关于您的具体情况,恕我直言,我没有发现 Order
上的 IsReadyForThatShippingOperation
放错了地方。如果此操作需要订单对象的私密数据,那么将该操作放在订单 class.
中没有错
也许你觉得不对,因为你认为订单应该只包含数据,而不是行为。然而,这被认为是 anti-pattern.
或者,您可以将操作放在服务中,但提供更适合该操作的顺序 API。因此,例如,而不是像这样执行检查(和 accessing some internal details of your object):
if (!order.chachingState == InProgress) return false;
你可以用这样的东西代替:
if (order.IsInProgress) return false;
假设我要执行涉及更新多个对象中的数据的操作。
这是一个例子:
void SomeOperation()
{
order.SetStatus(OrderStatus.ReadyForShipping);
order.Save();
email = emailFactory.CreateOrderIsReadyEmail(order);
mailService.QueueEmail(email);
mailService.Save();
shippingHandler = shippingHandlerSelectionStrategy.select(order.Location);
shippingHandler.addItem(order);
shippingHandler.Save();
}
在哪里放置这样的代码比较合适?
在它自己的 class?
class ReadyForShippingOperation : Operation
{
override void Execute()
{
// ...
}
}
但是,如果此操作对于何时可以执行有非常具体的业务规则,这种规则需要对这些对象有深入的了解怎么办?
比如说我们只能在某个订单处于某个状态时才执行这个操作。但是这个状态不是订单上的某个值,它是一组非常具体的要求,只与这个操作相关。
我真的要在我的订单对象中添加这样的东西吗?
class Order
{
bool IsReadyForThatShippingOperation();
}
此方法只涉及订单,因为它的实现与订单数据高度耦合。但从概念上讲,它并不是真正的命令的一部分。它仅与我们的一项操作相关。
另一种选择是制作订单的详细信息public。这也感觉不对。例如,我们的操作 class 可能看起来像:
class ReadyForShippingOperation : Operation
{
override void Execute()
{
if (!OrderIsReady(order)) {
// Handle error
}
// ...
}
bool OrderIsReady(order)
{
if (order.CreatedDate > someValue) return false;
if (order.LastUpdatedDate > someValue) return false;
if (!order.IsValid) return false;
if (!order.chachingState == InProgress) return false;
return true;
}
}
在这种情况下,我发现自己被迫扩展订单 API,其唯一目的是授予 OrderIsReady() 方法权限,以掌握它周围的脏手。
也许这个例子太具体了。总的来说,我真正想知道的是如何最好地组织需要来自许多对象但似乎不属于任何一个对象的私密数据的业务操作。
您可以在一些专用的 API class 中实施业务操作。例如,API class 主要与涉及订单的操作有关,可以称为 OrdersAPI
:
class OrderOperationsAPI {
void someOperation() {} // implement your SomeOperation here
void someOtherOperation() {} // implement something else over orders, shippings, etc..
}
流行的方法是将此类业务操作传递给 "Unit of work" 对象。
这个工作单元通常作为数据库或其他持久存储上的事务来实现。 这里的想法是跟踪在事务期间修改了哪些对象,然后在提交时保留更改的对象(或在回滚时丢弃所有内容)。
一个重要的特征是为此目的有一个特殊的会计对象,它与它监督的业务实体分离。
使业务 API 调用通过工作单元对象的常用方法是采用某种形式的 AOP(面向方面的编程)。这用入口代码包装了对业务 API (在你的情况下为 someOperation()
)的调用,它用所需的数据源和最终代码填充业务对象的上下文,它检查是否要提交事务,如果是, 收集所有修改过的对象并更新持久存储中的相应条目。
当某些操作的实现需要访问多个对象的私密数据时,这表明您的模型设计不当。尽管如此,某些操作并没有自然的 "home"。在这种情况下,您可以将它们分组为专用服务 classes(参见示例 http://gorodinski.com/blog/2012/04/14/services-in-domain-driven-design-ddd/)。
关于您的具体情况,恕我直言,我没有发现 Order
上的 IsReadyForThatShippingOperation
放错了地方。如果此操作需要订单对象的私密数据,那么将该操作放在订单 class.
也许你觉得不对,因为你认为订单应该只包含数据,而不是行为。然而,这被认为是 anti-pattern.
或者,您可以将操作放在服务中,但提供更适合该操作的顺序 API。因此,例如,而不是像这样执行检查(和 accessing some internal details of your object):
if (!order.chachingState == InProgress) return false;
你可以用这样的东西代替:
if (order.IsInProgress) return false;