如果实体处于某种状态,如何强制执行约束(如任何字段(或特定字段))不得更改?

我正在尝试在我当前的项目中使用DDD(c#,mvc,nhibernate,castle),我正在考虑检查一个约束的最佳方法,即如果实体处于某种状态,则任何字段(或特定字段)都不得更改,即。 已预订的发票(州=已预订)不得更改金额字段。 在服务层我得到一些我需要映射到域对象的DTO对象(来自gui或web服务等)。 映射完成后,我想validation我的对象 – 特别是我想检查我的问题中的具体约束。

目前我在考虑:

  • 跟踪实体级别的更改,即在每个setter添加字段到更改的字段集合,并切换NHibernate以使用字段访问策略。 如果不允许更改值,则此模式的变化是在setter上抛出exception
  • 在映射之前创建对象的副本以及比较原始值和映射值
  • 回到nhibernate并从nhibernate会话中获取此信息 – 但是规则将不会在实体级别强制执行(imho it break ddd)

你怎么看待这件事? 你知道这个有什么好的模式吗? 或者我错过了什么,我需要改变我对这种约束的思考方式?

在此先感谢您的帮助。

DDD中的域对象是“自我validation”。 换句话说,客户端代码不可能破坏域规则,因为对象强制执行其内部不变量。 例如:

public class Invoice { private Money _amount; private InvoiceState _state; public void ChangeAmount(Money newAmount) { if(_state == State.Booked) { throw new InvalidOperationException( "Unable to change amount for booked invoice."); } _amount = newAmount; } // Methods like this can be used by external code (UI) to check business // rules upfront, to avoid InvalidOperationException. public Boolean CanChangeAmount() { if(_state == State.Booked) { return false; } return true; } } 

DDD样本的另一个例子:

  public HandlingEvent(final Cargo cargo, final Date completionTime, final Date registrationTime, final Type type, final Location location, final Voyage voyage) { ... if (type.prohibitsVoyage()) { throw new IllegalArgumentException( "Voyage is not allowed with event type " + type); } 

永远不要让您的UI框架将域对象视为哑数据容器。 不幸的是,互联网上的很多例子和C#对吸气剂和制定者的强调令人鼓舞。 如果在不强制执行业务规则的情况下更改对象状态,最终将会出现“损坏的”对象。 对于NHibernate来说尤其如此,因为它的Session会记住所有对象,并且会在下次提交或刷新时将它们很好地转储到数据库中。 但这只是一个技术性问题,主要原因是您需要能够仅通过查看Invoice类来推理与Invoice相关的业务规则。 另请注意,代码应基于泛在语言 。 你应该看到像’Invoice’,’Booked’,’Amount’这样的词,而不是通用的’Field’,’Property’,’Validator’。

更新:empi,谢谢你重申你的问题。 您可能想要打开一个新问题。 这是我强调的引用

正如我在其中一条评论中所说 – 这个问题是我遇到的更大问题的一部分。 我正在寻找一种标准方法来仅在域中定义域逻辑和约束,然后将其转换为gui和其他层。 我没有看到以下常见需求的任何常见模式:域声明该字段无法编辑,并且此规则自动转换为gui逻辑 ,禁用该字段并使其成为只读。 我正在寻找mvc堆栈中的示例解决方案。 我觉得我正在重新发明轮子,大多数开发人员只是放弃并复制gui中的逻辑

我认为你正在寻找一种在域中陈述所有内容然后“生成”UI的方法。 像MVC的 裸体对象 ? 我从来没有使用过这种方法,但我怀疑生成的UI会赢得美容或可用性竞赛。 在我看来,UI中总会有一些“重述”业务逻辑。 一些域不变量太复杂,涉及多个字段,需要存储库甚至外部服务。 我不确定是否可以自动生成高质量的用户界面。 我认为尝试这样做可能会使您的模型弯曲以符合UI基础结构。

在设计我的域对象时,我尽量不将它们视为数据集合,而是将其视为可以采取行动的对象。 不是提供对数据的直接访问(甚至通过getter和setter方法),而是提供与对象可能采取的操作相对应的方法。 有时,操作会更改多个数据字段。 有时它可能只改变一个并且在function上与setter没有什么不同,但是它被命名为它代表动作而不仅仅是数据修改。

使用此方法,您可以根据实体的状态轻松实施允许的操作。 例如,对于发票,您可以添加或删除项目。 这将改变总数,但不提供直接修改总数的访问权限。 当您不再允许更改时,如果发票处于某种状态(例如已预订),则通过从添加或删除方法中抛出exception来强制执行该操作,以指示方法在当前状态下无效。 然而,其他方法可能仍然有效,例如与发票的运输或支付相关的方法。

除了这种方法,我还使用不同的实体来表示生命周期中不同点的相同数据。 发票处于活动状态时,它必须是可以对其执行操作的对象。 但是,一旦达到最终状态,它仅用于查看和报告,并且没有任何数据更改。 通过使用不同的实体(例如ActiveInvoice和CompletedInvoice),它在应用程序中变得清晰,它被用作进程的一部分以及仅用于查看的位置。 在处理可能来自不同表或只读视图的归档数据时,它还使其更容易。

如果对象只有两个表示可变和不可变状态的状态而没有太多逻辑允许不同的方法用于各种状态,则可以使用Eric Lippert的“Popsicle Immutability”模式。 它允许更直接地修改对象,但是一旦它被冻结就强制执行它的不变性。

虽然我找不到一个好的参考(我可以发誓几年前我从Martin Fowler那里听到它,但搜索他的网站干了),我习惯听到这个概念被称为“冻结”或“可冻结”。 它通常与两股会计交易结合使用。

具体而言,在相应的项目被冻结之前不会创建会计事务,此时不允许对可能改变余额的项目采取任何操作。 在许多情况下,根本不会采取进一步的行动,除非可能取消,实际上不会更改冻结的项目,而只是导致追加事件被添加。

奇怪的是,微软在与WPF完全不同的环境中实现了这一点。 他们使用“freezable”主要表示不再需要更改通知。 实际上,如果您正在使用WPF,您可以考虑查看Freezable类。

否则,如果你想要一个真正的通用模式,我强烈建议你阅读Kozmic的动态代理教程 。 虽然它主要是炫耀Castle Proxyfunction的借口,但“freezable”概念正是他选择实现的,他展示了一种使用通用可重用库实现此目的的方法,而无需在事后编写更多额外代码。

虽然有很多代码可以解决所有问题,但最基本的想法是编写一个拦截器,然后用它创建一个代理:

 internal class FreezableInterceptor : IInterceptor, IFreezable { private bool _isFrozen; public void Freeze() { _isFrozen = true; } public bool IsFrozen { get { return _isFrozen; } } public void Intercept(IInvocation invocation) { if (_isFrozen && invocation.Method.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase)) { throw new ObjectFrozenException(); } invocation.Proceed(); } } public static TFreezable MakeFreezable() where TFreezable : class, new() { return _generator.CreateClassProxy(new FreezableInterceptor()); } 

请注意,上面不是生产质量的代码 ,它只是介绍。 您应该阅读更多链接网站以获取更多信息。

据我所知,类/接口代理实际上是以域无关的方式执行此操作的唯一方法。 否则,您将不得不为每个freezable类重新实现freezable逻辑 – 也就是说,在属性设置器中放置大量if-then语句,并在设置状态时抛出FrozenException