如何将数据验证与简单域对象 (POCO) 分开?
这个问题与语言无关,但我是 C# 人员,所以我使用术语 POCO 来表示仅执行数据存储的对象,通常使用 getter 和 setter 字段。
我刚刚将我的域模型重新设计为超级 POCO,并且对如何确保属性值在域中有意义存在一些担忧。
例如,服务的结束日期不应超过该服务所依据的合同的结束日期。 然而,将检查放入 Service.EndDate setter 中似乎违反了 SOLID,更不用说随着需要完成的验证数量的增加,我的 POCO 类将变得混乱。
我有一些解决方案(将在答案中发布),但它们有其缺点,我想知道解决这个困境的一些最喜欢的方法是什么?
This question is language agnostic but I am a C# guy so I use the term POCO to mean an object that only preforms data storage, usually using getter and setter fields.
I just reworked my Domain Model to be super-duper POCO and am left with a couple of concerns regarding how to ensure that the property values make sense witin the domain.
For example, the EndDate of a Service should not exceed the EndDate of the Contract that Service is under. However, it seems like a violation of SOLID to put the check into the Service.EndDate setter, not to mention that as the number of validations that need to be done grows my POCO classes will become cluttered.
I have some solutions (will post in answers), but they have their disadvantages and am wondering what are some favorite approaches to solving this dilemma?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
我认为您一开始就有一个错误的假设,即您应该拥有除了存储数据之外不执行任何操作的对象,并且除了访问器之外没有任何方法。 拥有对象的全部意义在于封装数据和行为。 如果你有一个基本上只是一个结构的东西,那么你要封装什么行为?
I think you're starting off with a bad assumption, ie, that you should have objects that do nothing but store data, and have no methods but accessors. The whole point of having objects is to encapsulate data and behaviors. If you have a thing that's just, basically, a struct, what behaviors are you encapsulating?
我总是听到人们争论“Validate”或“IsValid”方法。
就我个人而言,我认为这可能可行,但对于大多数 DDD 项目,您通常最终会这样
根据对象的具体状态,可以进行多次验证。
所以我更喜欢“IsValidForNewContract”、“IsValidForTermination”或类似的,因为我相信大多数项目最终每个类都会有多个这样的验证器/状态。 这也意味着我没有接口,但我可以编写聚合验证器,这些验证器读取很好地反映了我所断言的业务条件。
我确实相信,在这种情况下,通用解决方案经常将注意力从重要的事情(代码正在做什么)上转移开来,以获取技术优雅(接口、委托或其他)的微小收益。 只要投票给我就可以了;)
I always hear people argument for a "Validate" or "IsValid" method.
Personally I think this may work, but with most DDD projects you usually end up
with multiple validations that are allowable depending on the specific state of the object.
So I prefer "IsValidForNewContract", "IsValidForTermination" or similar, because I believe most projects end up with multiple such validators/states per class. That also means I get no interface, but I can write aggregated validators that read very well reflect the business conditions I am asserting.
I really do believe the generic solutions in this case very often take focus away from what's important - what the code is doing - for a very minor gain in technical elegance (the interface, delegate or whatever). Just vote me down for it ;)
我的一位同事想出了一个想法,效果非常好。 我们从来没有给它想出一个好听的名字,但我们称之为检查员/法官。
检查员会查看一个对象并告诉您它违反的所有规则。 法官将决定如何处理。 这种分离让我们可以做一些事情。 它让我们将所有规则放在一个地方(检查员),但我们可以有多个法官并根据上下文选择法官。
使用多个法官的一个例子围绕着“客户必须有地址”这一规则。 这是一个标准的三层应用程序。 在 UI 层中,Judge 将生成 UI 可以用来指示必须填写的字段的内容。UI Judge 不会抛出异常。 在服务层还有一个法官。 如果在保存期间发现客户没有地址,则会引发异常。 到那时你真的必须阻止事情继续进行。
随着对象状态的变化,我们也有更严格的判断。 这是一份保险申请,在报价过程中,允许以不完整的状态保存保单。 但是,一旦该政策准备好生效,就必须设置很多事情。 所以服务端的Quoting Judge并没有Activation Judge那么严格。 然而,检查器中使用的规则仍然相同,因此即使您决定不采取任何措施,您仍然可以看出哪些内容不完整。
A colleague of mine came up with an idea that worked out pretty well. We never came up with a great name for it but we called it Inspector/Judge.
The Inspector would look at an object and tell you all of the rules it violated. The Judge would decide what to do about it. This separation let us do a couple of things. It let us put all the rules in one place (Inspector) but we could have multiple Judges and choose the Judge by the context.
One example of the use of multiple Judges revolves around the rule that said a Customer must have an Address. This was a standard three tier app. In the UI tier the Judge would produce something that the UI could use to indicate the fields that had to be filled in. The UI Judge did not throw exceptions. In the service layer there was another Judge. If it found a Customer without an Address during Save it would throw an exception. At that point you really have to stop things from proceeding.
We also had Judges that were more strict as the state of the objects changed. It was an insurance application and during the Quoting process a Policy was allowed to be saved in an incomplete state. But once that Policy was ready to be made Active a lot of things had to be set. So the Quoting Judge on the service side was not as strict as the Activation Judge. Yet the rules used in the Inspector were still the same so you could still tell what wasn't complete even if you decided not to do anything about it.
一种解决方案是让每个对象的 DataAccessObject 获取验证器列表。 当调用 Save 时,它会对每个验证器执行检查:
好处是非常清晰的 SoC,缺点是我们在调用 Save() 之前不会得到检查。
One solution is to have each object's DataAccessObject take a list of Validators. When Save is called it preforms a check against each validator:
The benefit, is very clear SoC, the disadvantage is that we don't get the check until Save() is called.
过去,我通常将验证委托给服务自己,例如 ValidationService。 这原则上仍然遵循 DDD 的哲学。
在内部,这将包含一组验证器和一组非常简单的公共方法,例如 Validate() ,它可以返回错误对象的集合。
非常简单,C# 验证器中的类似内容
可以添加到默认构造函数中,也可以通过其他类(例如 ValidationServiceFactory)注入。
In the past I have usually delegated validation to a service unto its own, such as a ValidationService. This in principle still ad hears to the philosophy of DDD.
Internally this would contain a collection of Validators and a very simple set of public methods such as Validate() which could return a collection of error object.
Very simply, something like this in C#
Validators could either be added within a default constructor or injected via some other class such as a ValidationServiceFactory.
实际上,我认为这可能是逻辑的最佳位置,但这只是我的想法。 您可以使用某种 IsValid 方法来检查所有条件并返回 true/false,可能是某种 ErrorMessages 集合,但这是一个不确定的主题,因为错误消息实际上并不是域模型的一部分。 我有点偏见,因为我已经对 RoR 做了一些工作,而这本质上就是它的模型所做的。
I think that would probably be the best place for the logic, actually, but that's just me. You could have some kind of IsValid method that checks all of the conditions too and returns true/false, maybe some kind of ErrorMessages collection but that's an iffy topic since the error messages aren't really a part of the Domain Model. I'm a little biased as I've done some work with RoR and that's essentially what its models do.
另一种可能性是让我的每个类实现
并让每个类的每个设置器在设置之前引发事件(也许我可以通过属性来实现这一点)。
优点是实时验证检查。 但代码比较混乱,并且不清楚谁应该进行附加操作。
Another possibility is to have each of my classes implement
And have each setter for each class raise the event before setting (maybe I could achieve this via attributes).
The advantage is real-time validation checking. But messier code and it is unclear who should be doing the attaching.
这是另一种可能性。 验证是通过域对象上的代理或装饰器完成的:
优点:即时验证。 可以通过 IoC 轻松配置。
缺点:如果是代理,则验证的属性必须是虚拟的,如果是装饰器,则所有域模型都必须基于接口。 验证类最终会变得有点重量级 - 代理必须继承该类,而装饰器必须实现所有方法。 命名和组织可能会令人困惑。
Here's another possibility. Validation is done through a proxy or decorator on the Domain object:
Advantage: Instant validation. Can easily be configured via an IoC.
Disadvantage: If a proxy, validated properties must be virtual, if a decorator all domain models must be interface-based. The validation classes will end up a bit heavyweight - proxys have to inherit the class and decorators have to implement all the methods. Naming and organization might get confusing.