域驱动设计是一种反SQL模式吗?


43

我正在研究域驱动设计(DDD),尽管我对此进行了更深入的了解,但有些事情我还是没有。据我了解,主要要点是将域逻辑(业务逻辑)与基础结构(数据库,文件系统等)分开。

我想知道的是,当我遇到非常复杂的查询(例如材料资源计算查询)时会发生什么?在这种查询中,您需要使用繁重的集合操作,这是SQL设计的目的。在域层内部进行这些计算并使用其中的许多集合就像丢弃SQL技术一样。

在基础架构中也无法进行这些计算,因为DDD模式允许在基础架构中进行更改而无需更改域层,并且知道MongoDB不具有SQL Server等相同的功能,这是不可能发生的。

这是DDD模式的陷阱吗?


34
尽管SQL是为处理关系集代数而设计的,但当您意识到一半的业务逻辑都埋在少数难以重构甚至难以测试的SQL函数中时,这并不是一个有趣的日子。因此,将其移动到可以与朋友一起玩的领域层听起来很吸引我。这会丢掉很多SQL技术吗?可以,但是当您仅使用SELECT / JOIN时,SQL易于管理。
Jared Goguen

30
@JaredGoguen,但这可能是因为您不是SQL专家,也不是因为该技术
Leonardo Mangano

2
@JimmyJames我想说的是,如果DDD实施良好,则可以以最小的努力更改层,例如从SQL Server切换到MongoDB。但是,如果在SQL中有复杂的查询,则可能由于技术上的差异而无法切换到MongoDB。我想我说的很明显。
Leonardo Mangano

7
... is like throwing away the SQL technology仅仅因为某种特定技术可以做某事并不意味着它是最佳选择。这是传闻,但我遇到了太多的企业,这些企业过去将业务逻辑存储在数据库中,并且由于长期的可维护性问题而从数据库中迁移过来。过于简化,但是数据库用于存储数据,而编程语言用于转换数据。我不想再将DB用于业务逻辑,而要尝试使用应用程序直接存储数据。
Conor Mancone

8
SQL本身就是DDD的一个很好的例子。当面对组织相关数据时,人们首先指定一种语言来做:SQL。实施并不重要。数据库管理员不需要知道C / C ++即可查询数据库。同样,当面对计划事件的任务时,有人想出了CRON语法(mhdmw),这是一种适合99%计划问题的简单域模型。DDD的核心不是创建类或表等。它是要了解您的问题并提出一个可以在您的问题域中使用的系统
slebetman

Answers:


38

如今,您可能会发现读取(查询)与写入(命令)的处理方式有所不同。在具有复杂查询的系统中,查询本身不太可能通过域模型(域模型主要负责维护写入的一致性)。

您绝对正确,我们应该将SQL呈现给SQL。因此,我们将设计一个围绕读取进行优化的数据模型,并且对该数据模型的查询通常会采用不包含域模型的代码路径(某些输入验证可能会例外-确保查询中的参数是合理的)。


13
+1很好的答案,但是您应该给这个概念起一个适当的名称,即Command-Query Segregation。
Mike

6
@Mike具有完全不同的读写模型更像CQRS而不是CQS。
安迪

3
“读取模型”不是域模型(或其一部分)吗?我不是CQRS方面的专家,但我一直认为命令模型与经典域模型有很大不同,但读取模型却没有。所以也许您可以举个例子?
布朗

我花了很长时间才意识到高性能标记正在引起人们对打字错误的关注。
VoiceOfUnreason

@DocBrown -这里是我试图澄清你- > cascadefaliure.vocumsineratio.com/2019/04/...
VoiceOfUnreason

20

据我了解,主要要点是将域逻辑(业务逻辑)与基础结构(数据库,文件系统等)分开。

这是造成误解的基础:DDD的目的不是沿着诸如“这是在SQL Server中,所以一定不能是BL”之类的硬线分离事物,DDD的目的是分离域并在两者之间创建障碍它们允许一个域的内部结构与另一个域的内部结构完全分开,并在它们之间定义共享的外部结构。

不要将“存在于SQL中”视为BL / DL障碍-事实并非如此。而是将“这是内部领域的终结”视为障碍。

每个域都应该具有允许其与所有其他域一起使用的面向外部的API :对于数据存储层,它应该对其存储的数据对象具有读/写(CRUD)操作。这意味着SQL本身并不是真正的障碍,而VIEWand PROCEDURE组件则是。您永远不要直接从表中阅读:是DDD告诉我们的实现细节,作为外部使用者,我们不必担心。

考虑您的示例:

我想知道的是,当我遇到非常复杂的查询(例如材料资源计算查询)时会发生什么?在这种查询中,您需要使用繁重的集合操作,这是SQL设计的目的。

这正是SQL中应有的功能,并且不违反DDD。这就是我们为DDD做的。与SQL该计算,即变成部分的BL / DL的。您将要做的是使用单独的视图/存储过程/您拥有什么,并将业务逻辑与数据层分开,因为是您的外部API。实际上,您的数据层应该是另一个DDD域层,其中您的数据层拥有自己的抽象来与其他域层一起工作。

在基础架构中也无法进行这些计算,因为DDD模式允许在基础架构中进行更改而无需更改域层,并且知道MongoDB不具有SQL Server等相同的功能,这是不可能发生的。

那是另一个误解:它说内部的实现细节可以更改而无需更改其他域层。并不是说您可以替换整个基础架构。

再次提醒您,DDD是关于使用定义良好的外部API隐藏内部组件。这些API的位置是完全不同的问题,DDD对此没有定义。它只是简单地定义了这些API的存在,并且绝不应更改

DDD的设置不允许您用MongoDB临时替换MSSQL,这是两个完全不同的基础结构组件。

取而代之的是,让我们使用DDD定义的类比:汽油车与电动车。两种车辆都有两种完全不同的产生推进力的方法,但是它们具有相同的API:开/关,油门/制动器和推动车辆的车轮。DDD表示,我们应该能够更换汽车中的发动机(汽油或电动)。并不是说我们可以用摩托车代替汽车,这实际上就是MSSQL→MongoDB。


1
感谢您的解释。对我来说,这是一个非常艰巨的话题,每个人都有不同的观点。我唯一不同意的是MSSQL(汽车)和MongoDB(摩托车)之间的比较,对我而言,正确的比较是这是同一辆汽车的两个不同引擎,但这只是一个见解。
Leonardo Mangano

8
@LeonardoMangano啊,但事实并非如此。MSSQL是关系数据库,MongoDB是文档数据库。是的,“数据库”同时描述了这两种方法,但这已经足够了。读/写技术完全不同。取而代之的MongoDB,您可以使用Postgre或MySQL作为替代,而将是一个有效的比较。
410_Gone

3
“你永远不应该直接从桌子上阅读……”疯狂。
jpmc26

“您永远不应该直接从表上阅读...”这是我在编写与数据库连接的软件十年后经历了尝试遵循结构化教程的早期痛苦后,靠自己实现的一条规则流行的设计模式。
路西法·山姆

@LuciferSam Aye。它使管理实现细节和域边界之间的分隔变得容易得多。域中的一个“对象”可能由5个表表示,因此我们使用View封装该对象。
410_Gone

18

如果您曾经参与过一个项目,在该项目中,由组织来托管应用程序的组织决定数据库层许可证的价格太高,那么您将可以轻松地迁移数据库/数据存储,这将使您满意。考虑到所有事情,尽管这确实发生了,但它并不经常发生

可以这么说,您可以两全其美。如果您考虑优化数据库中的复杂功能,则可以使用一个接口来注入计算的替代实现。问题是您必须在多个位置维护逻辑。

偏离建筑模式

当您发现自己纯粹地实施某种模式或在某些领域出现偏差时,您将做出决定。模式只是做事以帮助组织项目的模板化方式。此时,需要花一些时间来评估:

  • 这是正确的模式吗?(很多时候是这样,但是有时候这很不合适)
  • 我应该以这种方式偏离吗?
  • 到目前为止,我偏离了多远?

您会发现某些体系结构模式非常适合您的应用程序的80-90%,但对于其余部分则不太合适。出于性能或后勤方面的原因,有时会偏离规定的模式。

但是,如果您发现累积偏差占应用程序体系结构的20%以上,则可能不合适。

如果您选择继续使用该体系结构,那么请帮自己一个忙,并记录下您偏离规定的处事方式的位置和原因。当您的团队中有一个新的热心成员时,您可以将他们指向该文档,其中包括性能度量和依据。这将减少重复请求修复“问题”的可能性。该文档还将有助于消除严重的偏差。


我会避免在答案中使用诸如“这是正确的模式”之类的短语。要让人们在写问题时要表达具体的观点是非常困难的,而且根据您自己的承认,“有时候这很不合适”,这表明不,这不是正确的模式。
罗伯特·哈维

@RobertHarvey,我曾经在一些项目中使用过的模式不适合该应用程序,这导致它无法通过某些质量指标。当然,这不是规范,但是当发生这种情况时,您将很难决定更改体系结构或将棘手的代码保留在应用程序中。越早确定不合适的位置,越容易修复。这就是为什么我在评估边缘案例时总是将这种想法包括在内。伴随着最后一个项目符号,有时您直到意识到偏差的累积才意识到拟合的糟糕程度。
Berin Loritsch

6

SQL擅长的集合操作逻辑可以与DDD集成在一起。

举例来说,我需要了解一些汇总值,即按类型划分的产品总数。易于在sql中运行,但是如果我将每个产品加载到内存中并将其全部加在一起,则会很慢。

我只是介绍一个新的Domain对象,

ProductInventory
{
    ProductType
    TotalCount
    DateTimeTaken
}

和我的存储库中的方法

ProductRepository
{
    List<ProductInventory> TakeInventory(DateTime asOfDate) {...}
}

当然,也许我现在依靠我的数据库具有某些能力。但是从技术上讲,我仍然可以分开,只要逻辑很简单,我就可以说这不是“业务逻辑”


好吧,到目前为止,我还记得。存储库也应该Query作为参数获取。repository.find(query);。我已经阅读了相同的内容,但使用Specs. That opens a door to leave Query`作为抽象QueryImpl或对基础结构层的特定查询实现。
莱夫

5
哦,天哪,我知道有人这样做,但我认为这很糟糕。您可以将这种事情视为走这条路的一步。但我认为可以谨慎对待。
伊万

I know some people do that有些人是Pivotal及其框架。SpringFramework有很多这样的功能:-)。无论如何,正如@VoiceOfUnreason所建议的那样,DDD的关键在于保持作品的一致性。我不确定是否要使用仅用于查询或参数化查询的领域模型来强制设计。可以使用数据结构(pocos,pojos,dto,行映射器,等等)来解决这个问题。
Laiv

显然,我们需要某种调查来帮助那些人恢复理智。但是我坚持不懈。数据层的部分暴露在客观上有利于更好的应用时是可以接受的,其中“域对象”是主观还是非主观
Ewan

1
@LeonardoMangano取决于您的应用程序和实现。主要要意识到的是,您可以重新解释您的域以使其可行。
伊万

3

解决此难题的一种可能方法是将SQL视为一种汇编语言:很少(如果有的话)直接在其中编写代码,但是在性能很重要的地方,您需要能够理解C语言生成的代码/ C ++ / Golang / Rust编译器,甚至可能在汇编中编写一个小片段,如果您无法更改高级语言中的代码以生成所需的机器代码。

同样,在数据库和SQL领域中,各种SQL库(其中一些是ORM)(例如,用于Python的SQLAlchemy和Django ORM,用于.NET的LINQ)提供了更高级别的抽象,但在可能的情况下使用生成的SQL代码来实现性能。它们还为使用的数据库提供了一些可移植性,由于使用某些更优化的特定于数据库的SQL的某些操作,它们可能具有不同的性能,例如在Postgres和MySQL上。

与高级语言一样,理解SQL的工作方式至关重要,即使只是重新安排使用上述SQL库完成的查询,也能够达到所需的效率。

PS:我希望对此发表评论,但我对此没有足够的声誉。


2

像往常一样,这是取决于许多因素的事情之一。的确,您可以使用SQL做很多事情。使用它还存在挑战,并且关系数据库存在一些实际限制。

正如Jared Goguen在评论中指出的那样,SQL可能很难测试和验证。导致这种情况的主要原因是(通常)无法将其分解为组件。实际上,必须综合考虑复杂的查询。另一个复杂的因素是,SQL的行为和正确性在很大程度上取决于数据的结构和内容。这意味着测试所有可能的方案(甚至确定它们是什么)通常是不可行或不可能的。SQL的重构和数据库结构的修改同样存在问题。

导致脱离SQL的另一个重要因素是关系数据库往往只能垂直扩展。例如,当您在SQL中构建复杂的计算以在SQL Server中运行时,它们将在数据库上执行。这意味着所有这些工作都在使用数据库上的资源。您在SQL中所做的工作越多,就内存和CPU而言,数据库将需要更多的资源。在其他系统上执行这些操作通常效率较低,但是可以为此类解决方案添加的其他计算机数量没有实际限制。与构建怪兽数据库服务器相比,此方法更便宜,更容错。

这些问题可能适用于或可能不适用于当前的问题。如果您能够使用可用的数据库资源解决问题,则SQL可能适合您的问题空间。但是,您需要考虑增长。今天可能还不错,但是几年之后,添加更多资源的成本可能成为问题。


难道不是怪物数据库的替代品,仅仅是庞大而多样的辅助系统吗?如果辅助系统都与核心系统无关,它们具有什么弹性?而且,如果证明理由仅仅是核心系统的技术局限性,那么对于大多数业务系统而言,这通常是过早的优化。如果认为必要,通常可以以分离的方式编写SQL。
史蒂夫(Steve)

@Steve我认为您在这里出错的地方是假设必须有一个其他人“挂在嘴”的核心系统。
JimmyJames

@Steve举个例子,您可以用单个no-SQL数据库替换整个系统数据库(我并不是说这总是正确的选择,只是可以做到。)然后可以将该数据库存储在许多数据库中。系统,甚至地理区域。这样的数据库不是辅助数据库,它是SQL DB的批发替代品。
JimmyJames

@JimmyJames,同意,但是当没有核心系统时,它会在分析依赖关系和保持数据一致性方面产生自己的问题。这就是首先要考虑整体结构的原因-它们产生了某种简单性,因此也带来了某种分析和维护效率。非整体解决方案仅将一些问题或成本交换给其他问题。
史蒂夫(Steve)

@jmoreno随便扔一些资源,以使其mp行,这不是我所谓的良好工程设计:“为了处理站点的海量数据,并且正在运行9000个memcache实例,以跟上事务数量数据库必须服务。” 您是否考虑设计成本,还是假设有人会花钱使您的个人喜好可行?
JimmyJames

2

这是DDD模式的陷阱吗?

首先让我清除一些误解。

DDD不是模式。它并没有真正规定模式。

埃里克·埃文(Eric Evan)DDD书的序言指出:

领先的软件设计师将域建模和设计视为关键主题至少已有20年了,但令人惊讶的是,关于需要做什么或如何做的文章很少。尽管从未明确表述过,但一种哲学已在对象社区中浮出水面,我称之为领域驱动设计。

[...]

成功的共同特征是丰富的领域模型,该模型通过设计的迭代演变而来,并成为项目结构的一部分。

本书提供了制定设计决策的框架和讨论领域设计的技术词汇。它是广泛接受的最佳实践以及我自己的见解和经验的综合。

因此,这是一种进行软件开发和领域建模的方法,外加一些支持这些活动的技术词汇表(包括各种概念和模式的词汇表)。这也不是全新的东西。

要记住的另一件事是,域模型不是在系统中可以找到的它的OO实现-这只是表达它或表达它的一部分的一种方式。域模型是您考虑要使用软件解决的问题的方式。这是您理解和感知事物的方式,以及您如何谈论事物的方式。这是概念性的。但是从某种意义上讲并不是这样。它是深刻而精致的,是努力工作和知识积累的结果。它会进一步完善,并可能随着时间的推移而发展,并且涉及实现方面的考虑(其中一些因素可能会约束模型)。应该由所有团队成员共享 (和相关领域的专家),它应该驱动您实施系统的方式,以便系统紧密地反映它。

尽管OO开发人员通常可能更擅长以OO语言表达模型,并且OOP更好地支持了许多领域概念的表达,但本质上不是Pro-SQL还是anti-SQL。但是有时模型的某些部分必须用不同的范式表达。

我想知道的是,当我有非常复杂的查询时会发生什么?

好吧,一般来说,这里有两种情况。

在第一种情况下,域的某些方面确实需要复杂的查询,并且也许该方面最好用SQL /关系范式表示-因此请使用适当的工具来完成工作。在您的领域思考和沟通概念所使用的语言中反映这些方面。如果该域很复杂,则可能是该子域具有自己的有限上下文的一部分。

另一种情况是,认为需要在SQL中表达某些内容是思维受限的结果。如果一个人或团队的思维始终以数据库为导向,那么由于惯性,他们可能很难看到另一种处理事物的方式。当旧方法无法满足新需求时,这将成为一个问题,并且需要开箱即用的思考。DDD作为一种设计方法,在某种程度上是关于通过收集和提取有关领域的知识来摆脱困境的方法。但是每个人似乎都忽略了本书的这一部分,而是专注于列出的一些技术词汇和模式。


0

当内存昂贵时,Sequel变得流行起来,因为关系数据模型提供了规范化数据并将其有效存储在文件系统中的可能性。

现在内存相对便宜,因此为了提高速度,我们可以跳过规范化并以我们使用的格式存储,甚至可以复制很多相同的数据。

将数据库视为简单的IO设备,它负责将数据存储在文件系统中-是的,我知道很难想象,因为我们编写了许多应用程序,并且将重要的业务逻辑写入了SQL查询中-但请尝试想象一下SQL Server只是另一台打印机。

您是将PDF生成器嵌入到打印机驱动程序中,还是添加了一个触发器,该触发器将为从我们打印机中打印出的每个销售订单打印日志页?

我认为答案是否定的,因为我们不希望我们的应用程序耦合到特定的设备类型(甚至不谈论这种想法的效率)

在70年代至90年代,SQL数据库非常有效,现在呢?-不确定,在某些情况下,异步数据查询将比SQL查询中的多个联接更快地返回所需数据。

SQL并不是为复杂的查询而设计的,它是为高效存储数据而设计的,然后提供接口/语言来查询存储的数据。

我会说围绕具有复杂查询的关系数据模型构建应用程序是对数据库引擎的滥用。当然,当您将业务与产品紧密结合时,数据库引擎提供商会很高兴–他们会很乐意提供更多功能,使这种结合更加牢固。


1
但是我一直认为,SQL比任何其他语言都更适合集合计算。从我的角度来看。您的示例是颠倒的,将C#用于涉及数百万行和联接的非常复杂的集合操作使用了错误的工具,但我可能是错的。
Leonardo Mangano

@LeonardoMangano,一些示例:使用c#,我可以对数百万行进行分块并并行计算,可以异步检索数据并在返回数据时“及时”执行计算,使用c#,我可以通过枚举行来进行低内存使用量的计算按行。代码中包含复杂的逻辑将为您提供很多如何进行计算的选择。
法比奥
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.