写代码原来如此简单:两种常用代码范式

一、领域模型驱动的代码范式

领域模型驱动的代码范式,是围绕着领域知识设计的,需要先理解业务模型,再将业务模型映射到软件的对象模型中来;本章节重点在我们有了业务模型之后的代码模式,具体业务模型如何构建在《架构之道:人人都是架构师》中有详细讨论;

上图中间就是该模式最重要的领域,领域层代码作为系统的最核心资产模块,可以被打包迁移到任何应用上,而不关心具体的三方服务提供方和具体的持久化方案,即外部服务的变化对领域层代码是没有任何侵入的;

从上图来看,领域对象(ENTITY、AGGREGATION、VALUEOBJECT、MODEL)用于描述业务模型,是业务关系最重要的体现;为了屏蔽持久化方案的细节,我们使用仓库(REPOSITORY)来查询和持久化领域对象;有的时候我们期望直接获取到一个非空的领域对象,而不关心这个对象从哪里来,如何构造,那我们就需要工厂(FACTORY)来帮我们生产这个对象;当领域内依赖外部服务能力时,需要门面(FACADE)帮助我们屏蔽具体的服务提供方;

有了以上这些模型对象和基础能力模块,我们需要领域服务(DomainServie)层作为“上帝之手”帮我们编排具体的业务逻辑;本章对领域服务有更细的三层划分,第一层是实体操作服务(BaseUpdateDomainService),用于收敛操作实体/聚合的真实变更行为;第二层是业务流程服务(BaseBizDomainService),用于收敛基础的有业务语义的行为;第三层是用例领域服务(UserCaseDomainService),用于映射具体的业务需求用例场景。

1.1 领域模型对象

  • 实体

由标识定义,而不依赖它的所有属性和职责,并在整个生命周期中有连续性。这句话在初看的时候非常晦涩,简单来说,就是一个标识没变的对象,在其他自身属性发生变化后,它依然是它,那么它就是实体;以上图为例,一个商品的商品id没变,即使它的标题改了,图片改了,优惠信息变了,它发生了翻天覆地的变化,但它依然是它,它有唯一的标识来表明它还是它,只是一些属性发生了变化;通过这种方式来识别实体的目的,是因为领域中的关键对象,通常并不由它们的属性定义,而是由可见的/不可见的标识来定义,且有完整的生命周期,在这个周期内它如何变化,它都依然是它;通过这种方式识别出实体这种领域关键对象,也是领域驱动设计和数据驱动设计最大的差别,数据驱动设计是先识别出我们需要哪些数据表,然后将这些数据表映射为对象模型;而领域驱动设计是先通过业务模型识别出实体,再将实体映射为所需要的数据表。

  • 值对象

用于描述领域的某个方面而本身没有唯一标识的对象。被实例化后用来表示一些设计元素,对于这些设计元素,我们只关心它们是什么,而不关心它们是谁。如上图举个例子,一个商品实体的发货地址Address对象有区域信息、门牌信息、时区信息这几个属性,其中的门牌号从111修改成222后,它就已经不再是修改前的那个它了,因为门牌号222并不等于门牌号111的地址。即它是没有生命周期的,它的equals方法由它的属性值决定(实体的equals方法由唯一标识决定);

  • 聚合

聚合是一组实体和值对象的组合;内部包含一个聚合根,和由聚合根关联起来的实体和值对象;以上图为例,有商品、优惠、库存这三个实体和地址这一个值对象,对于一个商品而言,完整的商品信息需要包含优惠、库存、地址这些信息,那么在商品模型中,商品就是聚合根,其内部通过优惠id关联它的优惠信息,通过库存id关联商品的库存信息;聚合将这组关联关系建立,对外提供统一的操作,比如需要删除某个商品,那么这个聚合的内部可以在一个事务(或分布式事务)中,对库存进行清空,对优惠进行清理,最终对商品进行删除。

1.2 查询/构造能力

  • 仓库

仓库是可持久化的领域对象和真实物理存储操作之间的媒介,随意的数据库查询会破坏领域对象的封装,所以需要抽象出仓库这种类型,它定义领域对象的获取和持久化方法,具体实现不由领域层感知;至于具体用了什么存储,如何写入和查询,是否使用缓存,这些逻辑统一封装在仓库的实现层,对于后续迁移存储、增删缓存,都可以做到不侵蚀业务领域。

比如整个领域模块需要打包给海外业务使用,在海外我们需要用当地的存储,那么这个迁移对于领域层是没有侵入的,只需要在基础设施层修改仓库的实现即可;

再比如我们的数据库存在性能瓶颈,需要在数据库上增加一层缓存,这个操作对领域也是没有侵入的,只需要在仓库的实现处,增加缓存的读写即可,对业务逻辑无感。

  • 三方能力门面

门面用于封装第三方的能力,设计初衷本质和仓库是一样的,目的都是屏蔽具体的三方能力实现,让稳定的领域层不去依赖无法把控变化方向的第三方;上图中的三个case是比较经典的三个例子:

  • 我们的模型中依赖外部查询获取的商品模型,这个模型中有商品标题、商品图片、店铺名称这几个信息,那么我们需要在Domain层定义一个商品类ItemInfo,包含这几个属性,然后在Domain层定义一个获取ItemInfo对象的服务接口,比如叫ItemFacade,方法是getItemInfo;接下来我们需要在Infrastructure层实现Domain层定义的这个接口,比如具体的实现是依赖Ic的接口,将ItemDO转换为Domain层的ItemInfo;可以发现,这样的设计让Domain对商品信息的获取源是无感的,当我们的能力需要部署到海外,或者IC某一天进行了重大改革,需要对模型进行大改,那么我们只需要重新实现Infrastructrue中ItemFacadeImpl即可。这个思想其实就是依赖倒置的思想,稳定不应该依赖变化,变化应该依赖稳定;因为第三方的变化方向是无法把控的,它的变化不应该侵入到我们的领域知识内部。
  • 我们的领域还依赖一些消息发送、限流等基础功能,也需要在Domain层定义相应能力的Facade,在Infrastructure层实现,目的同上,将metaq替换成notify/swift时,领域层是无感的。
  • 工厂

当创建一个实体对象或聚合的操作很复杂,甚至有很多领域内部的结构需要暴露的时候,就可以用工厂进行封装。一种相对简单粗暴的判断方法是看这个类的构造方法实现是否复杂,并且看着这些逻辑不应该由这个类实现,那么不妨用工厂来构造这个对象吧!

1.3 领域服务

有一些对实体/聚合/值对象进行编排操作的概念并不适合被建模为对象,那么它应该被抽象为领域服务,化作一只上帝之手,做领域对象间流程操作的编排。服务很重要的特征,它的操作应该是无状态的。本文基于开发实践,对领域服务做了三层更细节的划分:

  • 实体操作服务

即图中的BaseUpdateDomainService,是最基础的一类领域服务,用于收口某个实体的真实物理操作,它的流程中一般包含一个核心的update/insert操作,作用在写数据库上,依情况可以增加:

BeforeUpdateHandlers和AfterUpdateHandlers,用于更新前后的一些额外业务操作;

如1.1中的图,我们在内存中操作完某个库存实体对象,需要更新db的时候,可以调用它对应的服务,在这个服务中,我们除了将变更的值更新db,还需要对外发送消息,更新前也需要执行一些校验的回调,那么校验回调可以放在BeforeUpdateHandler中,对外的消息可以放在AfterUpdateHandler中;抽象出这样的一个服务,好处是可以收敛最基础的变更操作,不至于不同的入口对某个对象的更新,还会出现不一样的操作(比如需要发送更新消息,不同的入口操作更新,有的发送消息,有的不发送)。

一般每个实体都需要有一个对应的操作服务(或者模型极其简单可以省略这一层),操作服务可以依赖其他的操作服务,比如1.1中商品模型的更新,是需要依赖库存更新服务和优惠更新服务的。

  • 业务流程服务

这一层的领域服务对应图中的BaseBizDomainService,用于收敛一些通用的业务流程,可以直接对接业务接口或者用于上层的用例编排;比如我们在商家请求、接到第三方消息、具体的某几个用例中,需要先查询ItemFacade,然后进行一些业务逻辑判断,然后根据情况对1.1商品模型中的优惠进行清理,那么就可以将这段逻辑收敛到“XX优惠清理领域服务”中,在多个上层场景需要进行该操作时,直接调用这个领域服务即可。

  • 用例领域服务

这一层对应图中的UserCaseDomainService作为领域服务的最上层,也是最具体的一层,用于实现 定制的/不可复用的 用例场景业务逻辑,只直接对接对外的api。

1.4 包结构实践

该包结构即描述了上述的所有模块,其中infrastructure模块依赖domain模块;domain模块的pom文件理论上不应该有任何三方依赖(除了一些工具类)。

二、过程驱动的代码范式

领域驱动的代码,重点是抽象领域模型,沉淀领域对象实体,它用模型间的关系以及模型直接的操作来沉淀知识;过程驱动的代码,重点是抽象能力,沉淀函数,并用编排引擎串联执行过程,实现对知识的描述。

在面向对象大张旗鼓的今天,大多数人对面向过程编程嗤之以鼻,但有些场景使用过程驱动的编程思路,反而能更好地描述业务规则以及业务流程,比如前台表达的渲染链路,或是章节一中 比较重的领域服务,使用过程驱动能更好地描述数据处理的过程以及产品用例流程。

上图能力库中的能力点是我们过程驱动最核心的部分,是我们对能力的抽象,一般一个能力只沉淀一个具体的原子方法,并决策流程是否能执行下去;往上是阶段划分,不同的能力在具体的业务流程中,是处于不同阶段的,这里的阶段划分是指从流程阶段的维度对能力点进行分类并放在不同的包中,让我们的流程更加清晰;再往上是不同场景下我们对能力点的执行链路编排,并对外做统一输出。

2.1 能力点

能力点通常由一个接口定义,入参是执行上下文,出参是流程是否需要继续。

基于这个接口,我们可以实现非常多原子能力节点。java中,这些能力节点作为bean由spring容器统一管理,运行时取出即用。

在一个业务场景下,我们的能力点往往会非常多,那么我们就需要对他们进行基于业务场景的阶段划分,并分门管理;比如我们在前台投放场的实践中,按照召回、补全、过滤、排序、渲染,划分了五个阶段,每一个能力点被归类到其中一个阶段中进行管理。

2.2 能力编排

有了能力点,我们需要基于编排引擎将这些能力串联起来,用以描述业务规则或是业务流程;这些能力执行的过程有些仅仅是可串行执行的,有些是也可以并行执行的,下面给出一套通用的流程协议以及实现过程:

其中流程协议为:

其中Ablitiy3 4 6表示需要并发执行,Ability1、3/4/6、5表示需要串行执行。

2.3 切面

有了能力点和流程编排引擎,基于过程编码的代码骨架就已经有了;但是往往我们还需要一些无法沉淀为能力的逻辑,需要在每个节点(或指定节点)执行前/后进行,这就需要切面的能力;

如图,比如流程埋点、预校验就非常适合放到切面这一层实现。下图是我们在实践中,基于切面实现的能力点追踪,可以清晰地看到每个能力点执行的过程以及数据变更情况。

2.4 包结构实践

该包结构即描述了上述的所有模块,chain目录下为多个业务场景流程,围绕着外层的AbilityNode和AbilityChain进行实现,编排出符合业务场景逻辑的流程。

写在后面

文章中的代码范式可以应用在大部分场景,在项目初起的时候直接套用,可以省下大部分关于包模块划分的思考精力,并且在后续迭代中,团队统一规范,持续按照这个框架演进,可以让代码更加井井有条,减少一些诡异的类职责划分问题。

如何从零开始学会编程?初学者的实用指南

十域看点:如何从零开始学会编程?初学者的实用指南

在数字化时代,编程已经成为一种重要的技能。不论是为了提高职业竞争力,还是为了进入科技行业,编程技能都显得尤为重要。但对于很多人来说,编程可能看起来像是一道难以跨越的门槛。如何从零开始学会编程?下面,我们将为你提供一个初学者的实用指南,帮助你从基础入手,逐步掌握编程技能。

学习编程的第一步是明确你学习的目标。你希望通过编程做什么?是开发网站、创建应用程序,还是进行数据分析?明确目标可以帮助你选择适合的编程语言和学习路径。常见的编程语言包括Python、JavaScript、Java、C++等,不同的目标可能会选择不同的语言。

对于初学者来说,Python是最推荐的入门编程语言。Python语法简洁、易于理解,广泛应用于数据分析、人工智能、Web开发等领域。除此之外,JavaScript是开发网页时常用的语言,若你对前端开发感兴趣,可以学习它。

推荐编程语言:

  • Python:适合数据分析、机器学习、自动化任务等。
  • JavaScript:适合网页开发和前端编程。
  • HTML/CSS:虽然不是编程语言,但学习它们能帮助你理解网页的结构和样式。
  • Java:适用于安卓开发和企业级应用。

现在有大量的在线资源可以帮助初学者入门编程。许多平台提供免费的编程课程和教程,适合不同水平的学习者。

推荐平台:

  • Codecademy:提供互动式的编程教程,适合初学者,课程内容包括Python、JavaScript等。
  • Coursera:提供包括斯坦福大学等知名机构的编程课程,适合深入学习。
  • LeetCode:一个代码挑战平台,适合提高编程能力并准备技术面试。
  • freeCodeCamp:提供免费的全栈开发课程,通过项目帮助学习者掌握编程技能。

学习编程最重要的一步是通过实践不断提升。仅仅看教程和读书是远远不够的。你需要亲自写代码,解决实际问题。可以从小项目开始,比如制作一个简单的网页、开发一个计算器,逐步增加项目的复杂度。

建议:

  • 小项目:从一些简单的应用开始,比如制作个人博客、待办事项列表等。
  • 开源项目:参与开源项目,贡献代码,这不仅能提高编程技能,还能与其他开发者共同进步。

调试是编程中非常重要的一环,学习如何识别和修复错误(bug)是编程技能的一部分。在编写代码时,经常会遇到问题,这时调试工具可以帮助你找到并解决问题。

  • 调试工具:大多数编程环境都提供调试工具,比如Python的pdb或JavaScript的浏览器开发者工具。
  • 常见错误:了解一些常见的编程错误和调试技巧,能够帮助你快速定位问题。

随着学习的深入,你将遇到更具挑战性的编程问题。不要害怕遇到困难,迎接挑战是提升编程技能的关键。尝试解决一些复杂的算法问题、开发更大规模的项目,或者学习新的编程语言和技术栈。

学习编程时,与其他编程爱好者交流是非常有益的。通过参与编程社区,你不仅能获取学习资源,还能得到他人的建议和帮助。

推荐社区:

  • Stack Overflow:一个全球最大的编程问答社区,可以在这里寻求帮助或回答他人的问题。
  • GitHub:代码托管平台,可以找到大量开源项目,参与其中提升编程能力。
  • Reddit的编程板块:在这个平台上,你可以与全球的编程爱好者交流,分享经验。

编程是一项需要长期积累的技能。不要期望短时间内能掌握所有内容,保持耐心,逐步深入。编程不仅仅是学会语法,更重要的是理解其背后的逻辑与思想。

学习编程从零开始并不容易,但只要你坚持不懈,保持实践,掌握适合的学习方法,终将能够在这条道路上取得进步。希望你能根据以上建议,规划出适合自己的学习路线,并为自己定下明确的目标,逐步成为一名熟练的程序员。

十域看点,带你一步步走向编程世界的大门,助你成功跨越技术的门槛!

本文作者及来源:Renderbus瑞云渲染农场https://www.renderbus.com

点赞 0
收藏 0

文章为作者独立观点不代本网立场,未经允许不得转载。