Anc*_*hor 2 architecture validation design-patterns domain-driven-design repository-pattern
在DDD中,分页查询的验证逻辑应该放在哪里?例如,如果服务层接收到具有类似(在Go中)的参数的集合的查询,尽管可以用任何语言回答:
// in one file
package repositories
type Page struct {
Limit int
Offset int
}
// Should Page, which is part of the repository
// layer, have validation behaviour?
func (p *Page) Validate() error {
if p.Limit > 100 {
// ...
}
}
type Repository interface {
func getCollection(p *Page) (collection, error)
}
// in another file
package service
import "repositories"
type Service struct {
repository repositories.Repository
}
// service layer
func (s *Service) getCollection(p *repositories.Page) (pages, error) {
// delegate validation to the repository layer?
// i.e - p.Validate()
// or call some kind of validation logic in the service layer
// i.e - validatePagination(p)
s.repository.getCollection(p)
}
func validatePagination(p *Page) error {
if p.Limit > 100 {
...
}
}
Run Code Online (Sandbox Code Playgroud)
我想强制执行"不Limit超过100"的规则,此规则是否属于服务层,还是更多的是存储库问题?
乍一看似乎应该在Repository层强制执行,但是第二个想法,它不一定是存储库本身的实际限制.它更多地是由属于实体模型的业务约束驱动的规则.但是Page,它实际上不是域实体,它更像是Repository层的属性.
对我来说,这种验证逻辑似乎停留在业务规则和存储库问题之间.验证逻辑应该放在哪里?
"这更像是一个由业务约束驱动的规则,属于实体模型"
这些规则通常不是业务规则,由于技术系统的限制(例如,保证系统的稳定性),它们很容易就位(很可能是没有业务专家参与的开发人员).他们通常在应用层中找到自然的家,但如果这样做更实际,可以放在其他地方.
另一方面,如果业务专家对资源/成本因素感兴趣并决定将其推向市场,以便客户可以支付更多费用以便一次查看更多,那么这将成为业务规则; 企业真正关心的事情.
在这种情况下,规则检查肯定不会进入,Repository因为业务规则将埋没在基础结构层中.不仅如此,它还Repository非常低级,可用于自动脚本或其他您不希望应用这些限制的过程.
实际上,我通常会应用一些CQRS原则,并避免完全通过存储库进行查询,但这是另一个故事.
对我来说,红旗与@plalx 相同。具体来说:
它更像是由属于实体模型的业务约束驱动的规则
很有可能,发生了两件事之一。两者中可能性较小的是业务用户正在尝试将技术应用程序定义为领域模型。每隔一段时间,您就会有一个对技术有足够了解的业务用户尝试插入这些东西,他们应该被倾听——作为一个关注点,而不是一个要求。用例不应定义性能属性,因为这些是应用程序本身的验收标准。
这导致了更可能的情况,因为业务用户正在根据用户界面描述分页。再次,这是应该谈论的事情。但是,这不是用例,因为它适用于域。限制数据集大小绝对没有错。重要的是如何限制这些尺寸。一个明显的担忧是可能会撤回过多的数据。例如,如果您的域包含数以万计的产品,您可能不希望退回所有这些产品。
为了解决这个问题,您还应该首先了解为什么您的场景会返回过多数据。当纯粹从存储库的角度来看时,存储库仅用作 CRUD 工厂。如果您关心的是开发人员可以使用存储库做什么,那么还有其他方法可以对大型数据集进行分页,而不会将技术或应用程序问题引入该领域。如果您可以安全地推断出分页方面是由应用程序的实现所拥有的,那么在应用程序服务中将分页代码完全置于域之外绝对没有错。让应用程序服务执行繁重的任务,了解应用程序的分页要求,解释这些要求,
与其使用某种 GetAll() 方法,不如考虑使用一个接受标识符数组的 GetById() 方法。您的应用程序服务执行一项专门的“搜索”任务,并确定应用程序期望看到的内容。好处可能不会立即显现,但是当您搜索数百万条记录时,您会怎么做?如果您想考虑使用 Lucene、Endeca、FAST 或类似的东西,您真的需要为此破解域吗?何时,或者如果,您到了想要更改技术细节的地步,并且发现自己必须实际接触您的领域,对我来说,这是一个相当大的问题。当您的域开始为多个应用程序提供服务时,所有这些应用程序是否都具有相同的应用程序需求?
最后一点是我觉得最有冲击力的一点。几年前,我也处于同样的境地。我们的域在存储库内有分页,因为我们有一个业务用户,他有足够的影响力和足够的技术知识来应对危险。尽管团队反对,我们还是被否决了(这是对自己的讨论)。最终,我们被迫将分页放在域中。第二年,我们开始在业务内部使用其他应用程序的概念内的域。实际的业务规则从未改变,但我们搜索的方式改变了 - 取决于应用程序。这让我们不得不提出另一套方法来适应,并承诺未来和解。
当我们最终传达这样的信息时,可以通过允许应用程序拥有自己的需求来避免域中的这些持续变化,并且可以避免域中的这些持续变化。提供促进特定问题的方法 - 例如“给我这些特定产品”。以前的“给我二十个产品,以这种方式排序,具有特定偏移量”的方法根本没有描述域。每个应用程序都确定了“分页”最终对自身意味着什么以及它希望如何加载这些结果。最佳结果,在分页集中间颠倒顺序等。这些都被淘汰了,因为它们更接近于他们的实际职责,我们在保护领域的同时赋予应用程序权力。我们使用服务层来描述什么被认为是“安全的”。由于服务层充当域和应用程序之间的中间人,我们可以在服务层拒绝请求,例如,如果应用程序请求了一百多个结果。这样,应用程序就不能随心所欲地做任何它喜欢的事情,并且域会兴高采烈地忘记应用于正在进行的调用的技术限制。例如,如果应用程序请求超过一百个结果,我们可以在服务级别拒绝请求。这样,应用程序就不能随心所欲地做任何它喜欢的事情,并且域会兴高采烈地忘记应用于正在进行的调用的技术限制。例如,如果应用程序请求超过一百个结果,我们可以在服务级别拒绝请求。这样,应用程序就不能随心所欲地做任何它喜欢的事情,并且域会兴高采烈地忘记应用于正在进行的调用的技术限制。