我正在尝试在当前项目(c#、mvc、nhibernate、castle)中使用 DDD,并且我正在考虑检查约束的最佳方法,该约束规定如果实体处于某种状态,任何字段(或特定字段)都不得更改, IE。已预订的发票(状态=已预订)不得更改金额字段。在服务层中,我得到一些 DTO 对象(来自 gui 或 web 服务等),我需要将其映射到域对象。映射完成后,我想验证我的对象 - 特别是我想检查我的问题中的特定约束。
目前我正在考虑:
- 跟踪实体级别的更改,即在每个 setter 上将字段添加到更改的字段集合中,并将 NHibernate 切换为使用字段访问策略。如果不允许更改值
- 在映射之前创建对象的副本,并且比较原始值和映射值,
- 则此模式的变体将在 setter 上抛出异常,并从 nhibernate 会话中获取此信息 - 但是该规则不会在实体级别(恕我直言,它破坏了 ddd)
您对此有何看法?您知道这方面有什么好的模式吗?或者我是否遗漏了一些东西,我需要改变我对这个限制的思考方式?
预先感谢您的帮助。
I'm trying to use DDD in my current project (c#, mvc, nhibernate, castle) and I'm thinking of best way to check a constraint that says any field (or specific field) must not change if entity is in some state, ie. invoice that is booked (state=booked) must not have amount field changed. In service layer I get some DTO object (from gui or web service, etc) that I need to map to domain object. After mapping is complete I want to validate my object - in particular I want to check the specific constraint in my question.
Currently I'm thinking of:
- tracking changes at entity level, i.e. on every setter add field to changed fields collection and switch NHibernate to use field access strategy. Variation of this pattern would be to throw exception on setter if changing value is not allowed
- creating copy of object before mapping and the comparing original and mapped values
- falling back on nhibernate and get this information from nhibernate session - however the rule will not be enforced at entity level (imho it breaks ddd)
What do you think about it? Do you know any good patterns for this? Or am I missing something and I need to change my way of thinking about this constraint?
Thanks in advance for help.
发布评论
评论(3)
DDD 中的域对象是“自我验证”的。换句话说,客户端代码不可能破坏域规则,因为对象强制执行其内部不变量。例如:
DDD 示例中的另一个示例:
永远不要允许您的 UI 框架将域对象视为哑数据容器。不幸的是,互联网上的大量示例以及 C# 对 getter 和 setter 的强调都鼓励了这一点。如果您更改对象状态而不强制执行业务规则,您最终将得到“损坏”的对象。对于 NHibernate 来说尤其如此,因为它的 Session 会“记住”所有对象,并且会在下次提交或刷新时将它们转储到数据库中。但这只是一个技术问题,主要原因是您需要能够仅通过查看 Invoice 类来推理 Invoice 相关的业务规则。另请注意,代码应基于通用语言。您应该看到“发票”、“预订”、“金额”等字样,而不是通用的“字段”、“财产”、“验证器”。
更新:empi,感谢您重述您的问题。您可能想提出一个新问题。这是我强调的引用
我认为您正在寻找一种方法来声明域中的所有内容,然后“生成”UI。类似于 裸对象 “ rel="nofollow">MVC?我从未使用过这种方法,但我怀疑生成的 UI 是否会赢得美观或可用性竞赛。在我看来,UI 中总会有一些业务逻辑的“重述”。一些领域不变量太复杂,涉及多个领域,需要存储库,甚至可能需要外部服务。我不确定是否可以自动生成高质量的用户界面。我认为尝试这样做可能会开始改变你的模型以符合 UI 基础设施。
Domain objects in DDD are 'self validating'. In other words, it is not possible for client code to break domain rules because objects enforce their internal invariants. For example:
Another example from DDD sample:
Never allow your UI framework treat domain object as dumb data container. Unfortunately this is encouraged by a lot examples on the internet and C#'s emphasis on getters and setters. If you change object state without enforcing business rules you will eventually end up with 'corrupted' objects. This is especially true for NHibernate because its Session 'remembers' all objects and will happily dump them into database on next commit or flush. But this is just a technicality, the main reason is that you need to be able to reason about Invoice related business rules just by looking at Invoice class. Also note that the code should be based on Ubiquitous Language. You should see words like 'Invoice', 'Booked', 'Amount' instead of generic 'Field', 'Property', 'Validator'.
UPDATE: empi, thank you for restating your problem. You might want to open a new question. This is the quote with my emphasis
I think that you looking for a way to state everything in domain and then 'generate' UI. Something like Naked Objects for MVC? I never used this approach but I doubt that generated UI would ever win beauty or usability contest. In my opinion there will always be some 'restating' of business logic in UI. Some domain invariants are too complex, involve multiple fields, require repository and maybe even external services. I'm not sure that it is possible to generate high quality user interface automatically. I think that attempts to do this may start bending your model to conform to the UI infrastructure.
在设计领域对象时,我尽量不要将它们仅仅视为数据集合,而是将其视为可以执行操作的对象。不要提供对数据的直接访问(即使通过 getter 和 setter 方法),而是提供与人们可能对对象执行的操作相对应的方法。有时一个操作会更改多个数据字段。有时它可能只改变一个,并且在功能上与 setter 没有什么不同,但它的命名是为了代表操作而不仅仅是数据修改。
通过这种方法,您可以轻松地根据实体的状态强制执行允许执行的操作。例如,对于发票,您可以添加或删除项目。这会更改总数,但不提供直接修改总数的访问权限。当发票处于某种状态(例如已预订)且您不再允许更改时,可以通过从“添加”或“删除”方法中抛出异常来强制执行该操作,指示这些方法在当前状态下无效。然而,其他方法可能仍然有效,例如与运输或发票付款相关的方法。
除了这种方法之外,我还使用不同的实体来表示生命周期中不同点的相同数据。当发票处于活动状态时,它需要是一个可以执行操作的对象。然而,一旦达到最终状态,它仅用于查看和报告,并且不会发生任何数据更改。通过使用不同的实体(例如ActiveInvoice 和CompletedInvoice),在应用程序中可以清楚地看出它在哪里用作流程的一部分以及在哪里仅用于查看。它还使得处理可能来自不同表或只读视图的归档数据变得更加容易。
如果对象只有表示可变和不可变状态的两种状态,而没有太多逻辑来允许针对各种状态使用不同的方法,则可以使用 Eric Lippert 的“冰棒不变性” 图案。它允许对对象进行更直接的修改,但一旦对象被冻结,就会强制其不变性。
When designing my domain objects, I try not to think of them just as a collection of data, but as an object that can be acted upon. Instead of providing direct access to the data (even through getter and setter methods), provide methods that correspond to the actions one might take with the object. Sometimes an action will change multiple data fields. Sometimes it might only change one and functionally be no different from a setter, but it is named such that it represents the action and not just a data modification.
With this approach you can easily enforce what actions would be allowed based on the state of the entity. For example with an Invoice, you might Add or Remove items. This would change the total, but access isn't provided to modify the total directly. When the invoice is in a certain state (e.g. booked) when you no longer allow changes, then enforce that by throwing an exception from the Add or Remove methods indicating the methods aren't valid in the current state. However other methods may still be valid such as those related to shipping or payment of the invoice.
Along with this approach, I've also used different entities to represent the same data at different points in the lifecycle. While the invoice is active, it needs to be an object that can be acted upon. However, once it reaches a final state, it is only used for viewing and reporting and none of the data changes. By using different entities (e.g. ActiveInvoice and CompletedInvoice), it becomes clear in the application where it is used as part of the process and where it is used just for viewing. It also makes it easier when dealing with archiving data that may be coming from a different table or read-only view.
If the object only has two states representing a mutable and non-mutable state without much logic for allowing different methods for various states, you can use Eric Lippert's 'Popsicle Immutability' pattern. It allows more direct modification of the object than, but then enforces its immutability once it is frozen.
虽然我找不到很好的参考资料(我可以发誓几年前我从 Martin Fowler 那里听说过,但搜索他的网站却一无所获),我习惯于听到这个被称为“冻结”或“可冻结”。它通常与两条腿会计交易结合使用。
具体来说,直到相应的项目被冻结后才会创建会计交易,此时不允许对该项目采取任何可能更改余额的操作。在许多情况下,除了可能取消之外,根本无法采取进一步的操作,取消实际上不会更改冻结的项目,而只是导致添加追溯事件。
奇怪的是,Microsoft 在完全不同的 WPF 环境中实现了这一点。他们使用“freezable”主要是为了表明不再需要更改通知。事实上,如果您正在使用 WPF,您可能会考虑查看 可冻结类。
否则,如果您想要一个真正通用的模式,我强烈建议您阅读 Kozmic 的 动态代理教程 。尽管这主要是炫耀 Castle Proxy 功能的借口,但“freezable”概念正是他选择实现的,并且他展示了一种使用通用可重用库来实现此目的的方法,而无需事后编写更多附加代码。
尽管需要相当多的代码来解决所有问题,但最基本的想法是编写一个拦截器,然后用它创建一个代理:
请注意,上面的代码不是生产质量的代码 ,这只是介绍。您应该阅读更多链接网站以获取更多信息。
据我所知,类/接口代理实际上是您可以以与域无关的方式执行此操作的唯一方法。否则,您将不得不为每个可冻结类重新实现可冻结逻辑 - 也就是说,在属性设置器中放入大量
if-then
语句并抛出FrozenException
(如果状态已设置)。Although I can't find a good reference (I could swear I heard it from Martin Fowler several years ago, but a search of his site came up dry), I'm used to hearing this concept referred to as "freezing" or "freezable". It's normally used in combination with two-legged accounting transactions.
Specifically, an accounting transaction is not created until the corresponding item is frozen, at which point no actions are allowed to be taken on the item which could change the balance. In many cases, no further actions may be taken at all, except possibly for cancellation, which actually doesn't change the frozen item but simply results in a retroactive event being added.
Oddly, Microsoft implemented this in a completely different context with WPF. They use "freezable" primarily to indicate that change notifications are no longer necessary. If you are, in fact, using WPF, you might consider looking at the Freezable class.
Otherwise, if you want a truly generic pattern, I highly suggest you read through Kozmic's Dynamic Proxy Tutorial. Although it's mainly an excuse to show off the features of Castle Proxy, the "freezable" concept is exactly what he chooses to implement, and he shows a way to do this using a generic reusable library without having to write much additional code after the fact.
Although there is quite a lot of code to work out all the kinks, the very basic idea is to just write an interceptor and then create a proxy with it:
Note that the above is not production-quality code, it's just the intro. You should read more of the linked site for more information.
As far as I know, class/interface proxying is really the only way you can do this in a domain-agnostic way. Otherwise, you're going to have to re-implement the freezable logic for every freezable class - that is to say, putting a lot of
if-then
statements in your property setters and throwing anFrozenException
if the status is set.