如何更改需要数据库检查的实体的属性?

发布于 2024-12-26 23:15:14 字数 1906 浏览 2 评论 0 原文

我有一个名为 StyleBundle 的对象。

public class StyleBundle
{
    public StylePricingType StylePricingType { get; private set;}
    public decimal Price {get; private set;}
    public IEnumerable<Style> Styles { get; set;}
    public DateTime StartDate {get; private set;}
    public TimeSpan Duration {get; private set;}
    public bool IsTransient {get; set;}

    public void ChangeStylePricingType(StylePricingType newStylePricingType)
    {
        this.StylePricingType = newStylePricingType;
    }

}

此 StyleBundle 对象有一个名为 StylePricingType 的属性。 StylePricingType 是两种类型的枚举:

PerStyle

Unlimited

StylePricingType 将影响 StyleBundle 的整体价格。它影响价格的方式是通过更改样式列表中保存的样式。 Unlimited StyleBundle 将自动包含所有可用的样式,但 PerStyle StyleBundle 将允许用户手动选择他们想要包含的样式。

如果 StyleBundle 是瞬态的,我现在需要允许更改 StylePricingType(之前的规则规定,一旦 StyleBundle 被新建,您就无法更改 StylePricingType)。

但是,为了进行此更改,我需要通过存储库/规范/服务对数据库进行检查...也就是说,但是我想这样做。

该检查基本上会在当前 StyleBundle 的同一持续时间内查找任何其他 StyleBundle,并确保 StyleBundle 中的样式没有重叠。

由于更改瞬态 StyleBundle 上的此属性需要检查其他持久的 StyleBundle,因此实现此操作的最佳方法是什么?

  1. 使用构造函数注入:将服务注入到 StyleBundle 实体的构造函数中。我不喜欢这样,因为我不喜欢将依赖项注入到我的实体中,除非我需要这样做。另外,由于我不喜欢在仅需要更改 StylePricingType 的方法调用时将依赖项注入到构造函数中的想法,因此我认为这是糟糕的设计。

  2. 使用方法注入:由于我只需要这个方法调用的服务,这似乎更有意义。但与此同时,我不喜欢用户能够在不知道他们正在运行数据库查询的情况下更改此类型的想法。另外,我仍然将服务注入到我的实体中,只是以不同的方式,而且我真的不喜欢将任何东西注入到我的实体中。

  3. 使用域服务:这似乎是最明确的。我可以创建一个 StyleBundleService 类,该类具有 ChangeStylePricingType 方法,该方法使用存储库或规范来运行给定 StyleBundle 的检查。这样,要求在代码中变得非常明确,但这里的缺点是代码仍然可以直接在 StyleBundle 对象上调用 ChangeStylePricingType 方法,并绕过我需要创建的服务上的 ChangeStylePricingType 方法。即使我将 StylePricingType 设置为 get;set;而不是私人集;并摆脱了 StyleBundle 上的 ChangeStylePricingType 方法,代码仍然可以绕过域服务进行更改。

因此,这些似乎都是进行此类操作的合法方法,那么使用 DDD 执行此操作的最佳/最受接受的方法是什么?另外,也许我的 StyleBundle 对象试图做太多事情,应该分成更小的类/功能,以便更雄辩地处理此需求更改?

麦克风

I have an object called StyleBundle.

public class StyleBundle
{
    public StylePricingType StylePricingType { get; private set;}
    public decimal Price {get; private set;}
    public IEnumerable<Style> Styles { get; set;}
    public DateTime StartDate {get; private set;}
    public TimeSpan Duration {get; private set;}
    public bool IsTransient {get; set;}

    public void ChangeStylePricingType(StylePricingType newStylePricingType)
    {
        this.StylePricingType = newStylePricingType;
    }

}

This StyleBundle object has a property called StylePricingType. The StylePricingType is an enum of two types:

PerStyle

Unlimited

The StylePricingType will effect the overall Price of the StyleBundle. The way it will affect the Price is by changing the Styles kept in the Styles list. An Unlimited StyleBundle will automatically include all available Styles, but a PerStyle StyleBundle will allow a user to manually pick which Styles they want to include.

I now need to allow the StylePricingType to be changed if the StyleBundle is transient (previous rules stated that once a StyleBundle is new'ed up, you can not change the StylePricingType).

BUT, in order to make this change, I need to run a check against the database via a repository/specification/service... aka, however I want to do it.

The check basically looks for any other StyleBundles during the same duration of the current StyleBundle, and makes sure there are no overlap in Styles in the StyleBundle.

Since changing this property on a transient StyleBundle requires a check against other persisted StyleBundles, what is the best way to go about implementing this?

  1. Use Constructor injection: inject a service into the StyleBundle entity's constructor. I don't like this, b/c I don't like injecting dependencies into my entities unless I need to do so. Also, since I don't like the idea of injecting the dependency into the constructor when it's only needed for the method call that will change the StylePricingType, I see this as bad design.

  2. Use Method injection: Since I would only need the service for this one method call, this seems to make more sense. Yet at the same time, I don't like the idea the user being able to change this type without knowing they're running a db query. Also, I'm still injecting a service into my entity, just in a different way, and I really do not like injecting anything into my entities.

  3. Use a Domain Service: this seems to be the most explicit of all. I could create a StyleBundleService class that has a ChangeStylePricingType method that uses a repository or specification to run the check given a StyleBundle. This way, the requirement is made very explicit in the code, but the drawback here is code could still call the ChangeStylePricingType method directly on the StyleBundle object, and BYPASS the ChangeStylePricingType method on the service I need to make. Even if I set the StylePricingType to get;set; instead of private set; and got rid of the ChangeStylePricingType method on StyleBundle, code could still make the change, bypassing the domain service.

So, these all seem like legitimate ways to go about doing something like this, so what is the best/most accepted way of doing it using DDD? Also, maybe my StyleBundle object is trying to do too much, and should be broken into smaller classes/functionality that would allow this requirement change to be handled more eloquently?

Mike

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

天涯离梦残月幽梦 2025-01-02 23:15:14

这是 DDD 中经常遇到的问题。 Udi Dahan 在 此中讨论了选项 1问题。选项 2 在 SO 的其他地方讨论过(没有确切的链接),但和你一样,我不是粉丝,尽管这是最简单、最直接的方法。选项 3 通常与贫血域模型相关,但我经常发现它更可取。原因是封装服务层是作为 DDD 的一部分自然出现的东西 - 它将领域层暴露给其他层,例如表示层或开放主机服务。此外,对域实体执行的操作可以表示为由服务处理的命令对象。在这种情况下,您可以拥有:

class ChangeStylePricingTypeCommand {
  public string StyleBundleId { get; set; }
  public StylePricingType StylePricingType { get; set; }
}

class StyleBundleService {
 IStyleBundleRepository db;

 public void Process(ChangeStylePricingTypeCommand command) {
   using (var tx = this.db.BeginTransaction()) {
    var bundle = this.db.Get(command.StyleBundleId); 

    // verification goes here.

    bundle.ChangeStylePricingType(command.StylePricingType);

    this.db.Commit();
   }
 } 
}

服务 StyleBundleService 是访问存储库和其他服务的完美场所。

Udi 概述的方法需要 ChangeStylePricingType 引发一个域事件,该事件将订阅一个处理程序,该处理程序进而执行所需的业务逻辑。这种方法更加解耦,但更复杂并且可能有点矫枉过正。基于领域事件的方法的另一个问题是处理程序在事件发生后执行,因此无法阻止它,它只能处理后果。

This is a common issue encountered in DDD. A similar problem is discussed by Udi Dahan in this post. Option 1 is discussed in this question. Option 2 is discussed elsewhere on SO (don't have exact link), but like you, I am not a fan, even though it is the simplest and most direct way. Option 3 is often associated with an anemic domain model, however I often find it to be preferable. The reason is that an encapsulating service layer is something that arises natural as part of DDD - it exposes the domain layer to other layers, such as the presentation layer, or an open host service. Furthermore, actions performed on domain entities can be represented as command objects which are handled by the service. In this case, you can have:

class ChangeStylePricingTypeCommand {
  public string StyleBundleId { get; set; }
  public StylePricingType StylePricingType { get; set; }
}

class StyleBundleService {
 IStyleBundleRepository db;

 public void Process(ChangeStylePricingTypeCommand command) {
   using (var tx = this.db.BeginTransaction()) {
    var bundle = this.db.Get(command.StyleBundleId); 

    // verification goes here.

    bundle.ChangeStylePricingType(command.StylePricingType);

    this.db.Commit();
   }
 } 
}

The service StyleBundleService is a perfect place for accessing repositories and other services.

The approach outlined by Udi entails the ChangeStylePricingType raising a domain event, to which would be subscribed a handler, which in turn executes the required business logic. This approach is more decoupled, but is more complex and may be overkill. The other issue with a domain event based approach is that the handler executes after the event happened, and thus cannot prevent it, it can only deal with the consequences.

尬尬 2025-01-02 23:15:14

虽然我同意将这种行为从 StyleBundle 中具体化出来是个好主意,但我通常会尽量避免使用服务。更准确地说,如果存在更适合您真正希望对象执行的操作的已知模式名称,我会尽量避免将某些内容命名为服务。

在您的示例中,我仍然不清楚您是否只是想根据分配给它的新 StylePricingType 检查 StyleBundle 的有效性,如果捆绑包不符合,则完全拒绝操作,如果您想要根据新的 StylePricingType 调整捆绑包的内容。

在第一种情况下,规范似乎最适合这种情况(您在评论中提到,在将样式添加到捆绑包时您已经在使用规范)。在第二个例子中,您需要一个真正作用于 Bundle 的对象,消除不兼容的样式。我将使用 StyleRuleOutStrategy/Policy 和 Enforce() 方法,以 Bundle 作为参数。在这两种情况下,当更改规范/策略时,您都会在属性设置器中调用新规范/策略的相关方法。

请注意,如果切换到 PerStyle 时所采取的操作与切换到 Unlimited 时所采取的操作不同,则“策略”部分具有其全部含义,但根据您的解释,尚不清楚情况是否如此。

While I agree it is a good idea to externalize that behavior out of StyleBundle, I usually try to avoid using Services as much as possible. To be more precise, I try to avoid naming something a Service if there are known pattern names that better suit what you really want the object to do.

In your example, it's still unclear to me whether you simply want to check the validity of a StyleBundle against the new StylePricingType you assign to it, rejecting the operation altogether if the bundle doesn't comply, or if you want to adjust the contents of the bundle according to the new StylePricingType.

In the first case a Specification seems best suited for that (you mentioned in the comments you're already using one when adding Styles to a bundle). In the second you need an object that will actually act on the Bundle, eliminating non-compliant styles. I'd use a StyleRuleOutStrategy/Policy with an Enforce() method taking the Bundle as a parameter. In both cases you'd call the relevant method of the new Specification/Strategy in the property setter when changing Specification/Strategy.

Note that the Strategy part takes all its meaning if the action to take is not the same when switching to PerStyle than when switching to Unlimited, but again from what you explained it is not clear this is the case.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文