当前位置: 首页 > news >正文

北京市专业网站建设宣传片制作公司有哪些类型

北京市专业网站建设,宣传片制作公司有哪些类型,专业制作app的公司,淄博建网站哪家好尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格#xff0c;遇到很多很重要的面试题#xff1a; 谈谈你的DDD落地经验#xff1f; 谈谈你对DDD的理解#x…尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格遇到很多很重要的面试题 谈谈你的DDD落地经验 谈谈你对DDD的理解 如何保证RPC代码不会腐烂升级能力强? 微服务如何拆分 微服务爆炸如何解决 你们的项目DDD是怎么落地实操的 所以这里尼恩给大家做一下系统化、体系化的梳理使得大家可以充分展示一下大家雄厚的 “技术肌肉”让面试官爱到 “不能自已、口水直流”。 也一并把这个题目以及参考答案收入咱们的 《尼恩Java面试宝典PDF》V132版本供后面的小伙伴参考提升大家的 3高 架构、设计、开发水平。 最新《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF请关注本公众号【技术自由圈】获取后台回复领电子书 除了本文尼恩输出了一个 《从0到1带大家精通DDD》系列帮助大家彻底掌握DDD链接地址是 《阿里DDD大佬从0到1带大家精通DDD》 《阿里大佬DDD 落地两大步骤以及Repository核心模式》 《阿里大佬DDD 领域层该如何设计》 《极兔面试微服务爆炸如何解决Uber 是怎么解决2200个微服务爆炸的》 《阿里大佬DDD中Interface层、Application层的设计规范》 《字节面试请说一下DDD的流程用电商系统为场景》 《DDD如何落地去哪儿的DDD架构实操之路》 《DDD落地从腾讯视频DDD重构之路看DDD极大价值》 《DDD落地从美团抽奖平台看DDD在大厂如何落地》 《美团面试微服务如何拆分原则是什么》 《DDD神药去哪儿结合DDD实现架构大调优》 《DDD落地从网易新闻APP重构看DDD的巨大价值》 大家可以先看前面的文章再来看本篇效果更佳。 另外尼恩会结合一个工业级的DDD实操项目在第34章视频《DDD的学习圣经》中给大家彻底介绍一下DDD的实操、COLA 框架、DDD的面试题。 DDD现在非常火爆是有其巨大生产价值经济价值的 绝不仅仅是一套概念那么简单。 DDD的绝大价值具体请参见以下视频 从腾讯视频DDD重构案例看看DDD极大价值 文章目录 尼恩说在前面阿里单据系统的DDD最佳实践一、前言二、单据1、生命周期1领域概念贫血实体2领域知识生命周期3领域模式聚合与聚合根 2、隐式概念1领域知识单据字段2概念突破命令实体 3、深层模型1领域知识状态推进本质2深层模型修正状态机模型 4、边界模型1领域知识边界隐式概念2领域模式防腐层3隐式概念重构中发现模型 5、领域服务 三、后记参考书籍说在最后尼恩技术圣经系列PDF 阿里单据系统的DDD最佳实践 作者少岚阿里同城履约物流技术团队 本篇以电商购物场景为背景探讨了领域驱动设计DDD在实际应用中的实践过程。你会发现DDD 的核心理念在于通过一系列实用技巧挖掘出能揭示问题本质的领域模型并通过模型间的协作解决领域问题从而驾驭问题领域的复杂性。对于 DDD 爱好者来说它犹如一个充满挑战和智慧的玩具在深入思考问题本质和构建抽象知识模型的过程中让人沉浸于心流状态。 一、前言 领域驱动设计Domain-Driven Design简称 DDD并非一种框架或具体的架构设计而是一种架构设计思想。其代表性著作便是“领域驱动设计之父”Eric Evans 的经典书籍《领域驱动设计》。DDD的核心目标是通过各种实用方法和技巧提炼出具有体现问题实质的领域模型并通过保护和组织模型协作来解决领域问题从而掌控问题领域本身的复杂性也就是为什么DDD会被认为是软件核心复杂性的应对之道。 DDD的理想应用场景是具有固定领域体系且复杂性较高的应用软件系统设计的各个环节和过程但这无疑是一项艰巨的任务。DDD要求技术人员高度协同提升建模技巧精通领域设计并通过不断的时间推移和领域知识的吸收消化以达成应对复杂性的目标。只有这样DDD的价值才能在项目的中后期得到充分体现。本文旨在带领大家从第一视角体验这种实践过程感受DDD的独特魅力掌握其精髓为在DDD中探索的朋友们指明方向。 我个人对面向对象编程有着浓厚的兴趣编写代码如同孩子玩玩具般充满乐趣。DDD让我有机会玩得更高级、更复杂、更具挑战性的玩具。对于一个始终保持少年心态的程序员来说构建领域模型极易让人进入心流状态。这种深入思考问题本质构建抽象知识模型的过程让我对DDD情有独钟。 我想用两个词来表达我体会到的魅力知识、思考。 知识Eric Evans发行的《领域驱动设计》一书中第一章介绍的就是知识特别指领域知识但是这里的知识并不是简单的问题的表象而是深入到问题的本质只有获取到真正的知识运用好各种DDD模式和优秀的战术打造具有丰富知识设计的模型才能充分发挥领域驱动设计的好处。 思考获取知识并不容易。例如给你一批地球日出月落的数据你可以用地心说、日心说和地平说等不同模型来拟合地球的各种现象究竟哪个模型的知识最适合呢产品和业务提出需求时很多时候难以触及问题的本质。因此设计模型、选择模型都需要设计者做到深入思考挖掘概念并和领域专家如果存在达成一致。 本文是一篇关于DDD实践的典型案例文章读者也可认为它类似于一种多字段单据的设计模式。全文将以一个简化的电商购物背景作为领域上下文重点介绍领域组件的形成过程并突出DDD的核心要点。但同时需要注意到本文专注于单个领域上下文的战术实践不涉及多个领域上下文的协作。文章核心内容将按照4个小节展开 从实体生命周期出发围绕一个聚合根的设计作介绍包括原因、好处从单据字段的性质特点等挖掘出一类命令对象集合是体现如何从深层领域本质修正一个状态机模型从而改变了我的组件设计为状态同步模型根据防腐层的一些好处以及如何在防腐层中通过重构去捡回来重要的领域实体 通过本文希望大家能更好地理解和应用领域驱动设计为复杂业务场景找到解决问题的方法。 二、单据 1、生命周期 1领域概念贫血实体 为了简化问题本文将以简化的电商交易平台领域为例探讨其中的核心概念。简而言之即消费者在某个购物平台下单购买商品支付完成后商品按照计划送达消费者手中。 其中系统比较重要的就是订单订单作为单据是一种交易凭证表达了交易关系的事实依据。它主要涵盖了客户、商品、时间、支付等要素可作为会计核算的原始资料和重要依据。电商交易单据以电子化形式存在于信息系统中我们统一称之为交易主订单。 通常情况下一个交易主订单代表一次交易行为。其中的交易内容会用交易子订单表示例如用户一次性购买5个苹果3个梨子那就对应为一个交易主订单它刻画了用户的购买行为其中有两个交易子订单一个描述5个苹果一个描述3个梨子。 如果我们系统的子单可以单独发货甚至多仓发货的那么我们再加一个发货单的概念用作和包裹一一对应一个包裹可以放任意交易子单的物品例如上面的两个子单可以放到两个包裹用两个发货单表示一个发货单4个苹果另一个发货单1个苹果加3个梨子当然我们的电商系统还有商品、客户、收件人、供应商等实体现在我们在系统中有了这些实体如下图所示。 注意请点击图像以查看清晰的视图 在系统中实体具有各自的生命周期。一个交易主订单可能包含多个交易子订单一个包裹可以随意组合子订单进行发货。但这些模型相对较弱因为难以充实。如何将这些需求封装为知识以设计出更完善的模型只有在实际操作中才能找到答案。这也是系统初期面临的实际情况不应过度设计往往一开始就是一个简单的CRUD系统。 2领域知识生命周期 以上介绍的实体都有自己的生命周期生命周期体现在系统行为中。以简单电商系统为例从下单到服务结束基本经历以下过程行为 下单 用户提交订单 商品的库存占用 用户在规定时间内进行支付 订单阶段性状态推进待支付、支付完成、待发货、运输中、配送中、妥投等等 查询 生命周期中发生查询请求 取消 订单有效期到期取消订单 用户取消订单 以上流程都和上面提到的实体相关但具有相同生命周期的实体组合较少。例如订单实体的生命周期与客户完全不同。客户从注册到注销一直存在而订单仅在一次完整交易行为中存在。商品和订单也不同订单被取消生命周期结束而商品可以重新售卖。因此在商品、供应商、客户、交易主订单、交易子订单、发货单等实体中只有交易主订单和交易子订单具有相同生命周期过程还包括发货单。 另一方面我们看一下会改变交易主订单和交易子订单状态的一些代码行为通常我们会封装到服务类中代码在系统刚开始基本会写成如下这样 注意请点击图像以查看清晰的视图 但以上的依赖链路会导致各种单据实体异常稳定无人敢于轻易更改。随着需求的复杂化管理这种依赖关系显得尤为重要。如果不加改进这种方式可能会引发诸多问题以下列举了一些问题及其特点 事务一致性在某种程度上这些服务都需要改变订单单据的状态。以提交订单服务、订单支付服务、取消逻辑处理服务、时效管理服务、物流服务等服务为例它们都需要独立保证事务的逻辑一致性。这涉及到并发和乱序问题保持一致性的逻辑代码复杂且易错开发人员维护起来也会感到疲惫。跨进程操作订单更是灾难性的后果。 共同闭包性我们发现大部分交易主订单的状态是由子订单或发货单推进的。例如妥投一致性规则例子如果包裹从不同仓库发出可以走不同的路线。只有当发货单Asub1、Asub2、Asub3都妥投后才能将交易主订单AOrder修订为完成。每个包裹更新事件基本上都是独立的一次事务。假设Asub1的妥投事件同步过来我们必须将Asub2、Asub3、AOrder从数据库中取出进行检查和处理。实际上子单的状态也可能被发货单推动。如果发现实体组合中有许多这样的同时修改需求说明它们基本上是一个共同闭包的整体我们可以考虑将这样的组合进行抽象封装。 共性逻辑散落例如以上提到的维护妥投一致性规则的代码如果还有一些乱序状态回传处理的代码记录状态变化流水的代码这些代码各自的本质其实是基本相同的。这些重复的逻辑散落在各类服务中每次修改一个需求的时候可能需要修改各个服务。例如我需要在记录流水类换了接口签名实现那么我就需要在各个服务类中都去更换这个接口签名这样的共性逻辑散落对修改就是关闭的。 3领域模式聚合与聚合根 实际上对于熟悉DDD的人来说他们会很容易接触到聚集的概念。我们需要考虑是否可以将这些单据构建为聚集以及构建后是否存在其他潜在问题。在经过对系统各个方面的权衡之后我们基本上可以确定得大于失 知识拓展聚合与聚合根在具有复杂关联的模型中确保对象更改的一致性是具有挑战性的。不仅相互无关的对象需要遵循一定的规则紧密关联的对象组也需要遵循一定的规则。然而过于保守的锁定机制会导致多个用户之间无谓地相互干扰从而使系统不可用。针对这些模型我们采用一个抽象来封装它们之间的引用。聚集Aggregate是一种相关对象的抽象封装被视为数据修改的基本单位。每个聚集都有一个根Root和一个边界boundary边界用于定义聚集内包含什么而聚合根则是唯一对外的引用。——摘自《领域驱动设计》。 如下图所示我们先来观察将交易主订单、交易子订单和发货单包裹构建为聚集并以交易主订单作为聚集根后的效果。然后我将列举几个方面说明通过这些改变使得交易实体从贫血模型转变为充血模型还有这样做的理由 注意请点击图像以查看清晰的视图 聚合根一致性聚合模式的核心特征是所有涉及交易子订单的操作都会通过其聚合根交易主订单来执行并由聚合根负责保持它们之间的规则一致性。聚合之间的实体能够相互引用下面我们将详细介绍一些关于聚合根一致性的规则 主子单一致性不仅上面提到的妥投一致性规则还可以有出仓库一致性规则也就是说交易子单的出仓操作是独立的当所有发货单都已完成出仓交易主单才将状态更新为出仓。这一规则的逻辑维护责任将转移到聚合根交易主订单实体中每次聚合状态发生变化时都会触发一次检查发货单与子单一致性如前所述包裹是存放子单的部分数量的地方。每个包裹中包含哪些子单以及数量多少所有包裹的子单总数需要与源交易子订单逻辑保持一致。现在这一一致性得以由交易主订单保证不再分散。在发现不一致的情况下只需在一个地方进行报警监控。 聚合根封装细节所以很自然的我们也可以把一些散落在各个操作交易主订单和交易子订单的逻辑都封装到聚合根中 节点流水记录由于单据作业流水记录的节点与单据状态相对应因此流水记录逻辑可以被封装到聚合根中。每当状态发生变化例如从提交订单Accepted到支付完成Paid都会记录一条流水。在2.3节中我们将专门介绍封装在聚合根内的状态同步模型。需要注意的是这里并不是让主订单直接操作数据库它只需负责生成流水而将流水记录到数据库则应由领域服务负责订单状态推进各种事件支付、发货、妥投同步及异步回传的处理代码都将会封装到交易主订单中让主订单变更子订单和发货单状态逻辑只有一份可维护性强另外一个状态的变更用状态机是前期可以考虑的方案。 事务修改的基本单元有了聚合仓储必须得到整合。而仓储整合的关键是确保聚合的修改成为事务的基本单元这具有诸多好处 没有数据库概念取消逻辑服务、查询服务、支付逻辑处理服务等服务不在需要写一遍SELECT交易主订单交易子订单UPDATA交易主订单交易子订单等逻辑甚至没有INSERT这种逻辑而它们都只需两个动作拿出交易主订单聚合放交易主订单聚合回仓库副作用的保护以前的模型中各个服务都会对单据产生副作用。而现在只有交易主订单会对包裹和子单产生副作用。这种副作用还可以被监控下一节我们将详细介绍如何通过深入演进命令实体模型来保护这些副作用。 注意请点击图像以查看清晰的视图 有了以上的设计可以想得到如果需要添加新的状态或一致性逻辑只需在交易主订单聚合操作中进行即可。此外新增拒收回传服务也无需重新编写保障业务事务的逻辑无需编写一行记录流水的代码封装性和可维护性的价值得到了很好的保障。同时支付处理服务、取消逻辑处理服务和妥投逻辑处理服务等服务的职责变得单一代码逻辑变得更加轻松可读性也得到了提升。 聚合的坏处正如没有完美的架构一样聚合模式也有其利弊。以下是实施聚合根后需要面临的问题及解决方法 查询性能显然如果你只想修改交易主订单的一个字段仓储将加载所有相关的交易子订单这无疑会对性能产生负面影响。另一方面如果你一次性加载所有聚合实体那么不需要修改的实体也必须写回数据库但这可以通过一些微小的设计优化来解决例如根据聚合根修改了哪个实体为该实体添加不同的版本这样仓储就只会根据版本按需更新对象。另外有些状态变化可能对一致性没有影响但仍然会触发一致性检查这类性能影响不大。无谓的更新例如你只想更新单据的一个字段而你的SQL是这样写的UPDATE TABLE A SET A.name “Marry” WHERE XX而使用了聚合根之后就需要更新整个DO的多个字段。如果你不小心设置了其他字段它们也会被更新从而减少了犯错的成本。但这并不会成为大问题可以加入断言或显式打印每次修改字段的日志以便开发者迅速发现错误。属性访问访问单据的问题显而易见例如某个服务需要访问交易子订单的数据只能通过交易主订单进行交互这是否让人难以接受其实这个问题很容易解决只要将查询分离出来创建一套聚合的访问视图访问模型让交易主订单的充血方法返回这个访问视图让服务操作这个视图即可。而且这个视图可以在各种地方使用不必担心会产生副作用性价比非常高。 其实聚合根的设计不应该过大里面的实体种类最好不要太多上面例子提到的聚合只有3个Entity刚刚好但实际问题中最多三到四个实体就到了一个比较合适的度了而且这个时候聚合根的好处会体现的更明显。 2、隐式概念 1领域知识单据字段 这节我们将会直面单据类CURD最讨厌的问题它就是单据的字段。单据字段在MVC三层架构中程序员很可能会去偷懒直接用一个DO对象捅到业务层去最多加一个DTO对象。而在聚合根中字段更加会难以管理但如果你愿意用心去细细思考字段的一些特性说不定也能发现很多不一样的世界。 单据字段多样性单据最重要的作用是承载属性而且属性非常多如下面的交易子订单实体的属性而且还有各种用作关联的属性再加上拓展字段如果这些字段全部由聚合根去维护那么聚合根的方法会臃肿成怎么样子 Getter Setter public class TradeSubOrder {private Long id;private Date gmtCreate;private Date gmtModified;private boolean test;private StatusEnum status;// more fieldprivate String size;private boolean repositoryTrace false;private String extendAttribute;//还有更多// getter setter toString}如果聚合根有20个属性发货单有15个属性交易子订单有20个属性那么聚合根就要有(202015) * 2 110个属性访问器对外这个充血对象和DTO感觉是没有差别的而且新加一个字段需要加两遍这样看的话子单据、发货单等实体必须单独自己去管理自己的字段比较好而聚合根只需维护一致性的时候去访问该字段即可。 动态拓展字段如果要你做一个属性经常动态变化的实体你应该很容易想起把属性打平建立一个表存key、value、关联id或者直接加一个extAttribute的Map实现把属性打平后我们也不用担心实体的搜索问题因为现在的查询分离的宽表、NoSQL索引都比较强大了如果一些字段属性只是在单据上作展示和透传用的并无多少行为关联那么很建议这样做。 字段的内聚性分析一下订单不难发现一个订单的字段可以归类从每一类的修改入参可以看出各自都具有相同的修改原因如果字段是具有内聚性的那么多样性的字段就应该是可以分类治理的 交易主订单“收货地址”“收货人姓名”“联系电话”“邮箱”共同变化的原因联系人信息类交易主订单“客户id”“客户姓名”“会员等级”“账号”共同变化的原因购买者信息类交易主订单“支付方式”“支付单号”“支付状态”“支付时间”“实付金额”共同变化原因支付行为发货单“送达时间”、“服务时效”、“配送员”、“物流订阅商”共同变化的原因物流节点交易子订单“规格”、“数量”、“价格”、“图片”、“货主”、“优惠价”共同变化的原因商品编码其他归类… 有意识的程序员已经开始把以上各类获取、设置字段属性的代码分别归类到各个不同的函数中或者不同的类可能叫商品表达类、物流信息Handler类等中等等这种方式在一定程度上是提高了复用性提高了可维性但这还远远不够 字段变化难跟踪单据承载了很多的信息各种字段信息是什么时候变化的单据字段的变化也可能是多次变化的这些变化的时间和轨迹对于业务的意义有时候也很重要通常有些特殊的字段产品和业务同学会明确给你提用例需求去做我们来展示一个真实例子“修改地址” 用户修改地址对于电子商务类服务无论是各种快递、淘宝等都是支持在未妥投之前让用户去修改地址的做的好的产品甚至可以支持多次地址的修改那么用户在什么时间修改了地址呢修改前是什么修改后是什么这些信息都必须在某个地方很明显的表达出来。加一个服务类当业务需要的时候我们自然可以专门开发一个服务类插入系统去支持但这种需求又有多少呢未来有没有能不能有一套设计方案可以保护核心流程保留可选项又不失优雅的去支持这类业务呢 2概念突破命令实体 知识拓展本小节将会介绍一个叫命令实体的领域概念在许多DDD框架和介绍文中Command通常被描述为一个简单的贫血DTO加上参数验证逻辑而不承担业务逻辑的角色。如果经常使用DDD框架可能会对这个概念产生混淆。这种用法可以类比应用Service和领域Service。为了避免误解我们将在下文中明确指出Command与CQRS架构中的Command的区别并建议将下文的Command替换为Operation以符合领域逻辑。 沟通获取知识在DDD中想要和领域专家通过沟通获取知识统一语言是很重要的本文的电商领域入门其实比较低所以基本上沟通会很顺畅但这不代表知识挖掘是一件容易的事情下面来自我和产品经历的一段对话 我业务最近上了新的话费充值特惠版产品那个客户的手机号他不让用户输入要我从账号中心获取为什么 产品是的他们这款是优惠充值产品只能给客户自己充值所以省略用户自己填写的步骤提高体验。 我明白了其实本质都是填写发货单的手机号只不过是实现方式不同在我们工程领域是一个标准实现方式不同。 产品我大概明白你的意思这样做没问题当然肯定还会有第三种方式的但他们都是做一件事。 我我记得在电脑端下单和手机端下单发货单的地址填充方式也不同。 产品嗯是的手机端可以提供精准下单地址电脑不行这也是不同的方式而且这些和产品无关了所以你也要支持组合使用哦。 我我知道怎么实现了我做一个发货单手机号填充命令但是实现类不同下次你们变化我就让你们自己配置。 产品可以命令我能听懂上次小冰跟我说什么interface就不知道是啥了。 我哦interface你就不用管了其实我也是用的interface哈哈哈哈。 其实通过以上沟通我明白的是产品需要的是这个补充字段是可以配置的但大多数人拿到需求立马代码就出来了也不考虑一下这样写的原因其实很多时候只有在写代码的时候你才会知道除了业务和产品表面上的需求内部可能蕴含着更深知识可以挖掘 专业知识查询与命令的划分是常见的做法。我们经常将代码分为两类一类用于改变状态这类代码称为命令另一类用于获取状态但不改变这类代码称为查询。单据的字段通常都会改变单据实体的状态因此如果我们将这类逻辑视为命令那么很明显如果我们看到一个类的名称带有Command后缀我们可以很容易地想到这个类必然会改变状态而单据的状态就是字段。 知识拓展柔性设计在Eric Evans的《领域驱动设计》一书中他建议我们将逻辑代码组织成无副作用的函数让函数返回Value Object。然后让简单的副作用命令根据返回的Value Object来更改对象状态。如果可能的话尽可能将这些逻辑代码封装到Value Object中形成一个无副作用、可组合复用的Value Object。由于无副作用我们可以自由地组合和复用这些函数。见《领域驱动设计》——柔性设计 p174 说到组合复用再结合产品要的配置以及我需要的柔性设计那么把以前所有的改变状态的代码都组织为命令对象让命令返回修改后的单据的编辑稿版本(Value Object)最后让聚合根自己把编辑稿(Value Object)更新到自己的字段上这样就基本符合Eric Evans的这种模式。整个过程类似于编辑表单的过程用户点击编辑命令后会获得可编辑的界面表单草稿编辑完成后提交按钮触发后台操作将表单草稿应用于实际生效的表单中。 注意请点击图像以查看清晰的视图 命令模式这个过程和命令模式是差不多的。我们会把命令交给聚合根去执行对比上面命令模式的图我们可以看出其实运维人员就是Client他把封装好的命令间接设置给交易主订单聚合根而Invoker则是聚合根他负责执行具体的命令同时也会记录命令的执行改变自身状态。例如下面的代码所示为聚合根执行命令的过程。 public class SubmitOrderUsercase{public void sumit(Request request) {TradeMainOrder mainOrder getMainOrder();//获取命令的具体实现IPhoneNumberCompleteCommand command getCommand(request,IPhoneNumberCompleteCommand.class);//聚合根执行手机号完善命令mainOrder.execute(command);// ......//获取命令的具体实现IDiscountCalculateCommand command getCommand(request,IDiscountCalculateCommand.class);//聚合根执行折扣计算命令mainOrder.execute(command);// ......}public IPhoneNumberCompleteCommand getCommand(Request request,Class clazz){// 业务配置好的什么场景用什么命令.......}}上面还提到字段内聚性那么我可以把所有相同原因变化的逻辑设计为一个个命令对象来管理我的单据字段。这个对象会封装逻辑所需的入参甚至查询外部服务实际上只是查询没有状态变更查询结果也是入参的一种它只依赖于入参。因此我们可以创建发货单完善命令、支付信息完善命令、购买者信息完善命令、商品信息完善命令等等对象我还可以给他们做一个最大的分类按照不同实体有不同的命令修改接口得到交易主订单变更命令、交易子订单变更命令这些命令只能由聚合根交易主订单执行。最后通过修改依赖关系我们可以得到如下图所示的组件结构 注意请点击图像以查看清晰的视图 如此的灵机一闪引入命令实体后以上所有的字段问题都刚好被这个模型拟合了我列举几个好处 设计良好很明显的倒置依赖保护聚合根的独立性函数式编程可以组合而不担心逻辑错误有人可能会质疑命令内部是不是会直接访问对象呢如下图的命令接口所示如果这样设计该接口明显是有副作用的但如果我们传入的是编辑稿类似视图然后我们编辑视图最后更新回到实体就可以了。 public interface TradeSubOrderChangeCommand {String getSubOrderId();void execute(TradeSubOrderDraft subOrder);}字段分治管理有了命令后加上适当的命令命名字段的管理再也不混乱。每个字段都应该有其对应的设置命令进行管理而不是让各种服务类去进行赋值管理。同时对字段的处理也可以封装到命令中。你可以随时定位一个字段的变更命令只需要思考一下字段的归类。最重要的是这种字段的分类的独立性可以让你操作字段的代码独立分离使其具有更好的开闭性。这一点正好可以解决字段的多样性问题。 命令封装逻辑命令可以封装action调用。赋值只是命令的目的。既然封装了action的调用那么对action的入参和结果的处理也可以封装到命令中。更重要的是只要是符合触发源的目的、职责单一部分业务逻辑也可以封装到命令中。在以往很多贫血系统中这些都是由service负责的似乎没有service不知道该如何安置代码一样。 随时随地跟踪下面是一个简单版本的聚合根执行命令的代码示例。其中record方法根据命令本身的属性提供有选择性地记录执行结果的能力。如果有重要的字段你可以找到该单据对应命令的执行流水并进行可视化管理。这种粒度的管理在业务运维和开发疑难问题排查上都非常有用。 public class TradeMainOrder{public void onCommand(TradeSubOrderChangeCommand command) {if (!tradeSubOrderDict.isEmpty()) {TradeSubOrder subOrder findSubOrder(command.getSubOrderId());// 变更前的快照代码command.execute(subOrder);// 变更后的对比逻辑代码记录字段变化个数、时间record();// ......makeStateConsistent();} else {log.error(子单变更命令执行失败子单列表为空,{}, EagleEye.getTraceId());}}public void onCommand(TradeOrderChangeCommand command) {// 变更前的快照代码command.execute(this);// 变更后的对比逻辑代码记录字段变化个数、时间record();// ......makeStateConsistent();}}组合命令如前所述无副作用的函数可以方便地进行组合复用。举个例子一个提交订单的场景中调用了一个名为Combine的发货单完善命令。这个Combine命令充当容器的角色包含了手机完善、邮箱完善等几个命令。它的执行逻辑就是依次执行这些命令因为它们具有相同的接口所以实现这个组合非常简单。此外它还具备以下特点 只要给每个命令一个id那么命令组合就可以在外界进行配置化由于命令组合可以进行配置化因此哪些命令被执行是在运行时决定的从而体现了灵活性命令组合的实现都是基于函数式的因此组合后的命令不会出现“组合爆炸”的问题整个过程也是透明和安全的 有了组合命令我们可以轻松地根据业务需求将命令定制为组合并上线。能够被管理和配置的独立代码是程序员追求的最高艺术境界。 3、深层模型 1领域知识状态推进本质 发货单也具有状态已接单、待发货、运输中、揽收、妥投、拒收、取消。这些状态的变化驱动是接收外部事件进行推动的但因为要考虑事件丢失、乱序问题当一个事件到来后但前置事件已经丢失、延迟未到那单据应该决策成为什么状态呢自然而言我们很容易联想到状态机开始我们也是这样做的状态图如下 注意请点击图像以查看清晰的视图 问题空间物流实操业务流程的真正设置如下且中间流程不允许跳过例如如果没有在运输中那么揽收就不可能发生这说明实际业务状态转换与状态机的解空间不匹配后者包含了很多不必要的部分。另一方面如果我们设计一个游戏机的投币程序用一个状态机实例来表示游戏机的状态投币状态、空闲状态、游戏状态那么状态机就完全没问题这里的根本原因在于问题空间本身就是解空间的模型驱动的。 注意请点击图像以查看清晰的视图 不纯粹的解空间为什么解空间中有多余的连线例如运输中会跳到妥投这是因为解空间考虑了计算机和架构的细节问题如事件传播中的异常和速度问题。如果事件保证顺序消费那这条连线就不需要如果按照这种思路组织代码和编写代码必然会在领域实体中加入不属于领域的逻辑这是领域驱动设计DDD所禁忌的。另一方面如果使用状态机需求变更添加一种新状态那么新的连线也会让很多人感到困扰。是时候调整模型把该逻辑给去掉了那么怎么去掉呢 代码职责问题假设业务要求在每个状态节点经历时记录节点流水。状态机的实现方式会如何呢 如果正确的事件顺序是 1、运输事件2、揽收事件3、妥投事件 但实际顺序是 2、揽收事件1、运输事件3、妥投事件 当状态顺序混乱时状态机在揽收状态运输事件到达记录运输节点流水应该让messageHandler处理还是揽收节点处理呢后者明显不合理前者也显得勉强。如果除了记录流水还需要发送外部消息呢那么messageHandler的职责就会越来越重。 2深层模型修正状态机模型 不存在状态推进我们讨论的是发货单的状态它代表者物流的操作过程所以其操作进度要反馈到订单的进度这个过程其实更多的是一种状态同步过程而不是状态流转的过程所以我们的解决办法是**换个角度思考订单状态变更这件事是状态同步而不是状态推进**。我们用一个流程实例** 也可以设计为无状态的流程来解决整个问题。 注意请点击图像以查看清晰的视图 我们现在把更新状态的算法换了从状态推进变为状态同步如上图所示首先刻画整个问题空间的状态流程作为解空间模型我们发现这个流程是绝对的无环的一种拓扑排序。它和状态机有几点不同 有序性状态机的节点是无序的或者说只能相对有序而我们的同步模型则是有序的这与问题空间的工序顺序一致问题空间的每一步都是有序的。拓扑结构状态机的节点可能存在环状结构但我们的同步模型是拓扑排序的符合业务节点的特性。每个节点都没有环拓扑结构是该领域的特有属性这一点很重要因为我们采用领域驱动设计。运作机制状态机的运作核心是围绕事件和当前状态寻找下一个流转状态而同步模型则以流程实例为核心。每当有事件到来时我们将该流程的节点标记为已同步。如上图所示1、2、4、5对应的事件均已到达因此它们呈绿色。每次事件处理完成后我们比较最大序号的节点和单据当前状态的序号将序号较大的节点更新为单据状态。计算机无关性模型不再关注事件是否乱序、延迟。只要事件到达我们就将其对应的节点标记为已同步并触发相应节点的业务逻辑如计费消息的发送和流水的记录。 逻辑封装到节点上显然上述流水记录代码无需放入messageHandler发送节点的外部消息发送代码也不需要绑定messageHandler。它们可以封装在运输节点内或采用观察者模式监听运输节点以完成相应行为。这样的代码更具扩展性灵活性较高。 与状态机相比新的状态同步模型在开发效率和代码维护方面都有所提升。状态机具有线性复杂度而状态同步模型则是常数级别的复杂度。这个例子充分证明了领域驱动设计的核心本质**领域的重要性、知识的重要性**。** 4、边界模型 1领域知识边界隐式概念 上面我们讨论完核心模型我们这节主要讨论的是边界的模型软件设计的一个关键在于恰当地区分边缘。的确单一订单处理系统与许多外部系统如账户中心、商品中心、库存中心、决策中心、支付中心和履约中心等具有丰富的交互。这些交互主要通过调用各系统的接口来获取或写入数据其依赖关系如下所示 注意请点击图像以查看清晰的视图 零散的隐式概念很明显在整个接单的系统中这些边界很多概念是应该属于我们的领域上下文中的例如赠品、计划、库存、会员等级等概念但这些概念往往只是存在于字段属性中例如会员等级就只存在账号实体的属性中并没有专门为他们创建实体但需不需要为他们创建实体也是一个问题这种发现实体的契机其实也是需要另一个因素决定的那就是有没有行为和这些属性绑定所以一开始我们先不为这些散落的领域逻辑设计实体但我们应该为以后需要这些实体而做好准备下面的防腐层正是最重要的一步。 2领域模式防腐层 知识拓展防腐层设计一个隔离层以便根据客户自己的领域模型为其提供相关功能。这个层通过与另一个系统的现有接口进行对话而对系统的修改最小。在内部这个层负责在两个模型之间进行必要的双向转换。——摘自《领域驱动设计》 如上图所示的依赖关系我们显然与其他领域进行了绑定。为确保内部逻辑的独立性实现对修改的封闭和对扩展的开放我们必须改变依赖关系。这是明确划分外部边界的关键一步如下图所示 注意请点击图像以查看清晰的视图 设计防腐层会带来一定的编程困扰。你需要在内部设计一个进出参模型或内部接口并添加一层适配器层。适配器层负责实现内部与外部实体的对接。尽管这种方法较为复杂但我们仍需了解使用防腐层的理由 保护核心层概念 例子 例如你在公司中的角色是老板但在家里的角色是父亲。如果你将老板实体放在家庭中为孩子做饭这个家庭就会依赖不必要的逻辑这违反了整洁架构的原则可能导致变更和稳定性问题 例子 在交易系统这种复杂的系统中例如一个在供应商系统中代表它自己编码的merchantCode可能来到交易系统这边会变成supplyMerchantCode同一个值用角色字段区分他们这自然是很重要的 关注点分离 说明外部接口的非逻辑依赖变更不会影响核心逻辑。你只需确保返回字段的含义一致即可 适配逻辑的代码 说明有很多代码你只是用来做外部实体的处理的变成内部可识别的实体例如决策中心传给你的是2021-07-12 ~ 2021-07-13但你内部用的是一个stat的Date变量和一个end的Date变量那就需要适配了这些代码如果编写在核心逻辑中那你在维护核心逻辑的时候也不得不多思考一件事不仅代码臃肿还消耗你的精力。 说明有一个点很重要为什么要做这种设计因为设计就是需要把代码放在它该呆的地方这种转换的代码总要有一个适配器处理 可随时挖掘隐式概念 说明例如用户的会员等级这个会员等级字段属性就是一个隐藏的概念它存在于用户账号中所以你难以发觉。但日后不断的需求变更中你或许会发现它可能是一个封装性很好的实体。下一节我们将具体介绍如何挖掘除会员等级这个实体。 严格遵守防腐层并不容易首先要求编写代码的人具备这方面的意识以免违反规则。其次编写代码的人应对整个系统架构有一定了解。虽然如此如果有人把控这部分代码新手也可以参与系统建设。这一思想来源于《人月神话》中的外科手术医生只有一个的观点。 实际上划分边缘有两种方式防腐层和基础设施、应用类业务划分。当将与领域逻辑无关的逻辑划分边界后六边形架构就出来了。 3隐式概念重构中发现模型 以上提到在划分和外部边界的时候先不考虑散落的逻辑概念抽象为实体有了防腐层之后当有新实体的产生需求即可把这些概念实体化了这篇我们用一个例子来说明如何通过重构从防腐层代码中抽象一个实体出来首先下面是一个简化版本账号中心域的防腐设计我们专门为计划的返回做了一个内部的Entity — 用户账号 Data public class UserAccount {// 其他字段 ...... /*** 会员等级*/private int userLevel;// 其他字段 ...... }现在有另一段获取账号信息根据会员等级获取对应折扣比例的信息这个代码是这样写的 public class XxxxxxxService {public Double getDiscount(UserAccount account) {switch(account.getUserLeval){case 1:return 0.99;case 2:return 0.98;case 3:return 0.97;case 5:return 0.95default:return 1.00;}} }特别的我们在其他service中也发现了一样的代码当你注意到这点的时候就是一个领域实体出现的时候了那么我们可以复用这段逻辑并把逻辑和账号关联起来把该行为封装到账号中如下所示 Data public class UserAccount {/*** 客户等级**/int userLevel;public Double getDiscount() {switch(userLevel){case 1:return 0.99;case 2:return 0.98;case 3:return 0.97;case 5:return 0.95default:return 1.00;}} }但这还不够我们忘记了一个领域概念遗留了那就是会员等级 现在是时候把它显性化为一个领域实体了所以最终的重构结果是 Data public class UserAccount {/*** 客户等级*/private UserLevel userLevel; }public class UserLevel {int userLevel; public Double getDiscount() {switch(userLevel){case 1:return 0.99;case 2:return 0.98;case 3:return 0.97;case 5:return 0.95default:return 1.00;}} }领域上下文在代码重构的过程中可能会遇到这样一个情况UserLevel类在账户中心也有名称一样但数据绑定行为却大相径庭。这是因为我们所关注的字段所处的领域上下文发生了改变从账户中心领域转向了交易领域。在不同的领域中针对相同字段的行为封装是各具特色的这也是领域驱动设计DDD的一个显著特点。 重构是领域驱动设计的引擎在重构过程中借助领域知识来引导设计方向确保领域逻辑的独立性发掘领域实体和聚合根具有至关重要的意义。这个例子虽然简单但在很多情况下我们要突破深层模型发掘更优质的设计都离不开重构。掌握重构技巧是程序员的必备素质。若你认为代码难以重构可以尝试引入单测和小步快跑的方法。 5、领域服务 知识拓展有时候对象不是一个事物在某些情况下的操作你可能找不到合适的Entity或者Value Object去封装强制把他们归于一类不如顺其自然引入一种新元素SERVICE服务。其中这个SERVICE元素在DDD的各个层中也会有体现所以会存在应用层的SERVICE领域层的SERVICE和基础设施层的SERVICE。 领域服务如何设计领域服务是一个值得探讨的问题。本文借鉴了《架构整洁之道》中的用例划分领域服务。在需求分析阶段用例对于问题分析非常有帮助。将一个用例设计为一个服务有助于区分应用层服务和领域服务。 应用层服务用作和输入输出相关的逻辑并且负责调用领域层服务领域层服务用作和领域模型交互负责组织和协调的领域模型工作的逻辑 因此针对本文“生命周期”小节介绍的单据的流程自然就有以下的领域服务 提交订单领域服务执行读取命令配置、执行命令、库存占用、价格计算、定时失效等逻辑代码支付领域服务读取命令配置、执行命令负责支付校验、调用支付服务、订单各种命令执行等逻辑代码取消领域服务读取命令配置、执行命令负责释放库存、取消订单、取消定时任务等逻辑代码… 此外还有应用层服务如提交订单应用服务、支付应用服务和取消应用服务。区分它们的关键在于逻辑是否具有领域概念。例如导出操作并无领域含义但获取运维针对不同产品身份的命令配置、命令组合及执行命令结果等业务逻辑则应放到领域服务层。 从上面可以看出许多领域或应用层服务是基于实体Entity和价值对象Value Object构建的。例如提交订单服务涉及操作单据Entity和命令Value Object。从这个角度看他们的行为类似于将领域中的潜在功能组织起来以执行特定任务的脚本。由于文章重点关注单据模型设计和介绍此处不再赘述。 三、后记 领域驱动设计的核心目标我们在文章开头阐述了领域驱动设计DDD的关键目的它旨在利用多种实用策略和技巧来提炼出一个能够真正反映实际问题本质领域的模型并且保护和组织好这个模型之间的相互作用以便解决领域内的问题。我们已经在文章中运用了聚合根模式、统一语言交流、防腐层模式和重构技术等方式来进行探讨然而在实际应用中可用于解决该问题的方法和知识远远不止于此。有时候我们还需要对现有的模式进行调整和创新来克服建模过程中遇到的问题。这就需要我们的团队技术人员全面掌握DDD的相关知识同时具备精湛的建模技术和丰富的实践经验还包括灵活的思维能力和敏锐的洞察力。这些品质对于我们技术人员的日常工作的开展和自身的专业发展都有着重要的意义。 领域模型协作与组织由于文章的篇幅和主题的限制我们并没有在文章中详细地探讨关于领域模型之间合作与组织的问题然而这实际上是非常重要的一部分内容。通常情况下我们需要考虑的不仅仅是领域模型的纯度还有其性能和交易属性。例如领域服务如何管理领域实体怎样将领域服务和应用程序服务区分开以及如何将构建程序和执行程序独立开来等等。如果没有具体的案例作为参考那么上述的问题可能会让人感到有些抽象但是我们可以通过参考一些优秀的设计规范比如提高模块间的凝聚力和降低它们之间的耦合度SOLID原则以及软件架构的基本原则等以此来指导我们的实践工作。读者可以在自己的实践中慢慢领悟这些原则的重要性。 演进与重构本文以业务单据系统为例系统初始阶段可能非常简单直接采用三层架构即可。但随着需求的增长和变化简单的系统将面临复杂性问题。我们必须掌握每次需求变化实践Martin Fowler的两顶帽子原则——重构编写新功能。不断重复这个过程系统得以逐步演进。若重构编写新功能始终围绕领域知识统一模型进行设计那么这个过程就是所谓的领域驱动设计。这也是为什么DDD会如此重视那些随着时间的推移而逐渐演化的强大领域模型。 总结领域驱动设计是一个不断发展和重构的过程。在实际情况当中我们可以采用多种方式和技术比如说聚合根模式、统一的语言交流、防腐层模式等等来解决领域内的问题。团队的技术人员应该具备丰富的DDD的知识同时也需要有出色的建模技巧和丰富的实践经验。在设计的过程中我们还需要考虑到性能、交易属性等方面的因素以确保领域模型的纯度。通过不断地进行重构和添加新的功能的工作我们就能很好地应对系统的发展和复杂性的问题。最终领域驱动设计的目标是将投入的成本与业务需求的行为价值保持平衡。 参考书籍 1.《重构》 2.《架构整洁之道》 3.《领域驱动设计》 说在最后 DDD架构如何落地是非常常见的面试题。 以上的内容如果大家能对答如流如数家珍基本上 面试官会被你 震惊到、吸引到。 在面试之前建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》并且在刷题过程中如果有啥问题大家可以来 找 40岁老架构师尼恩交流。 最终让面试官爱到 “不能自已、口水直流”。offer 也就来了。 当然关于DDD尼恩即将给大家发布一波视频 《第34章DDD的学习圣经》 帮助大家彻底穿透DDD。 尼恩技术圣经系列PDF 《NIO圣经一次穿透NIO、Selector、Epoll底层原理》《Docker圣经大白话说Docker底层原理6W字实现Docker自由》《K8S学习圣经大白话说K8S底层原理14W字实现K8S自由》《SpringCloud Alibaba 学习圣经10万字实现SpringCloud 自由》《大数据HBase学习圣经一本书实现HBase学习自由》《大数据Flink学习圣经一本书实现大数据Flink自由》《响应式圣经10W字实现Spring响应式编程自由》《Go学习圣经Go语言实现高并发CRUD业务开发》 ……完整版尼恩技术圣经PDF集群请找尼恩领取 《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF请到下面公号【技术自由圈】取↓↓↓
http://www.zqtcl.cn/news/425969/

相关文章:

  • 温州哪里做网站设计seo报名在线咨询
  • 四川住房和城乡建设厅进不去网站网站专题方案
  • 企业网站维护服务做网站设计都需要什么软件
  • jsp电商网站开发教程盐城网站建设制作
  • 企业解决方案网站做企业官网多少钱
  • 宁波网站建设哪家比较好怎麽做网站
  • 诸塈市建设局网站做移动网站开发
  • 南京建站公司网站网站视频源码地址
  • 德阳建设局网站做公众号首图的网站
  • 南阳网站优化渠道山西太原最新消息
  • 发布做网站需求qq群centos wordpress 建站教程
  • 东阳网站建设yw126南京网站改版
  • discuz视频网站模板徐州专业网站建设公司哪家好
  • 网站开发投资成本Wordpress显示成缩略图
  • 网站域名和网站网址吗中东跨境电商平台有哪些
  • 常宁市城乡和住房建设网站怎样加强文化建设
  • 视频网站如何做营销策划模板网站 seo
  • 中企动力做网站好吗网页建设软件
  • 爱站网seo浙江省嘉兴市建设局网站
  • 南宁做网站比较好的公司有哪些贵阳网站上门备案业务
  • 网络叶子 网站推广做一手房做那个网站好
  • 太仓网站建设平台成都家装设计公司排名
  • 现在建一个网站一年费用只要几百元如何建一个免费试用网站
  • 网站没有被收录销售型网站的建设流程及特点
  • 成都58手机微信网站建设名录近一周财经新闻热点
  • wordpress情侣网站源码微信开放平台官网登录
  • 网站改版提示无需改版有没有兼职做设计的网站
  • 网站sem怎么做网络建设设计方案
  • wap网站在线生成做饰品网站
  • 网站主机在哪里注册呢江西的赣州网站建设