CQRS-与-DDD-软件开发的黄金组合
引言
随着数据密集型应用程序对处理和存储能力要求的不断提升,单一的工具已无法满足这些广泛的需求。为了应对这种挑战,业界逐渐采用了一种新的方法,将总体任务拆分成一系列由单个工具高效完成的较小任务,并通过应用代码将这些任务集成在一起,对外提供服务,同时屏蔽内部的复杂性。这种拆分也可能带来新的问题。代码组织结构不合理导致的复杂性
在日常开发工作中,我们经常会遇到以下情况: 同时处理写入(Command)和读取(Query)操作,导致代码复杂度大幅增加。 采用不同的复杂性揉在一起,形成恶性循环,最终陷入难以解脱的复杂性旋涡。 这些问题的本质在于代码组织结构的不合理。CQRS:命令查询职责分离
CQRS(Command Query Responsibility Segregation)是一种软件设计模式,它将写入操作(Command)和读取操作(Query)完全分离。这种分离的核心思想是拆分,将复杂系统拆分为两个独立的部分,并针对不同的场景使用不同的模式,选择最合适的技术,避免相互掣肘和影响。 CQRS的主要目的是降低系统的整体复杂性。它的逻辑原理如下: 在同一个模型中处理命令和查询时,系统的复杂度为 MN,因为两者相互影响,调整一方同时要时刻关注对另一方的影响。这种相互交织的设计方式使得两者之间的相互影响成为系统最复杂的部分,大量的精力消耗在排查影响,而非最有价值的设计和编码。 如果将命令和查询彻底分离,系统的复杂度变成 M+N。命令的变更不会影响查询,而查询的修改也不会影响命令。 在实际工作中,系统复杂度通常介于这两个极端之间。分层架构中的冲突与拆分
我们经常使用的分层架构也可以进行 CQRS 纵向拆分,将每层的组件拆分为命令和查询两部分。 以最常见的分层架构为例: ``` UI 层 应用服务层 领域层 仓储层 基础设施层 ``` 其中的应用服务层拆分最具价值,能够避免许多不必要的麻烦。命令和查询存在以下主要区别: | 命令服务 | 查询服务 | |---|---| | 依赖验证服务和无缓存仓库 | 依赖缓存仓库 | | 核心流程为验证、加载、业务操作、同步、发布事件 | 核心流程为验证、加载、数据组装、转换 | | 主要增强功能为事务管理器 | 主要增强功能为缓存组件 | 通过应用服务层拆分,可以有效避免使用错误的组件,从而消除常见的烦恼。模型层冲突与拆分
模型层是系统的核心,其设计直接影响整个系统的质量。模型层常用的复杂流程实现策略包括: 贫血模型 充血模型 领域驱动设计(DDD) 对于哪个策略才是最优解,业界一直争论不休。我认为,脱离具体的业务场景讨论方案是没有意义的。根据不同的应用场景,可以得出以下结论: 对于简单的读写场景,贫血模型即可满足需求。 对于复杂的业务逻辑,DDD 可以提供更好的支持。总结
拆分是一种重要的设计原则,可以有效降低系统复杂性。通过对代码组织结构、分层架构和模型层进行拆分,我们可以更轻松地管理复杂性,提高代码的可维护性,最终打造出更可靠、更可扩展的系统。2022年软件开发的十五种趋势 - geekculture
以下是通过参加了一些关于软件开发的会议搜集到的软件开发趋势:
1. 可观察性[跟踪、监控和记录]是至关重要的! 你正在开发你的软件,并且你已经准备好部署它。所有的测试都通过了,测试覆盖率也达到了一个不错的水平。知道了这一点,我们就可以部署我们的代码,并继续平静地工作。尽管这不是最理想的情况(也很罕见),但我们的代码仍然可能失败。是的! 因此,开发人员需要一直观察他们的代码,并让它一直报告指标。万一有什么故障,你需要让你的系统准备好向你提供日志。可观察性是至关重要的。没有它,开发者就是瞎子。它让我们有机会随时对系统中发生的每个问题做出反应。
2. 同时使用 无服务器 和 有服务器 方法是一个很好的做法。 在这种情况下,我们可以从两种软件开发方法中获益。无服务器是一种在没有任何服务器参与的情况下运行应用程序(看似)的方式。当然,这是一个重大的简化--总是有服务器参与其中;只是在这种情况下,你不需要对它们做任何事情,而且它们是预先配置好的。它被吹捧为新的黑 科技 ,除了......它并不是解决所有疾病的完美疗法。首先,你不能配置底层服务器,正如我们之前提到的。你也不能真正知道引擎盖下有什么。这个主要的缺点同时也是这个方法的主要优点。你不需要配置任何东西,所以与其说是部署 担心,不如说是部署 忘记。无服务器或有服务器的解决方案都有好处。在现代系统中,通常会加入两种方法来获得大部分的解决方案。
3. 容器化一切! Kubernetes是一项热门技术! 并非所有的软件开发趋势都是好主意。你还记得CoffeeScript或Ruby吗?很遗憾,我们有。幸运的是,Kubernetes(K8S)看起来并不像要加入这两者的悲哀谷中。K8S正在使 DevOps 专家的生活变得更加、更加、更加容易。以下是引入容器化和容器协调作为你的技术战略的核心条款所能带来的好处。
Pearson案例研究 | Kubernetes:缩短新功能的上市时间,将配置速度从几个月提高到几分钟,并确保为一家服务于 7500 万用户的教育公司提供高 SLA。
Prowise 案例研究 | Kubernetes:应用程序版本之间的停机时间为零,新部署几小时到几秒,在包含许多产品的复杂开发环境中,新版本的速度提高了 3 倍。
Zalando 案例研究 | Kubernetes:欧洲 时尚 电子商务领导者使用 K8s 实现可 扩展性 ,支持多种业务用例,如当日交付、多租户,增加其产品和地理范围,并使他们能够重写和创建所有 SaaS 产品他们一直用作定制软件。
阿迪达斯案例研究 | Kubernetes:电子商务网站的加载时间减少了一半,每天发布多次而不是每月一次,由于阿迪达斯转向 云原生 ,开发人员拥有更多的自主权。
4. 当涉及到软件架构时,我们应该分而治之 大规模的单体在某种程度上是一个昨天的故事。它们长期困扰着开发者,不过现在已经不是了。将巨大的单一代码库分割成较小规模的应用程序是新的做事方式。它可以使你的应用程序防火,减少错误的频率,使应用程序在发生错误时更加安全。缺点是,应用程序变得更难测试,而且需要更多的资源来完成。对于规模较小的团队来说,维持一个单体还是比较有意义的。将一个单体应用划分为独立的 微服务 。
5. 开源和自由软件是未来的方式。 React、Angular和Zuul,分别来自Meta(曾经是Facebook)、谷歌和Netflix,是无数开发者每天在工作中使用的工具。如果没有这些组织向所有愿意使用它们的人免费发布的工具,每个人的工作就会变得更加困难。无数的服务将不会出现在阳光下,因为编写这些应用程序太难或太耗时了。所有这些都是因为,在编写这些应用程序之前,人们必须弄清楚如何为规模而编写前端,而不分享所学到的经验将是极其低效的。这就是为什么我们要赞扬开源和自由软件的维护者、创造者以及所有其他为创造和维护这种软件做出贡献的人。创造一种工具/技术并使其开源(或使其免费),给组织带来永恒的荣耀。
6. 使用架构模式 在软件开发中,有一条常见的规则--不要重新发明车轮。知道我们很可能曾经面临过与别人相同的问题,这条规则就变得更有价值。这就是为什么世界各地的工程师和开发人员都使用建筑模式来构造他们的项目--而不是把时间浪费在思考如何找出别人已经想出的解决方案上。许多现代的软件都使用 CQRS 和Event Sourcing等模式。不要重新发明轮子,要使用这些模式。
7. 编程语言在不断发展。 我们有越来越多的新的编程语言这一事实并不奇怪。它们都是来来去去,离开后又被其他语言取代。没有人再用Algol或Pascal编码了。然而,有一个老前辈,C,仍然存在,尽管这是个值得单独探讨的话题。一个值得注意的方面是它们在这些年里的演变方式。起初,命令式语言是唯一存在的。然后,面向对象的语言蓬勃发展,现在,有些人可能会争辩说,它们正被更灵活的语言所排挤,这些语言混合了一些命令式、函数式和 面向对象 的特性。语言的发展方式越来越独立于我们工作的系统,也越来越独立于我们的系统。现代语言是跨平台的。由于DevOps的发展,语言的选择变得越来越不重要了。
9. SCRUM != AGILE 采用特定的流程通常会导致学习行为,最终形成习惯。至少,这是它的理论。然而,在某些情况下,流程仍然是流程,人们只是为了走过场而苦苦挣扎,但行为从未发展。这样想吧,你见过多少开发团队经历了所有的Scrum仪式,但实际上没有以敏捷的方式工作?太多了吗?我们同意。那么你能做什么呢?首先,团队买入,这永远是需要建立的第一步。如果你的团队没有看到使用这种方法工作的价值,那么从长远来看,所有的流程和仪式都不会有什么进展。第二步是确保你有一个优秀的Scrum主管和项目经理,以确保良好的实践被传递下去,并确保任何反对意见被采纳。第三步是认识到:当敏捷价值和Scrum框架没有任何价值时,将其强行灌输到人们的喉咙里,会让你很快就一无所获。我们在题为 Scrum不是每个IT项目的答案()的文章中已经详细介绍了这一点以及更多的内容。SCRUM可以是敏捷的,但它并不能保证敏捷性。敏捷性来自于行为,而不仅仅是流程。
10. 持续安全 正如我们以前多次写过的那样,安全不能是事后的想法。我们不能简单地 留待以后。检查应用程序的安全问题必须被整合到DevOps流程中,并且从第一天开始就整合到开发流程本身。幸运的是,我们可以使用一些工具来使这个过程无摩擦。Snyk就是其中之一。这是一个全面的工具,找出并自动修复你的代码、开源依赖、容器和基础设施作为代码的漏洞[...]。我们必须在开发周期中应用安全检查程序。安全是信任的基础--未来的货币。
11. 审计云供应商的服务价格 由于三个主要的云计算供应商几乎不享有竞争,而且他们提供的服务的差异是(或多或少)任意的。在现实中,我们可能看到的唯一差异是服务价格的差异。这就是为什么,对这个特定的供应商有偏见并不一定是坏事。大多数情况下,确实没有什么区别。选择你感到满意的、已经了解的供应商。边走边评估,不要害怕改变。云供应商没有虚拟竞争,也没有成本套利。云基础设施的成本非常依赖于通货膨胀和经济衰退。
12. 一切都可以 作为一种服务 来做。 平台即服务,基础设施即服务,数据库即服务,软件即服务,后台即服务......我们没有给你更多的例子,你应该明白我们的意思。你能想到的一切都可以由第三方完成并出售给你。使用这些服务是一种折衷。你放弃了一些控制权,以便变得更精简,能够更快地迭代,同时也能在前期节省一些钱。由于云供应商和 无服务器 方法的重要性的增长,每一个软件都可以作为一个服务来完成。
13. 每个人都在使用Visual Studio Code Visual Studio Code在世界范围内掀起了一场风暴。有微软的支持,有开源许可证,用TypeScript编写,并允许轻松扩展功能,这些组合都是伟大的决定。到目前为止,文本编辑器是现代程序员中最受欢迎的选择。其他选择,如基于Intellij的集成开发编辑器(IDE)或Vim,都在Code的阴影下,尽管JetBrains的Fleets可能会改变这种情况。由于有多种扩展和定制工具,VS Code成为开发者中最受欢迎的IDE。
14. 如今,TensorFlow被广泛使用 TensorFlow是谷歌的机器学习框架,在程序员中是一个非常受欢迎的选择。首先,它在GitHub的最多星级存储库中排名前20。然后,有多个端口,包括JavaScript端口,团队在他们的例如React Native应用程序,或React或任何其他JS框架的Web应用程序中使用。这提供了巨大的灵活性,并允许团队将解决方案嵌入许多解决方案中。由于TensorFlow,我们可以在网络应用中实现AI解决方案。用于训练的模型是由库提供的。开发人员应该专注于训练它们。
15. 一个很好的长期雇用策略是雇用后辈并培训他们 雇用后辈(后起之秀的年轻人)是一个很好的长期战略。虽然没有适合所有公司的 最佳策略,但雇用后辈并培训他们绝对是成长和保留内部人才的最佳方式之一。雇用后辈是一个很好的方式,可以随着时间的推移慢慢扩大你的团队,并建立一个内部文化,与雇用那些可能已经定型的人相比,更容易塑造。初中生还能提供一个新的视角,并更多地接触到当前的趋势。在一些情况下,这并不理想,例如,当你的公司需要迅速扩大规模和开发新功能时。如果你有一个小的内部团队,由于不现实的开发期望,他们总是试图赶上他们的积压工作,这也不是最好的。在这种情况下,雇用一个外部技术合作伙伴来帮助开发,同时同步扩大内部团队的规模,可能是一个很好的中间解决方案。雇用后辈来培训他们的策略并不是没有陷阱。加入你的团队的年轻人没有经过以前公司的审查,他们没有工作经历,而且很可能是一击即中。不幸的现实是,虽然这种策略在适当的补偿方案下可以很好,但初级雇员可能会发现自己处于这样的位置:他们只需转移公司,而不是等待或推动晋升或加薪,就可以使自己的工资翻一番、三番,甚至四番。这就是为什么拥有透明的工资和薪资表是如此重要,以显示人们在职业道路上可以在哪里以及如何晋升。这就是为什么拥有优秀的入职培训计划也非常重要,以确保花在培训后辈上的时间得到很好的利用,使导师和学员都受益。
原文:2022年软件开发的十五种趋势 - geekculture
学习scala有哪些好的资源
为什么学习函数式编程在阅读DDD巨著《Patterns, Principles, and Practices of Domain-Driven Design》的过程中,Scott在第5章提到了使用函数式编程语言配合贫血模型去实践DDD的一种思路,这激发了我的无限遐想。 在开发领域,我们已经拥有了许多的抽象方法论和大量的实现技术。 但我个人认为,这一切归根结底,都是人类思维在开发领域的具体表达方式。 而人类在认识和分析所要解决的业务领域问题时,思考的内容不外乎由两个部分组成:『业务流程』与『业务规则』。 前者,回答了业务活动中先做什么后做什么的问题;后者,则回答了遇到什么情况时应该怎么做的问题。 两者结合后,得到我们需要的业务结果,或者叫作“实现业务目标”。 再想想目前学习和掌握的面向对象的一系列方法,又是如何将上述思维结果映射到中去的呢?我认为是这样的:对于业务流程,我们将其表达为若干对象之间的合作,比如UML里序列图的对象与消息,进而具化为具体的类及其职责,比如类及其若干业务方法。 对于业务规则,我们将其表达为若干的判断逻辑,比如UML流程图里的判断分支,进而具化为业务方法里的if-else语句,或者再复杂一点,表达为工厂、策略等设计模式的实际运用。 然后,我认为,对于复杂业务规则的梳理,可以象数学归纳法一样进行演绎:假设一个函数y=f(x),给定x的定义域,确定y的值域。 特别是在排列组合等方面的一些问题,也经常采用递归的方式来解决。 所以,从这个角度讲,函数式编程更贴近人类思维习惯,所以让我自然而然地把目光转向了它。 为什么选择Scala在选择具体的函数式编程语言时,我首先想到的是它最好是同时能支持面向对象编程的。 因为即便LISP作为函数式编程语言的先祖,诞生已长达半个世纪,但单纯的函数式编程语言与面向对象编程语言相比,在抽象领域概念、组合系统模块、实现信息隐蔽等方面存在一定的差距,所以一直没有成为开发的主流。 信息隐蔽原理:在西安电子科大蔡希尧与陈平老师于1993年合作出版的《面向对象技术》一书中是这样描述的:把需求和求解的方法分离;把相关信息——数据结构和算法,集中在一个模块之中,和其他模块隔离,它们不能随便访问这个模块内部的信息。 其次,由于我的语言路线是从Pascal → C → C++ → C#,所以我希望能选择一种风格近似于C、强类型的函数式编程语言。 在比较了F#、R、ErLang等几种常见的函数式编程语言之后,我最终选择了Scala。 Scala有何优势注:以下内容,节选翻译或参考自《Programming in Scala》第1章、第3章,《Programming Scala》第6章,不算完整意义上的个人心得。 函数式编程的优势纯的函数是没有副作用的。 无论何时何地,对于一个函数y=f(x),给定x必定得到y,不会因此产生二义结果。 因此无论对于代码测试还是并发,由于给定输入必定得到预期输出,而不受其他因素干扰,所以能有效减少Bug产生。 在函数式编程里,大量使用immutable的值。 这意味着函数运算的结果总会创建一个新的实例,避免了通常并发环境下为防止数据共享冲突而采取的保护机制。 尽管这需要额外的Copy操作,但Scala针对性地提供了高效的Copy实现,以及延迟计算等弥补机制。 函数是一等公民。 函数作为表达式的一部分,可以借由函数之间的嵌套、组合,实现复杂的判断逻辑。 Scala语言本身的优势Scala是面向对象与函数式编程的混合语言,所以能有效结合二者的优点。 Scala属于Java生态圈,可以在JVM上与Java一起编译运行,所以许多Java的框架、工具都可以直接应用于Scala语言编写的项目。 Scala视一切数据类型皆为对象,且支持闭包、lambda、by-name参数等特性,语法简洁明快。 Scala使用Actor作为并发模型,与Akka框架自然契合。 这是一种区别于传统的、基于数据共享、以锁为主要机制的并发模型,其特点在于以Actor为基本单位、没有数据共享、基于消息传递实现Actor之间的协作,因此可以有效避免死锁、减少竞争。 最后,如果有朝一日要转向大数据领域,有Spark这样的大型框架作为支撑。 知乎:与 Hadoop 对比,如何看待 Spark 技术?Scala对实践DDD有何意义说了那么多,我的根本目的还是要将Scala作为实现DDD的主要武器。 那么试想一下,Scala在我们实现DDD的过程中能有哪些帮助呢?我暂且胡侃乱诌如下:表示值对象、领域事件等元素更直观。 值对象、领域事件在DDD里都应该是immutable的,以往多采取POCO形式表示,现在改用Scala里的val以及case class表示,在语法层面就直观地表明是不可修改的。 在类的方法层面实现CQRS时有语法支持。 用Scala里的Function(返回类型为非Unit)对应CQRS里uery,保证类的方法没有副作用;用Procedure(返回类型为Unit)对应CQRS里的Command,明确表明这一类方法会产生修改状态等副作用。 这同样从语法层面就能对二者进行明确区分。 模式匹配丰富了函数操作。 除了正则表达式,Scala形式多样的模式匹配语法,为提取数据、完成数据分组聚合等运算、实现逻辑判断提供了强大支持。 比如定义def sum_count(ints:Seq[Int) = (, )这样一个函数后,我们可以这样调用,以得到一个1至6的整数序列的整数值合计,及该序列的尺寸:val(sum, count) = sum_count(List(1, 2, 3, 4, 5, 6))。 为实现DSL提供有力支持。 Scala自带有解析框架,加上灵活的函数语法支持,要自己实现一套DSL及其相应的语法解析器将不再困难。 比如在配置文件里这样的一条配置语句,表示退休条件为年龄达到60周岁或者工龄届满30年:retire = (Age >= 60) || (ServiceLength >= 30)。 以往的方式是自己写一个语法解析器,把这条文本转换成相应的Specification对象,然后扔给聚合去使用。 现在有了Scala的帮助,就使编写语法解析器这一环节的工作量大大减少。 合理的高阶函数设计,使规则编写得到简化。 比如规则、费用报销规则,以往可能需要若干层的if-else嵌套,现在则将通过高阶函数得到大幅简化。 对此,我强烈刘光聪先生的Refactoring to Functions,你会在刘先生的重构过程中发现高阶函数的强大。 Actor为高效并发打下基础。 Actor内部完全自治,自带用于存储消息的mailbox,与其他Actor只能通过消息进行交互,每个Actor都是并发的一个基本单位。 这些特点,非常适合于采取Event Sourcing方式实现的DDD。 每个聚合都好比一个Actor,在聚合内部始终保持数据的强一致性,而在聚合之间交互的领域事件则好比Actor之间的消息,聚合之间借由领域事件和Saga保证数据的最终一致性。 Trait成为AOP利器。 Trait是Scala的另一大特色,它就象AOP织入一样,能动态地给某个类型注入方法或者结构。 比如配合类Circuit和with后面那4个Trait的定义,val circuit = new Circuit with Adders with Multiplexers with Flipflops with MultiCoreProcessors这样就创建了一个带有加法器、乘法器、触发器和多核处理器的元件。 隐式实现为类型扩展提供支持。 对应C#里的静态扩展方法,Scala通过implicit为实现数据类型的方法扩展提供了便捷,成为Trait之外的另一个功能扩展手段。 能降低常见BDD框架的学习成本。 尽管这一点可能比较牵强,但我正在努力摸索如何将BDD与DDD结合,而常见的Cucumber、Spock等一些BDD框架,其语法与Scala比较相近,所以我才有如此一说。 有哪些Scala学习资料以下是我目前主要的学习资料,并衷心欢迎各位留言补充。 书籍Programming in Scala:由Scala语言的设计师Martin Odersky编写,循序渐进,配合了大量实例,入门必读吧。 Programming Scala:视角与上面那本有点不一样,没有Scala语言基础会感觉很困难,适合掌握了基本语法后温故而知新。 在线文档与Scala 官方文档:Scala的,作为寻找资料的出发点是不错的。 Scala 课堂:中文版的Scala基本语法在线课堂。 Scala Synatax Primer:由Jim McBeath整理的Scala语法概要,可以当字典用。 The Neophyte’s Guide to Scala:很出名的一个Scala入门指南,以Scala中的提取器Extractor作为实例开始。 Scala 初学指南:这是上面那本指南的中译本。 Effective Scala:中文版的Scala高效编程SBT中文入门指南:Scala Build Tool社区Scala 中文社区:不算活跃,原因你懂的。 Scala User:Scala入门者聚集地,没有Stack Overflow那么严格,但也需要点爬墙的身手。 SDK及IDEJava SE:先装这个Scala SDK:再装这个SBT:然后装这个IntelliJ IDEA:最后装这个,就能比较方便地开始Scala编程了写在最后最近读的书很多也很杂,DDD、BDD、Scala、Cucumber以及Java基础等等都有涉及,真恨不得一口吃成个大胖子。 由于时间和精力有限,所以现在知识消化很成问题,迟迟没有进入学以致用的环节,只能先这样纸上谈兵了,好歹先把自己在学习过程中的一些思考、看到的好东西先记载下来,以备将来之需。
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。