我对 DDD 还很陌生。如果我有任何错误,请纠正我。谢谢。
考虑以下示例:
假设我们有一个顺序域模型(或者可能是概念)。我们有适用于该模型的不同用例。然后,我们可以定义多个用例范围的聚合“视图”,而不是创建单个聚合并在每个相应的用例中使用它。每个视图都包含该用例所需的所有业务数据以及业务规则和逻辑。
我认为这种方法可能是被允许的,因为我们仍然:
我们只是将业务逻辑分组(工作单元)提升到一个新的水平。
这是一个使用不同聚合“视图”的存储库。看评论。
type Repository interface {
/*
Store - stores new order into repository.
(aggregate) OrderNew here - is an order representation having minimum dataset for existence. No more, no less.
*/
Store(ctx context.Context, order OrderNew) error
/*
GetState - returns orders' state distinguished by its `uid`.
(value object) OrderState here - is an orders' state. Based on its value a use case makes decision about what to
do next.
Example for "UseCaseOrderConfirmation":
- The usecase retrieves OrderState;
- If state is "declined" (let's say because of a timeout) - action forbidden, return error;
- If state is "awaiting_confirmation" - proceed execution;
*/
GetState(ctx context.Context, uid string) (OrderState, error)
/*
SyncUpdateOnConfirmation - begins transaction under the hood and passes `order` to the given callback `update`
function. Callback returns either updated OrderOnConfirmation and ok, or an empty struct and false if current
transaction has to be rolled back.
(aggregate) OrderOnConfirmation here - is another "view" of the same domain model, that is different aggregate.
It contains all the required data needed to perform confirmation usecase only. No more, no less.
*/
SyncUpdateOnConfirmation(
ctx context.Context, uid string, update func(order OrderOnConfirmation) (OrderOnConfirmation, bool),
) error
}
这是示例用例:
type UseCaseOrderCreation struct {
repository orders.Repository
}
func (c UseCaseOrderCreation) Create(ctx context.Context, data orders.OrderNewData) error {
// ... (omitted) order duplication check
order, err := orders.New(data)
// ... (omitted) error handling
err = c.repository.Store(ctx, order)
// ... (omitted) error handling and possible following application logic
}
type UseCaseOrderConfirmation struct {
repository orders.Repository
}
func (c UseCaseOrderConfirmation) Confirm(ctx context.Context, uid string) error {
state, err := c.repository.GetState(ctx, uid)
// ... (omitted) error handling
if !state.AwaitingConfirmation() {
return ErrOrderInvalidState
}
update := func(order orders.OrderOnConfirmation) (orders.OrderOnConfirmation, bool) {
if !order.State().AwaitingConfirmation() {
return orders.OrderOnConfirmation{}, false
}
// ... (omitted) domain or application services usage
return order.Confirm( /* ... data */ ), true
}
err = c.repository.SyncUpdateOnConfirmation(ctx, uid, update)
// ... (omitted) error handling and other stuff
}
我认为这个方法可能有效。因为我们没有违反任何总体目标。无论如何,如果我们在确认时不需要订单描述,为什么我们要从存储库获取它 - 在确认的上下文中它没有任何意义。
或者可能是另一种更好的方法?
顺便说一句,目前我使用Golang。延迟加载似乎不是一个选择。因为它导致两种方法:
我已经尝试用谷歌搜索这个问题,在相应的书籍和文章中找到了好几天。但是,我觉得这可能不是 DDD 的选择。可能这是另一种服务于此目的的设计模式。或者,可能是我出了什么问题。
对相同概念有不同的表示是合法的,它被称为多义域模型,但它是根据有界上下文而不是根据用例完成的。
假设您有一个与
shipment
上下文分开的 order
上下文。当您想要更改发货状态时,您可能不需要订单的所有详细信息,尽管您可能需要订单状态以防止在订单未确认时发送发货。
每个用例具有不同的表示意味着您可能在同一上下文中应用不一致的业务规则。域层的目的是确保业务不变性,并且这些不变性不应根据您是创建还是确认订单而变化。
例如(在 C# 中,抱歉我不会说 go):
public enum OrderStatus { Pending, Declined, Confirmed };
public class Order
{
public OrderStatus Status { get; init; }
public string Value { get; init; }
public void Decline() => SetStatus(OrderStatus.Declined);
public void Confirm() => SetStatus(OrderStatus.Confirmed);
private void SetStatus(OrderStatus value)
{
// Business rule is valid for all use cases of the context
if (Status != OrderStatus.Pending)
{
throw new InvalidOperationException();
}
Status = value;
}
}
public class CreateOrderUseCase
{
private readonly OrderRepository repository;
public void Execute(CreateOrderDto payload)
{
var order = new Order { Status = OrderStatus.Pending, Value = payload.data };
repository.Insert(order);
}
}
public class ConfirmOrderUseCase
{
private readonly OrderRepository repository;
public void Execute(CreateOrderDto payload)
{
var order = repository.Find(payload.id);
order.Confirm();
repository.Update(order);
}
}