DDD:每个用例相同域模型的不同聚合。有办法吗?

问题描述 投票:0回答:1

我对 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。延迟加载似乎不是一个选择。因为它导致两种方法:

  • 聚合部分初始化(然后用存储库填充丢失的数据)-似乎不好。我们必须意识到缺失的数据;
  • 存储库逻辑泄漏到聚合。即使repository是一个接口,似乎也不好;

我已经尝试用谷歌搜索这个问题,在相应的书籍和文章中找到了好几天。但是,我觉得这可能不是 DDD 的选择。可能这是另一种服务于此目的的设计模式。或者,可能是我出了什么问题。

go domain-driven-design aggregateroot
1个回答
0
投票

对相同概念有不同的表示是合法的,它被称为多义域模型,但它是根据有界上下文而不是根据用例完成的。

假设您有一个与

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);
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.