gpt4 book ai didi

c# - 如何为将在多个步骤中创建的聚合建模,例如向导样式

转载 作者:行者123 更新时间:2023-12-03 14:41:14 25 4
gpt4 key购买 nike

我将以Airbnb为例。
注册Airbnb帐户后,您可以通过创建列表来成为房东。要创建列表,Airbnb UI会分多个步骤指导您完成创建新列表的过程:
enter image description here
它还会记住您走过的最远的步骤,因此下一次要恢复该过程时,它将重定向到您离开的地方。

我一直在努力决定是否应该将 list 作为汇总根,并将方法定义为可用步骤,还是将每个步骤都视为自己的汇总根,以使其较小?
列为汇总根

public sealed class Listing : AggregateRoot
{
private List<Photo> _photos;

public Host Host { get; private set; }
public PropertyAddress PropertyAddress { get; private set; }
public Geolocation Geolocation { get; private set; }
public Pricing Pricing { get; private set; }
public IReadonlyList Photos => _photos.AsReadOnly();
public ListingStep LastStep { get; private set; }
public ListingStatus Status { get; private set; }

private Listing(Host host, PropertyAddress propertyAddress)
{
this.Host = host;
this.PropertyAddress = propertyAddress;
this.LastStep = ListingStep.GeolocationAdjustment;
this.Status = ListingStatus.Draft;

_photos = new List<Photo>();
}

public static Listing Create(Host host, PropertyAddress propertyAddress)
{
// validations
// ...
return new Listing(host, propertyAddress);
}

public void AdjustLocation(Geolocation newGeolocation)
{
// validations
// ...
if (this.Status != ListingStatus.Draft || this.LastStep < ListingStep.GeolocationAdjustment)
{
throw new InvalidOperationException();
}

this.Geolocation = newGeolocation;
}

...
}
聚合根中的大多数复杂类都是值对象,而 ListingStatus只是一个简单的枚举:
public enum ListingStatus : int
{
Draft = 1,
Published = 2,
Unlisted = 3,
Deleted = 4
}
但是 ListingStep可以是一个枚举类,用于存储当前步骤可以前进的下一步:
using Ardalis.SmartEnum;

public abstract class ListingStep : SmartEnum<ListingStep>
{
public static readonly ListingStep GeolocationAdjustment = new GeolocationAdjustmentStep();
public static readonly ListingStep Amenities = new AmenitiesStep();
...

private ListingStep(string name, int value) : base(name, value) { }

public abstract ListingStep Next();

private sealed class GeolocationAdjustmentStep : ListingStep
{
public GeolocationAdjustmentStep() :base("Geolocation Adjustment", 1) { }

public override ListingStep Next()
{
return ListingStep.Amenities;
}
}

private sealed class AmenitiesStep : ListingStep
{
public AmenitiesStep () :base("Amenities", 2) { }

public override ListingStep Next()
{
return ListingStep.Photos;
}
}

...
}
将所有内容都放在列表的总根中的好处是可以确保所有内容都具有事务一致性。步骤被定义为领域关注的问题之一。
缺点是聚合根很大。在每个步骤上,为了调用列表操作,您必须加载包含所有内容的列表聚合根。
在我看来,除了地理位置调整可能取决于属性地址之外,其他步骤并不相互依赖。例如,列表的标题和描述与您上传的照片无关。
因此,我在考虑是否可以将每个步骤视为自己的总根?
每个步骤都作为自己的汇总根
public sealed class Listing : AggregateRoot
{
public Host Host { get; private set; }
public PropertyAddress PropertyAddress { get; private set; }

private Listing(Host host, PropertyAddress propertyAddress)
{
this.Host = host;
this.PropertyAddress = propertyAddress;
}

public static Listing Create(Host host, PropertyAddress propertyAddress)
{
// Validations
// ...
return new Listing(host, propertyAddress);
}
}

public sealed class ListingGeolocation : AggregateRoot
{
public Guid ListingId { get; private set; }
public Geolocation Geolocation { get; private set; }

private ListingGeolocation(Guid listingId, Geolocation geolocation)
{
this.ListingId = listingId;
this.Geolocation = geolocation;
}

public static ListingGeolocation Create(Guid listingId, Geolocation geolocation)
{
// Validations
// ...
return new ListingGeolocation(listingId, geolocation);
}
}

...
将每个步骤作为自己的聚合根的好处在于,它使聚合根变小(从某种程度上来说,我什至感觉它们太小了!),因此当将它们持久化回数据存储时,性能应该更快。
缺点是我失去了 list 汇总的交易一致性。例如,列表地理位置聚集仅按ID引用列表。我不知道是否应该在此处放置一个列表值对象,以便我可以在上下文中获得更多有用的信息,例如最后一步,列表状态等。
以基于意见的方式关闭吗?
我在网上找不到任何示例,该示例演示了如何在DDD中对这种类似于向导的样式进行建模。同样,我发现的大多数将巨大的聚合根拆分为多个较小的根的示例都是一对多的关系,但是我这里的示例主要是一对一的关系(可能是照片)。
我认为我的问题不会基于观点,因为
  • 只有有限的方法可以在DDD中对聚合进行建模
  • 我已经介绍了一个具体的业务模型airbnb,作为示例。
  • 我列出了我一直在思考的2种方法。

  • 您可以建议我采用哪种方法, 为什么选择,或者其他与我列出的两种方法不同, 则选择的原因。

    最佳答案

    让我们讨论拆分大型集群聚合的两个原因:

  • 多用户环境中的事务性问题。
    在我们的例子中,只有一个Host管理Listing。其他用户只能发布评论。通过将Review建模为单独的聚合,可以在根Listing上实现事务一致性。
  • 性能和可伸缩性。
    与往常一样,这取决于您的特定用例和需求。尽管创建了Listing后,您通常会查询整个 list ,以便将其呈现给用户(除了可能是折叠的评论部分)。

  • 现在让我们看一下值对象的候选对象(不需要身份):
  • 位置
  • 便利设施
  • 说明和标题
  • 设置
  • 可用性
  • 价格

  • 请记住,将内部零件限制为值对象是有优势的。首先,它大大降低了整体复杂性。
    至于向导部分,关键在于需要记住当前步骤:

    ..., so next time when you want to resume the process, it will redirect to where you left.


    由于骨料从概念上讲是一种持久性单位,因此要恢复到您离开的地方,将需要我们保留部分水合的骨料。您确实可以在集合上存储 ListingStep,但是从域的角度来看真的有意义吗?是否需要在 AmenitiesDescription之前指定 Title?这真的是对 Listing集合的关注吗,或者可以将其移至服务吗?当通过使用同一Service创建了所有 Listing时,该Service可以轻松确定上次中断的位置。
    将这种向导方法引入域模型感觉就像违反了关注分离原则。对于向导流程,B&B领域专家可能会漠不关心。
    考虑到以上所有因素,将 Listing作为聚合根似乎是一个不错的起点。

    更新

    I thought about the wizard being the concept of the UI, rather than of the domain, because in theory, since each step doesn't depend on others, you can finish any step in any order.


    确实,步骤独立是一个明确的指示,表明合计并没有在数据输入的顺序上构成任何真正的不变性。在这种情况下,它甚至都不是域问题。

    I have no problem modeling those steps as their own aggregate roots, and have the UI determine where it left off last time.


    向导步骤(页面)不应映射到它们自己的集合。在DDD之后,通常将用户操作转发到Application API/Service,后者又可以将工作委派给域对象和服务。应用程序服务仅涉及技术/基础架构方面的内容(例如,持久性),因为领域对象和服务拥有丰富的领域逻辑和知识。这通常称为洋葱或六角形建筑。请注意,依赖关系指向内部,因此域模型不依赖其他任何东西,并且不知道其他任何东西。
    Onion arch
    考虑向导的另一种方法是,这些向导基本上是数据收集器。通常在最后一步完成某种处理,但是在此之前的所有步骤通常仅收集数据。当用户关闭向导(过早)时,可以使用此功能包装所有数据,将其发送到Application API,然后混合聚合并保留该数据,直到下次用户来回为止。这样,您只需要在页面上执行基本验证,而无需涉及任何真正的域逻辑。

    My only concern of that approach is that, when all the steps are filled in and the listing is ready to be reviewed and published, who's responsible for it? I thought about the listing aggregate, but it doesn't have all the information.


    在这里,Application Service作为工作的委托(delegate)人开始发挥作用。它本身不具备真正的领域知识,但可以“了解”所有参与的参与者,并可以将工作委派给他们。它不是非绑定(bind)上下文(无双关语),因为您希望一次将事务范围限制为一个聚合。如果没有,您将不得不诉诸两个阶段的提交,但这是另一回事了。
    若要将其包装起来,可以将 ListingStatus存储在 Listing上,并使它后面的不变式成为根聚合的责任。这样,它应该具有所有信息,或者随其一起提供,以相应地更新 ListingStatus。换句话说,这与向导步骤无关,而与描述聚合背后过程的名词和动词有关。在这种情况下,将输入保护所有数据的不变量,并且该变量当前处于要发布的正确状态。从那时起,仅以部分状态或不连贯的方式返回并保留聚合是非法的。

    关于c# - 如何为将在多个步骤中创建的聚合建模,例如向导样式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66098719/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com