react 建模
域驱动设计是我认为应该开发软件的方式。 从Evan最初出版的《 域驱动设计》,《解决软件中心的复杂性》开始,这已经是一个艰巨的尝试 ,并且我们已经走了很长一段路。 现在实际上有关于域驱动设计的会议,而且我看到了越来越多的兴趣,包括商务人士的参与,这确实是关键。
如今,React性是一件大事,稍后我将解释为什么它吸引了很多人。 我认为真正有趣的是,使用或实现DDD的方式(例如2003年)与我们今天使用DDD的方式有很大不同。 如果您读过我的红皮书《 实现领域驱动的设计》 ,那么您可能已经熟悉以下事实:我在书中建模的有限上下文是独立的过程,具有独立的部署。 而在埃文(Evan)的蓝皮书中,有界上下文在逻辑上是分开的,但有时部署在同一部署单元中,也许是在Web服务器或应用程序服务器中。 在现代使用DDD的过程中,我看到越来越多的人采用DDD,因为它与具有单独部署(例如在微服务中)相吻合。
需要明确的一件事是,领域驱动设计的本质实际上仍然是它以前的样子-它是在有限的上下文中对无处不在的语言进行建模。 那么,什么是有界上下文? 基本上,有界上下文背后的想法是在一个模型和另一个模型之间进行清晰的划分。 围绕领域模型的这种划分和边界使边界内的模型变得非常明确,对于概念,模型元素以及团队(包括领域专家)如何思考模型。
您会发现团队使用的一种普遍存在的语言,并且由团队在软件中进行建模。 在有人说“产品”的场景和讨论中,他们在那种情况下确切地知道产品的含义。 在另一种情况下,产品可以具有不同的含义,这是另一个团队定义的。 产品可能会在有限的上下文中共享身份,但是通常来说,另一种上下文中的产品至少具有稍微不同的含义,甚至可能具有极大不同的含义。
我们正在与DDD共同努力,以认识到没有一种可行的方法来建立规范的企业数据模型,其中模型中的每个元素都代表企业中每个团队将如何使用它。 只是没有发生。 总是存在差异,很多时候,许多差异使一个团队尝试使用另一团队创建的模型非常痛苦。 这就是为什么我们专注于使用无处不在的语言的有限上下文的原因。
一旦了解了在一个团队中使用一种普遍使用的语言为一个实体定义的非常明确的定义,那么您就会意识到存在由其他团队开发的其他模型。 也许即使是开发此模型的同一团队也可能负责其他模型。 您可能会遇到多个有界上下文,因为自然地,我们无法为要使用的每个概念定义单个企业或单个系统中的所有含义。
鉴于我们有多种上下文和多种语言,我们必须在它们之间进行协作和集成。 为此,我们使用一种称为上下文映射的技术或一种称为上下文映射的工具。
图1-上下文映射
在此简单图中,有界上下文之间的线是上下文映射,可以适当地称为转换。 如果有界上下文中的一种讲一种语言,而连接上下文中的一种讲另一种语言,那么您在语言之间需要具备什么才能使一个模型理解另一种模型? 翻译。 通常,我们将尝试在模型之间进行转换,以使每个单独的模型保持纯净和干净。
创建上下文映射时,请勿混淆自己或对行的含义进行限制。 尽管可以涵盖技术集成,样式或技术,但定义上下文之间的团队关系非常重要。 我使用的是RPC还是REST并不重要。 与我集成的人比与我集成的人更重要。
定义上下文之间的团队关系非常重要。 与我集成的人比与我集成的人更重要。
有各种用于不同类型关系的上下文映射工具,包括伙伴关系,客户-供应商或遵循者关系。 在合伙关系中,一个团队将对另一团队的模型了解很多。 客户与供应商的关系在两个非常独立的模型(一个上游和一个下游)之间具有反腐败层。 我们将反腐败上游模型,因为它会被下游模型消耗。 如果下游模型需要将某些东西发送回上游模型,则它将把它转换回上游模型,以便可以以明确的含义一致,可靠地交换数据。
到目前为止,我所描述的战略设计实际上是域驱动设计的本质,因此也是最重要的部分。
在某些情况下,我们将决定以一种非常仔细甚至细粒度的方式对一种特定的普遍存在的语言进行建模。 如果您将战略设计视为使用宽笔触绘画,则将战术设计视为使用精美的画笔填充所有细节。
根据我对涉及DDD的会议演示的观察以及与团队合作的时间,我已经找到了一些有助于建模的小技巧。 这些并不是要指出任何特定的操作是错误的。 相反,我希望提供一些指导,以朝着正确的方向前进。
在建模时,我们必须记住的一件事是DDD,特别是在战术上,我们需要领域专家的帮助-不仅仅是程序员。 我们必须限制他们的时间使用,因为在团队中扮演领域专家角色的人员将非常忙于处理与业务相关的其他事务。 因此,我们必须使他们的经历非常有意义。
我们要做的另一件事是避免贫血领域模型。 每当您看到有关域模型的演示文稿,其中包括将自动创建getter和setter的批注, Equals(), GetHashCode()
等时,请认真考虑如何避免这种情况。 如果我们的领域模型仅涉及数据,那么这可能是一个完美的解决方案。 但是,我们需要提出一些问题。 数据如何产生? 我们实际上如何将数据获取到我们的域模型中? 是否根据企业和与我们合作的任何领域专家的思维模式来表达? 获取器和设置器没有为您提供模型含义的明确指示,它只是在移动数据。 如果您在战术DDD中进行思考,那么您就必须考虑吸气剂和塞脂剂最终会成为敌人,因为您真正想建模的是表示企业考虑如何完成工作的方式的行为。
建模时,要明确。 例如,假设您看到一个实体或聚合的业务标识,称为UUID。 将UUID用作业务标识符没有什么问题,但是为什么不将其包装为强类型的ID类型呢? 考虑到另一个未使用Java的有界上下文可能无法理解什么是UUID。 您很可能必须生成UUID ToString()
,然后将该字符串保留为其他类型,或者在有界上下文之间发送事件时从该类型转换该字符串。
与其直接使用BigDecimal
,不如考虑一个名为Money的值对象。 如果您使用过BigDecimal
,那么您将知道确定舍入因子是一个常见的困难。 如果我们让BigDecimal
遍及我们的模型,那么我们如何舍入一些钱呢? 解决方案是使用Money类型,该类型根据业务要求应采用的舍入规范进行标准化。
另一个小技巧是不要担心使用哪种持久性机制或使用哪种消息传递机制。 使用符合您特定服务水平协议的内容。 对所需的吞吐量和性能保持合理,不要使事情复杂化。 DDD并不是真正在谈论技术,而是需要使用技术。
至少在我的世界中,我一直在看到React系统的趋势。 不仅是微服务中的React式,而且是构建React式的整个系统。 在DDD中,React性行为也在有限的上下文中发生。 React性并不完全是新事物,埃里克·埃文斯(Eric Evans)在介绍赛事时远远领先于行业。 使用领域事件意味着我们必须对过去发生的事件做出React,并使我们的系统协调一致。
如果要可视化系统不同层上的所有连接,则会看到重复的模式。 无论您是查看整个Internet,还是企业级的所有应用程序,还是微服务中的单个参与者或异步组件,每层都有大量的连接和相关的复杂性。 这给我们带来了很多非常有趣的问题要解决。 我想强调,我们不应该用技术解决这些问题,而要对它们建模。 如果我们在云中开发微服务作为组建业务的手段,那么分布式计算就是我们正在从事的业务的一部分。并不是说分布式计算能使我们的业务(在某些情况下确实如此),但是我们绝对可以解决分布式计算的问题。 因此,通过对分布式计算方面进行建模,使它们明确。
我需要花一点时间来解决一些开发人员用作反对异步,并行,并发或任何给人印象或实际上有助于一次执行多个动作的技术的说法。 经常引用Donald Knuth的话:“过早的优化是万恶之源。” 但这只是他表达的结束。 他确实说过:“我们应该忘记效率低下……过早的优化是万恶之源。” 换句话说,如果我们的系统中存在很大的瓶颈,则应该解决这些瓶颈。 我们可以用被动方式解决这些问题。
唐纳德·克努思(Donald Knuth)还说了另外一些非常有趣的事情:“对计算机不仅仅感兴趣的人们应该至少对底层硬件的状态有所了解。否则,他们编写的程序会很奇怪。” 他只是在说我们需要通过编写软件来利用我们今天拥有的硬件。
如果回溯到1973年,再看看处理器的制造方式,晶体管很少,时钟速度低于1MHz。 摩尔定律说,每两年晶体管和处理器速度就会增加一倍。 然而,当我们到达2011年时,时钟速度开始下降。 今天,以前需要一年半或两年半才能使时钟速度提高一倍,现在甚至需要十年甚至更长的时间。 晶体管的数量不断增加,但时钟速度却没有。 今天,我们拥有的是核心。 除了拥有更快的速度,我们还有更多的核心。 那么我们如何处理所有这些核心?
如果您一直跟上使用响应流的Spring和Reactor项目 ,那么这实际上就是我们现在所能做的。 我们有一个发布者,而一个发布者正在发布某些东西,我们称它们为域事件,即图2中的橙色小框。这些事件被传递给流中的每个订阅者。
图2-React式
请注意,信息流上的淡紫色框上带有问号。 这实际上是一项政策。 该策略在流和订户之间。 例如,一个订户可能对其可以处理的事件或消息有一个限制,并且该策略为任何给定的订户指定了限制。 重要的是,使用了单独的线程来运行发布者,流和所有三个订阅者。 如果这是一个智能实现的大型复杂组件,则在任何时间都不会阻塞线程。 因为如果线程被阻塞,那么难题的另一部分将饿死。 我们必须确保下面的实现也充分利用了线程。 随着我们对建模不确定性的深入研究,这将变得更加重要。
在微服务中,我们是被动的。 但是,当我们查看内部时,有各种各样的组件可以同时运行或并行运行。 当事件在这些微服务之一中发布时,最终可能会在某些主题的有限上下文之外发布,可能使用Kafka。 为了简单起见,我们假设只有一个主题。 我们的响应式系统中的所有其他微服务都在使用该主题上发布的事件,并且它们在其微服务中进行响应式操作。
图3-React系统
最终,这就是我们想要成为的地方。 当所有事物都异步发生时,会发生什么? 这使我们陷入不确定性。
在理想情况下,当我们发布一系列事件时,我们希望这些事件按顺序交付,并且恰好一次交付。 每个订户将收到事件1,随后是事件2,然后是事件3,并且每个事件仅出现一次。 程序员被教导要嫉妒这种情况,因为它使我们对正在做的事情有把握。 但是,在分布式计算中,这只是没有发生。
微服务和React式带来了不确定性,首先是不确定事件可能以什么顺序交付,以及是否已多次接收事件,或者根本不接收事件。 即使您正在使用像Kafka这样的消息传递系统,如果您认为要按顺序使用它们,那也是在自欺欺人。 如果有任何消息出现故障的可能性,则必须计划所有消息都发生故障。
如果有任何消息出现故障的可能性,则必须计划所有消息都发生故障。
我为不确定性找到了一个简洁的定义:不确定性的状态 。 作为状态的不确定性意味着我们可以处理它,因为我们有推理系统状态的方法。 最终,我们希望处于一种确定且可以感到舒适的状态,即使该状态仅持续一毫秒。 不确定性不断攀升,这使事情变得不可预测,不可靠且具有风险。 不确定性令人不舒服。
大多数开发人员都学会了开发软件的“正确方法”,该软件可以引导人们上瘾。 当您有两个需要相互通信的组件时,将一个称为客户端,将另一个称为服务器很有帮助。 这并不意味着它必须是远程客户端。 客户端将在服务器上调用方法或调用函数。 发生该调用时,客户端只是坐在那里,等待接收来自服务器的响应,否则可能会引发异常。 在任何情况下,客户端都可以确定它将再次获得控制权。 确定执行流程是我们必须处理的一种成瘾。
第二类常见的成瘾是对事物排序的成瘾,没有重复。 这是我们幼小的时候在学校教给我们的东西,当我们学会按顺序计数时。 当我们知道事情发生的顺序时,这会让我们感觉很好。
另一个沉迷是锁定数据库。 如果我在数据源中有三个节点,那么当我向那些节点之一写入数据时,我相信我已经牢固地锁定了数据库。 这意味着当我获得成功响应时,我相信数据将持久保存在所有三个节点上。 但是,然后您开始使用Cassandra,它不会锁定所有节点,如何查询尚未在集群中传播的值?
所有这些事情使我们感到不舒服,我们必须学会应对。 但是,可以感到不舒服。
由于我们沉迷于确定性,阻塞性和同步性,因此开发人员倾向于通过建造堡垒来应对不确定性。 如果要创建建模为有限上下文的微服务,请对其进行设计,以使该上下文中的所有内容都处于阻塞,同步且不可重复的状态。 那就是我们建造堡垒的地方,因此所有不确定因素都存在于我们的研究范围之外,我们可以肯定地发展。
从基础结构层开始,我们创建一个重复数据删除器和一个重新排序器。 这些都来自企业集成模式。
如果事件1、2、1和3通过重复数据删除器进入,我们可以确定我们只有事件1、2和3。这似乎很难解决,但是考虑一下实现。 我们可能会启用一些缓存,但是我们无法在内存中存储无限事件。 这意味着要有一些数据库表来存储事件ID,以允许检查每个传入事件以了解之前是否曾见过该事件。
如果以前没有发生任何事件,请传递它。 如果以前看过它,那么可以忽略它,对吗? 好吧,为什么以前见过? 是因为我们不承认收到它吗? 如果是这样,我们是否应该承认我们再次收到了它? 我们将这些ID保留多久才能说我们看过活动? 一个星期够吗? 一个月怎么样
谈论不确定性。 试图通过抛出技术来解决这个问题可能非常困难。
对于重新排序的示例,假设我们看到事件3,然后是事件1,然后是事件4。无论什么原因,事件2都挂了很长时间。 我们首先必须找到一种方法来对事件1、3和4进行重新排序。如果事件2尚未到达,我们是否有效地关闭了系统? 我们可以允许事件1到事件之间通过,但是我们可以有一些规则说直到事件2到达才处理事件1。 一旦事件2到达,我们就可以安全地将所有四个事件放到应用程序中。
无论选择哪种实施方式,所有这些事情都很困难。 存在不确定性。
最糟糕的是,我们还没有解决任何业务问题。 我们只是在解决技术问题。 但是,如果我们承认分布式计算现在已经成为我们业务的一部分,那么我们可以在模型中解决这些问题。
我们想说:“停止一切。好,我现在准备好了。” 但是没有“现在”。 如果您认为自己的业务模型是一致的,那么它可能仅保持一纳秒的一致性,然后又变得不一致。
一个很好的例子是对LocalTime.now()
的Java调用,这根本不是真的。 调用它后,就不再是它了。 当您收到回复时,现在不再是。
所有这些使我们回到了分布式系统都与不确定性有关的事实。 分布式系统将继续存在。 如果您不喜欢,那就换个职业。 也许开一家餐厅。 但是,您仍将使用分布式系统,只是不会编写它们。
建模不确定性很重要。 这很重要,因为这里有多个核心。 这很重要,因为云将继续存在。
微服务很重要,因为这是每个人的前进方向。 大多数人向我介绍了学习域驱动设计的方法,因为他们将其视为实现微服务的绝佳方法。 我同意这一点。 而且,人们和公司都希望摆脱困境。 他们被困在泥里,要花几个月才能释放出来。
延迟很重要。 当网络作为分布式系统的一部分时,延迟很重要。
物联网很重要。 许多小型廉价设备,都通过网络进行通信。
我所说的好设计,坏设计和有效设计。
您可以很好地完全设计软件,但却错过了业务需求。 以SOLID为例。 您可以将100个类设计为SOLID,并且完全错过了业务需求。 OO和Smalltalk的发明者Alan Kay说,关于对象的真正重要的事情是它们之间发送的消息。
Ma,日语单词,意为“之间的空间”。 在这种情况下,对象之间的空间。 这些对象需要设计得足够好,以便它们能够发挥其单一,负责任的作用。 但这不只是关于对象的内部。 我们需要关心对象的名称以及它们之间发送的消息。 这就是无处不在的语言的表达能力所在的地方。如果您能够在模型中捕捉到这种语言,那将使您的模型有效而不仅仅是好,因为您将满足业务需求。 这就是域驱动设计的意义所在。
我们要开发React系统。 我们将处理不确定性是一种状态这一事实。 我们将开始解决一些问题。
帕特·海兰德(Pat Helland)在他的论文《超越分布式交易的生命:叛逆者的观点 》中写道:“在无法依靠分布式交易的系统中,必须在业务逻辑中实施不确定性的管理。” 在实现分布式事务的悠久历史之后,才实现了这一实现。 但是,当他在亚马逊工作时,他确定使用分布式交易无法达到所需的规模。
Helland在论文中谈到了活动。 当一个实体的状态发生变化并引发描述该变化的事件时,两个伙伴实体之间就会发生活动,而第二个实体最终希望收到该消息。 但是何时会收到消息? 如果从未收到该怎么办? 有很多“假设”,帕特·海兰德说这些“假设”应该由活动来处理。
在任何两个合作伙伴之间,每个合作伙伴都必须管理从其合作伙伴到自己所看到的活动。 图4是我解释Pat Helland这些含义的方式。
图4-活动
每个合作伙伴都有一个PartnerActivities
对象,该对象代表我们从中接收活动的一个相关实体的活动。 当我们看到针对我们的活动时,我们可以记录下来。 然后,在将来的某个时间,我可以问:“我见过这种伙伴活动吗?” 这既可以处理我依赖于已发生的活动的情况,也可以处理我是否再次看到它的情况。
这相当简单,但是对于一个寿命长的实体而言,情况变得更加复杂。 甚至Pat Helland都说这可以极大地发展。 任何给定的长期存在的实体(可能有很多合作伙伴)都可能导致收集庞大的实体,或者DDD称为汇总实体,以试图跟踪活动。 此外,这不是明确的,也没有说关于业务本身的事情。
我认为我们应该比PartnerActivity
更进一步。 我提出的技术是将其放在域模型的软件核心中。 我们消除了基础架构层中的那些元素(重复数据删除器和重新排序器),并在发生一切时让所有内容通过。 我相信这使系统不太复杂。
在一个显式模型中,我们侦听事件并发送与业务操作相对应的命令。 我们的域聚合中每个都包含有关如何处理这些命令的业务逻辑,包括如何应对不确定性。 事件和命令的通信由流程管理器处理,如图5所示。虽然该图看起来很复杂,但实际上实现起来非常简单,并且遵循响应式模式。
图5-流程管理器
每个域实体都根据其接收的命令负责跟踪其状态。 通过遵循良好的DDD惯例,可以基于这些命令安全地跟踪状态,并使用事件源来保持状态更改事件。
根据领域专家的决定,每个实体还负责知道如何处理任何潜在的不确定性。 例如,如果收到重复事件,则聚合将知道它已经看到该事件,并可以决定如何响应。 图6显示了一种解决方法。 收到deniedForPricing()
命令时,我们可以检查当前进度,如果已经看到PricingDenied
事件,则不会发出新的域事件,而是响应以表明已经看到拒绝事件。
图6-处理重复消息
这个例子几乎让人难以理解,但这是故意的。 它表明,将不确定性视为我们领域的一部分意味着我们可以使用DDD实践来使其成为我们业务逻辑的另一个方面。 这确实是DDD的重点; 以帮助管理软件核心中的复杂性(例如不确定性)。
翻译自: https://www.infoq.com/articles/modeling-uncertainty-reactive-ddd/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1
react 建模