首页 > 其他分享 >蚂蚁面试:DDD外部接口调用,应该放在哪一层?

蚂蚁面试:DDD外部接口调用,应该放在哪一层?

时间:2024-05-10 10:57:21浏览次数:34  
标签:聚合 蚂蚁 对象 实体 业务 领域 面试 DDD

文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :

免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,完成职业升级, 薪酬猛涨!加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领

免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取


蚂蚁面试:DDD外部接口调用,应该放在哪一层?

尼恩特别说明: 尼恩的文章,都会在 《技术自由圈》 公号 发布, 并且维护最新版本。 如果发现图片 不可见, 请去 《技术自由圈》 公号 查找

尼恩说在前面:

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如字节、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:

DDD 的外部接口调用,应该放在哪一层?

DDD架构,如何落地?

谈谈你的DDD落地经验?

谈谈你对DDD的理解?

如何保证RPC代码不会腐烂,升级能力强?

微服务如何拆分?

微服务爆炸,如何解决?

你们的项目,DDD是怎么落地实操的?

所以,这里尼恩给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”
也一并把这个题目以及参考答案,收入咱们的 《尼恩Java面试宝典PDF》V155版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

除了本文,尼恩输出了一个 《DDD学习圣经》系列,帮助大家彻底掌握DDD。

同时,尼恩持续输出最新的《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请关注本公众号【技术自由圈】获取,后台回复:领电子书

问题1:DDD的外部接口调用,应该放在哪一层?(蚂蚁面试)

无论是RPC (如Feign),还是HTTPClient调用Rest API,具体的外部接口调用实现放在infrastructure 基础设施层。

在领域驱动设计(DDD)中,基础设施层负责处理与外部资源的交互,包括数据库、文件系统、消息队列、外部服务等。

来看看 cola的 DDD 架构, 具体如下图

COLA(Clean Object-oriented Architecture)框架是一种基于领域驱动设计(DDD)和面向对象设计原则的架构风格,旨在帮助开发人员构建清晰、可维护和可扩展的应用程序。

1)适配层(AdapterLayer):负责对前端展示(web,wireless,wap)的路由和适配,对于传统B/S系统而言,adapter就相当于MVC中的controller;

2)应用层(ApplicationLayer):主要负责获取输入,组装上下文,参数校验,调用领域层做业务处理,如果需要的话,发送消息通知等。层次是开放的,应用层也可以绕过领域层,直接访问基础实施层;

3)领域层(Domain Layer):主要是封装了核心业务逻辑,并通过领域服务(Domain Service)和领域对象(DomainEntity)的方法对App层提供业务实体和业务逻辑计算。领域是应用的核心,不依赖任何其他层次;

4)基础实施层(InfrastructureLayer):主要负责技术细节问题的处理,比如数据库的CRUD、搜索引擎、文件系统、分布式服务的RPC等。此外,领域防腐的重任也落在这里,外部依赖需要通过gateway的转义处理或者代理处理,才能被上面的App层和Domain层使用。

gateway 代理的架构,也非常类似与适配器的架构。 gateway 代理架构分为两个部分:

  • gateway 接口处于 领域层,

  • gateway 的实现处于 基础实施层(InfrastructureLayer)。

具体如下:

在COLA框架中,Gateway通常指的领域层的网关,是一种隔离在应用程序领域层内部和系统外部之间进行通信和交互的接口或者组件。COLA框架中的Gateway通常被用于以下几个方面:

  1. 数据交互:Gateway可以用于在领域层和基础设施层之间进行数据交互。例如,领域层的服务可以通过Gateway将数据传递给基础设施层进行持久化,或者基础设施层可以通过Gateway将数据加载到领域层中进行处理。
  2. 外部接口调用:Gateway也可以用于与外部系统进行通信。例如,Gateway可以封装与外部服务的交互逻辑,使得领域层不直接依赖于外部服务的细节。
  3. 跨边界通信:在复杂的应用程序中,可能存在多个边界或子系统。Gateway可以用于在这些边界之间进行通信,帮助将系统拆分为更小的模块并保持模块之间的松耦合性。

总之,COLA框架中的Gateway扮演着连接不同层和组件之间的桥梁角色,有助于实现应用程序的模块化、可维护和可扩展性。

问题2:什么是聚合?

“聚合”即“高内聚,低耦合”中的“内聚”之意;

聚合是业务和逻辑紧密关联的实体和值对象组合而成,聚合是数据修改和持久化的基本单元,一个聚合对应一个数据的持久化;

聚合在DDD分层架构中属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑,聚合内的实体以充血模型实现个体业务能力,以及业务逻辑的高内聚; 跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现;

首先我们来看下聚合模式的定义:

将实体和值对象划分为聚合并围绕着聚合定义边界。

选择一个实体作为每个聚合的根,并仅允许外部对象持有对聚合根的引用。作为一个整体来定义聚合的属性和不变量,并把其执行责任赋予聚合根或指定的框架机制。

一个聚合包含一个聚合根(aggregation root)和一些相关的其他领域对象。

问题3:什么是聚合根?

在《DDD学习圣经》中,我们讲到了“什么是聚合根”,这里再重复一下。

聚合根中的“聚合”即“高内聚,低耦合”中的“内聚”之意;

而“根”则是“根部”的意思,也即聚合根是一种统领式的存在。

事实上,并不存在一个教科书式的对聚合根的理论定义,你可以将聚合根理解为一个系统中最重要最显著的那些名词,这些名词是其所在的软件系统之所以存在的原因。

为了给你一个直观的理解,以下是几个聚合根的例子:

  • 在一个电商系统中,一个订单(Order)对象表示一个聚合根
  • 在一个CRM系统中,一个客户(Customer)对象表示一个聚合根
  • 在一个银行系统中,一次交易(Transaction)对象表示一个聚合根

你可能会问,软件中的概念已经很多了,为什么还要搞出个聚合根的概念?

我们认为这里至少有2点原因:

  1. 聚合根遵循了软件中“高内聚,低耦合”的基本原则
  2. 聚合根体现了一种模块化的原则,模块化思想是被各个行业所证明的可以降低系统复杂度的一种思想。所谓的DDD是“软件核心复杂性应对之道”,也即这个意思,它将软件系统在人脑中所呈现地更加有序和简单,让人可以更好地理解和管控软件系统。

在实际项目中识别聚合根时,我们需要对业务有深入的了解,因为只有这样你才知道到底哪些业务逻辑是内聚在一起的。

这也是我们一直建议程序员和架构师们不要一味地埋头于技术而要多关注业务的原因。

事实上,如果让一个从来没有接触过DDD的人来建模,十有八九也能设计出上面的订单、客户和交易对象出来。

没错,DDD绝非什么颠覆式的发明,依然只是在前人基础上的一种进步而已,这种进步更多的体现在一些设计原则上,对此我们将在下文进行详细阐述。

问题4:聚合和MYSQL 表的对应关系是什么 ?

首先,简单说说聚合根

  • 聚合根代表的是一个领域边界
  • 聚合根的内容要保证数据一致性(这里的一致性指的不是数据持久化的事务一致性,而是业务数据的一致性,包含业务上的业务校验) 比如订单和订单详情,一个没有订单详情的订单是不完整的
  • 聚合根里面有多少个实体,由领域建模决定
  • 永远不要删除聚合根
  • 聚合根之间有引用,如果删除了聚合根,会导致关联聚合的数据不一致
    这边很容易和实体的生命周期从属于聚合根搞混了。这边的依赖是关联依赖,实体依赖聚合根是has a
  • 聚合根引用聚合根值id/或者id值对象

然后,简单说说实体

  • 实体一般从属于某个聚合根,要不然就可以定义成聚合根了

  • 实体有自己的生命周期,他的生命周期从属于聚合根。也就是聚合根没有,实体也就没了
    比如我可以对订单详情的数据进行编辑,删除。

  • 聚合根与实体的关系通常是1:N
    因为如果是1:1,通常不需要定义实体了。直接放在聚合根里面,不需要唯一id了。

  • 可以单独更新聚合根中实体数据

最后,说说Domain 聚合和MYSQL 数据表的对应关系是什么

Domain 聚合属于领域建模

MYSQL 数据表设计属于 DB建模

注意,从理论上来说, DB建模(数据表建模)和领域建模没有关系,二者在处于不同的 设计阶段:

  • 领域建模处于 概要设计阶段, 理清楚业务的内外边界,业务的内外流程,成果物为 HLD 概要设计文档。
  • DB建模 处于 详细设计阶段, 理清楚业务的“关系数据模型”进而推导出“物理数据模型”,成果物为 LLD 详细设计文档。

具体如下图

DDD建模的四个标准动作,具体请参见 尼恩《DDD学习圣经》以及配套视频。

数据建模的经典方法就是 E-R Model建模, 而当我们做MYSQL E-R建模的时候,我们需要考虑:

  • 需要建什么主表 - 可以类比为对象 (当然也有些表只是为了关系映射)
  • 表里面需要什么列 - 可以类比为这对象所需的属性
  • 需要什么关联 - 表现数据对象与数据对象之间的联系

当然,E-R Model是数据模型的一种表现形式(数据建模不只是E-R Model一种表现形式) ,E-R Model以数据为中心,关注的是对象的实体和关系,建模时并不考虑Entity的行为。

在E-R概念模型的基础上可以建立“关系数据模型”进而推导出“物理数据模型”,这是一条以E-R Model为起始的数据建模的路线

ER模型分为实体、属性、关系三个核心部分。实体是长方形体现,而属性则是椭圆形,关系为菱形。
ER模型的实体(entity)即数据模型中的数据对象,例如人、学生、音乐都可以作为一个数据对象,用长方体来表示,每个实体都有自己的实体成员(entity member)或者说实体对象(entity instance),例如学生实体里包括张三、李四等,实体成员(entity member)/实体实例(entity instance) 不需要出现在ER图中。

ER模型的属性(attribute)即数据对象所具有的属性,例如学生具有姓名、学号、年级等属性,用椭圆形表示,属性分为唯一属性( unique attribute)和非唯一属性,唯一属性指的是唯一可用来标识该实体实例或者成员的属性,用下划线表示,一般来讲实体都至少有一个唯一属性。

ER模型的关系(relationship)用来表现数据对象与数据对象之间的联系,例如学生的实体和成绩表的实体之间有一定的联系,每个学生都有自己的成绩表,这就是一种关系,关系用菱形来表示。
ER模型中关联关系有三种:

1对1(1:1) :1对1关系是指对于实体集A与实体集B,A中的每一个实体至多与B中一个实体有关系;反之,在实体集B中的每个实体至多与实体集A中一个实体有关系。
1对多(1:N) :1对多关系是指实体集A与实体集B中至少有N(N>0)个实体有关系;并且实体集B中每一个实体至多与实体集A中一个实体有关系。
多对多(M:N) :多对多关系是指实体集A中的每一个实体与实体集B中至少有M(M>0)个实体有关系,并且实体集B中的每一个实体与实体集A中的至少N(N>0)个实体有关系。

在数据建模中通常将E-R Model就称之为概念数据模型,接下来的层面是关系数据模型和物理数据模型;

而Domain 建模是属于对象建模、业务建模的范畴,Domain 聚合和MYSQL 数据表 二的区别是职责不同, ER建模关心数据如何存储,Domain 对象建模需要为对象建立职责(对象的行为)。

当然Domain 建模 和E-R Model的分析具有很大的相似性,

在一些极为简单的业务系统、业务场景中,二者可以做简单的映射:

  • Domain 模型里边 聚合根 可以映射到 E-R Model的 主表

  • Domain 模型里边 普通实体、值对象 可以映射到 E-R Model的 关联表

问题5:什么是领域驱动设计(DDD)?

什么是DDD

领域驱动设计最初由Eric Evans提出,

2004年著名建模专家eric evans(埃里克埃文斯)发表的他最具影响力的书籍:

《domain-driven design –tackling complexity in the heart of software》(中文译名:领域驱动设计—软件核心复杂性应对之道)一书。标志着 DDD 这种 设计和架构方法的诞生。

我们在日常开发中,经常针对一些功能点争论“这个功能不应该我改,应该是你那边改”,最终被妥协改了之后都改不明白为什么这个功能要在自己这边改。

区别于传统的数据驱动架构(Data Driven Design)设计,领域驱动设计(DDD)也许在这个时候能帮助你做到清晰的划分。

但是多年以来一直停留在理念阶段,

然后,真正能实现并且落地的项目和公司少之又少,

近来,包括阿里在内很多大厂,都在大力推行DDD的设计方法,

它主要可以帮助我们解决传统单体式、集中式、大泥球架构难以快速响应业务需求落地的问题,并且针对中台和微服务盛行的场景做出指导。

在这里插入图片描述

DDD为我们提供的是架构设计的方法论,既面向技术也面向业务,从业务的角度,自顶向下来把握设计方案。

在这里插入图片描述

DDD的巨大价值

在这里插入图片描述

统一思想:统一项目各方业务、产品、开发对问题的认知,明确组织当中产品、业务、架构、开发的角色和如何配合。通过统一的语言、明确的定义,统一方方面面的理解误差和理解分歧。

DDD工具链能帮助进一步强化能力逻辑的表达,借助DDD可视化流程构建专业知识库,能快速提升产品和技术、技术和技术之间的沟通效率,帮助开发同学快速、直观的了解业务,进而提高技术同学对业务有快速/全局了解,快速+全局的业务了解,反向帮助开发通讯能够方便把握细节,降低反复、返工、甚至推导重来的概率

动态建模:需求是不断变化的,传统HLD、LLD建模都是核心思想是静态建模,缺乏有效的动态建模方法论和工具。DDD通过从领域事件、领域命令出发对领域对象进行建模,可以真实的反映这些变化。更好的通过边界划分将复杂业务领域简单化,将隐藏的业务显性化, 隐式流程显性化, 隐式字段显性化,帮助我们设计出清晰的领域边界,准确的业务流程,可以很容易地实现业务和技术统一的架构演进;

拉通“断层”:传统的需求常常是一句话需求,模型设计常常是工程师负责, 工程师的设计路径是 库表驱动+界面驱动,结合常规MVC三层架构进行自底向上的设计,完美的实现了业务人员和编码人员断层,需求和开发隔离的目标。DDD拉通了业务和编码,对常规MVC开发模式做一个反转,以业务为主导,自顶向下的进行领域模型设计,拉通业务和编码之间的巨大“断层”,使得代码更能反馈业务,反哺业务,提升代码逻辑的准确度和生命值。

业务人员和编码人员隔离的原因:轻设计、重编码。 一句话需求,敏捷迭代、极速迭代、轻量级的流程。很容易把代码弄得杂乱无章,混乱不堪。

彻底“反腐”:首先是领域模型与数据模型分离,用领域模型来界定哪些需求在什么地方实现,保持结构清晰,隔离数据模型、存储模型的变化和腐败。除了数据模型,咱们应用 中有许多容易"腐败"的操作,比如直接的外部依赖,例如 MyBatis 的 Mapper 类、HttpClient 注入、RocketMQ 的监听、缓存的直接操作等,DDD通过防腐层的架构设计,实现 业务代码的彻底反腐败。

简单来说:

  • “反腐败”设计,从一半业务+一半技术 ,提升到 业务代码和 基础设施解耦。
  • “反腐败”设计,使得领域代码更有业务纯度。

同时,DDD帮助沉淀各领域的业务,标准化流程链路,各领域间无耦合,沉淀的领域能力能够很好的复用,粗粒度的应用能力能基于细粒度领域能力去构建,构建好的能力可以在其他场景直接复用,提高开发效率,最终提升 领域代码的生命力、复用力

提升“测维扩”能力:

可维护性 = 当依赖变化时,有多少代码需要随之改变。传统MVC三层架构,面临各种库升级、依赖服务升级、中间件升级、jar包冲突、微服务框架、存储扩容升级等依赖变化工作,需要从上到下,每一层的代码都要动,可维护性差。

可扩展性 = 做新需求或改逻辑时,需要新增/修改多少代码。在库表驱动开发、库表驱动架构的开发流程中,一般做第一个需求都非常的快,但是由于代码复用性低,越做到后面,做第N个需求时需要的时间很有可能是呈指数级上升的,绝大部分时间花费在老功能的重构和兼容上,最终你的创新速度会跌为0,促使老应用被推翻重构。

可测试性 = 运行每个测试用例所花费的时间 * 每个需求所需要增加的测试用例数量。在库表驱动开发、库表驱动架构的开发流程中,由于设施搭建困难、用例笨重运行耗时长、业务耦合度高用例爆炸等原因,业务代码很难有比较好的测试覆盖,而绝大部分的业务代码上线前的测试属于人肉的“集成测试”。低测试率导致我们对代码质量很难有把控,容易错过边界条件,异常case只有线上爆发了才被动发现。

最终这个应用变成了一个不敢升级、不敢部署、不敢写新功能、并且随时会爆发的炸弹,终有一天会给你带来惊喜。

DDD根本上解决上面的问题,提升“测维扩”能力。

降本增效: 以爱奇艺DDD落地案例为例,其会员业务部门在打赏业务中实践 DDD 后,取得了以下显著成果:新需求接入开发成本节约20%;更换底层中间件开发成本节约20%;项目熟悉成本节约30%(对DDD有基本了解为前提);单测开发成本指数级降低;上线风险、成本降低

以上内容,具体请参见 尼恩《DDD学习圣经》以及配套视频。

DDD宏观概念

1. 领域、子域

领域可以理解为所要面和的客户要开发的软件的业务范围和业务逻辑。

由于领域很大,所以只能关注领域的的某个方面,并进行相关的软件建模,这个软件模型又称之为“领域模型”。

领域又可按照业务归类逻辑拆分为相互分离的子领域。

为什么要相互分离?理解一下面向接口的编程就可以了,子域与子域间通过接口关联通信,隐藏子域内部细节。

2. 限界上下文

限界上下文可以理解为:

  1. 一般情况,是 一个子域对应一个限界上下文,也就是说,我们拆分领域为分离的子域,也就是在拆限界上下文

  2. 在限界上下文内,各种术语、流程、代号有固定的意思。而这些术语、流程、代号在别的限界上下文就可能不是一样的意思。所以才会使用限界上下文。

    举个例子:在软件开发范畴内(限界上下文),质量是代码经过单元测试和集成测试、自动化测试、安全测试等一系统测试的软件,如果在建筑工程范畴,这个质量就是另一种意思了。

    一个很简单的例子,“顾客”在订单子域是下单付费的登录用户,

    而在产品目录子域,“顾客”则是所有浏览商品的用户,包括匿名未登录的和已登录的用户,同一个术语在不同的子域里有不同的意思,所以这两个子域不应规划到一个限界上下文里。

  3. 甚至一个子域可能包括多个限界上下文,理论上讲应该将这个子域拆分为更细的子域,以达到一个子域对应一个限界上下文的标准。

  4. 可以粗粗地认为限界上下文就是子域关联的一系列的术语、流程和代号。确定统一语言的上下文

  5. 当关注到一个子域时,该子域即是核心域,而与其集成的支撑域和通用子域,为了支持该核心域业务方面,该核心域的限界上下文会包含支撑域、通用子域的一部分或全部。

限界上下文包含哪些领域模型呢?

  1. 实体
  2. 值对象
  3. 聚合
  4. 领域事件
  5. 领域服务
  6. ....

限界上下文是一个显式的边界,领域模型便存在于这个边界之内。

你可以认为他是一个namespace。

限界上下文并不只是局限于容纳模型,它通常标定了一个系统、一个应用程序或者一种业务服务。

3. 核心域、支撑域、通用子域

  1. 核心域,可以理解为领域中你关注的问题空间的业务子域。比如说,电商系统领域,订单子域是关于订单业务的,所以是核心域。
  2. 支撑域,可以理解为领域中涉及业务但是不是核心业务的子域。比如说,商品品类子域,它是关于业务的,但是相对于核心域来说,它不是核心业务,但是它给核心域提供了商品查询支持,所以它是支撑域。
  3. 通用子域,被所有子域需要的子域,一般只提供数据、接口,但不是涉及业务相关。比如说帐号子域,它并不涉及电商的业务,但是却给核心子域提供了帐号相关的数据资料。它属于工具类型,可以理解为对核心域提供接口支持的都可以称之为通用子域。

核心域是相对的。
当你要解决什么问题,那么该问题对应的子域就是核心域,所以对该子域提供支持的都是支撑域和通用子域。

在这里插入图片描述

DDD微观概念

领域驱动设计围绕着领域模型进行设计,通过分层架构将领域独立出来。

这里有两个关键词:

  • 领域模型
  • 分层架构

领域模型的对象包括:实体、值对象和领域服务,领域逻辑都应该封装在这些对象中。

领域模型 Domain Model

领域反映到代码里就是模型,模型是对领域某个方面的抽象,并且可以用来解决相关域的问题,

Domain Model 的基础单元,分为实体和值对象两种。实体和值对象,二者是领域模型中非常重要的基础领域对象(Domain Object,DO)。

在这里插入图片描述

实体对象 Entities

有唯一标志的核心领域对象,且这个标志在整个软件生命周期中都不会发生变化。

这个概念和我们平时软件模型中和数据库打交道的Entity实体比较接近,

不同的是DDD中这些实体会包含与该实体相关的业务逻辑,它是操作行为的载体。

白话解释:实体就是对象的方法和属性实现业务逻辑的类,一般由唯一标识id和值对象组成,属性发生改变,可以影响类的状态和逻辑。

实体 = 唯一身份标识 + 可变性【状态 + 行为】

DDD 中要求实体是唯一的且可持续变化的。

意思是说在实体的生命周期内,无论其如何变化,其仍旧是同一个实体。

唯一性由唯一的身份标识来决定的。

可变性也正反映了实体本身的状态和行为。

实体以 DO(领域对象)的形式存在,每个实体对象都有唯一的 ID。

我们可以对一个实体对象进行多次修改,修改后的数据和原来的数据可能会大不相同。

但是,由于它们拥有相同的 ID,它们依然是同一个实体。

比如商品是商品上下文的一个实体,通过唯一的商品 ID 来标识,不管这个商品的数据如何变化,商品的 ID 一直保持不变,它始终是同一个商品。

值对象 Value Object

依附于实体存在,通过对象属性来识别的对象,它将一些相关的实体属性打包在一起处理,形成一个新的对象。

这些对象是用来表示临时的事物,或者可以认为值对象是实体的属性,这些属性没有特性标识但同时表达了领域中某类含义的概念。

通常值对象不具有唯一id,由对象的属性描述,可以用来传递参数或对实体进行补充描述。

白话解释:不关心唯一性,具有校验逻辑、等值判断逻辑,只关心值的类。

举个栗子:

比如用户实体,包含用户名、密码、年龄、地址,地址又包含省市区等属性,而将省市区这些属性打包成一个属性集合就是值对象。

值对象与实体的区别是什么?

  • 值对象没有唯一标识和连续性,任何属性发生变化, 都可以认为是新的值对象。判断对象是否相同:值对象需要判断所有属性是否相同,而实体只需要判断唯一标识是否相同。
  • 值对象一般依附于实体而存在,是实体属性的一部分,而非独立存在。值对象属性是只读的,可以被安全的共享.

值对象 = 将一个值用对象的方式进行表述,来表达一个具体的固定不变的概念。

还是举个订单的例子,订单是一个实体,里面包含地址,这个地址可以只通过属性嵌入的方式形成的订单实体对象,也可以将地址通过 json 序列化一个 string 类型的数据,存到 DB 的一个字段中,那么这个 Json 串就是一个值对象,是不是很好理解?

当你只关心某个对象的属性时,该对象便可作为一个值对象。

我们需要将值对象看成不变对象,不要给它任何身份标识,注意,**不要给它任何身份标识,该对象便可作为一个值对象。 **

注意:应该尽量避免像实体对象一样的复杂性。

聚合(aggregate)

实体和值对象表现的是个体的能力,而我们的业务逻辑往往很复杂,依赖个体是无法完成的,这时候就需要多个实体和值对象一起协同工作,而这个协同的组织就是聚合。

聚合是数据修改和持久化的基本单元,同一个聚合内要保证事务的一致性,所以在设计的时候要保证聚合的设计拆分到最小化以保证效率和性能。

白话解释:就是对象之间的关联,只是规定了关联对象规则,操作聚合时,类似于操作Hibernate中的One-Many对象的概念。

在DDD中,实体和值对象是很基础的领域对象。实体一般对应业务对象,它具有相对丰富的业务属性和业务行为。而值对象主要是属性集合,主要完成对实体的状态和特征描述。

聚合在 DDD分层架构里属于领域层,同一个微服务的领域层可以有多个聚合,每个聚合内有一个聚合根,多个实体、值对象和领域服务等领域对象。同一个限界上下文内的多个聚合,通过应用层组合在一起共同实现了领域模型的核心领域逻辑。

我们为每一个聚合设计一个仓储完成聚合数据的持久化操作。为了避免聚合数据频繁地提交,建议你尽可能将聚合内变更的数据,封装在一次交易中提交仓储完成持久化。

聚合在领域模型里是一个逻辑边界,它本身没有业务逻辑实现相关的代码。聚合的业务逻辑是由聚合内的聚合根、实体、值对象和领域服务等来实现的。聚合内的实体以充血模型实现自身的业务逻辑。跨多个实体的领域逻辑通过领域服务来实现。比如,有的业务场景需要同-个聚合的A和B两个实体来共同完成,我们就可以将这段业务逻辑用领域服务组合A和B两个实体来完成。

跨多个聚合的业务逻辑的组合和编排,是通过应用服务来实现的。比如,有的业务逻辑需要聚合C和聚合D中的两个领域服务来共同完成,为了避免聚合之间的领域服务直接调用,实现微服务内聚合解耦,此时你可以将这段业务逻辑上升到应用层,通过应用服务组合两个聚合的领域服务来实现。

聚合是领域对象的显式分组,我们把一些关联性极强、生命周期一致的实体、值对象放到一个聚合里。

聚合定义了一组具有内聚关系的相关对象的集合,每个聚合都有一个根对象(聚合根实体)。

我们把聚合看作是一个修改数据的单元。 一个聚合是一组相关的被视为整体的对象。每个聚合都有一个根对象(聚合根实体),从外部访问只能通过这个对象。

根实体对象有组成聚合所有对象的引用,但是外部对象只能引用根对象实体。只有聚合根才能使用仓储库直接查询,其它的只能通过相关的聚合访问。如果根实体被删除,聚合内部的其它对象也将被删除。

为啥需要进行聚合?旨在支持领域模型的行为和不变性,同时充当一致性和事务性边界。

聚合有两个核心要素:

  • 一个聚合根
  • 一个上下文边界

这个边界 根据业务单一职责和高内聚原则,定义了聚合内部应该包含哪些实体和值对象,而聚合之间的边界是松耦合的。

按照这种方式设计出来的服务很自然就是“高内聚、低耦合”的。

聚合在 DDD 分层架构里属于领域层,领域层包含了多个聚合,共同实现核心业务逻辑。

聚合根(aggregate root)

如果把聚合比作组织,那聚合根就是这个组织的负责人。

也叫做根实体,一个特殊的实体,它是聚合的管理者,代表聚合的入口,抓住聚合根可以抓住整个聚合。

聚合根也称为根实体,它不仅是实体,还是聚合的管理者。

  • 首先它作为实体本身,拥有实体的属性和业务行为,实现自身的业务逻辑。
  • 其次它作为聚合的管理者,在聚合内部负责协调实体和值对象按照固定的业务规则协同完成共同的业务逻辑。
  • 最后在聚合之间,它还是聚合对外的接口人,以聚合根 ID 关联的方式接受外部任务和请求,在上下文内实现聚合之间的业务协同。也就是说,聚合之间通过聚合根 ID 关联引用,如果需要访问其它聚合的实体,就要先访问聚合根,再导航到聚合内部实体,外部对象不能直接访问聚合内实体。

聚合是一种边界,它可以封装一到多个实体与值对象,并维持该边界范围之内的业务完整性。

在聚合中,至少包含一个实体,且只有实体才能作为聚合根。

简单概括一下:

  • 通过事件风暴(我理解就是头脑风暴,不过我们一般都是先通过个人理解,然后再和相关核心同学进行沟通),得到实体和值对象;
  • 将这些实体和值对象聚合为“投保聚合”和“客户聚合”,其中“投保单”和“客户”是两者的聚合根;
  • 找出与聚合根“投保单”和“客户”关联的所有紧密依赖的实体和值对象;
  • 在聚合内根据聚合根、实体和值对象的依赖关系,画出对象的引用和依赖模型。

服务(services)

服务提供的操作是它提供给使用它的客户端,并突出领域对象的关系。

所有的service只负责协调并委派业务逻辑给领域对象进行处理,其本身并未真正实现业务逻辑,绝大部分的业务逻辑都由领域对象承载和实现了。

service可与多种组件进行交互,这些组件包括:其他的service、领域对象和repository 或 dao。

服务又细分为领域服务和应用服务。

领域服务(Domain Service)

接下来,看看领域服务和应用服务两个核心概念。

领域中的一些概念,如果是名词,适合建模为对象的一般归类到实体对象或值对象。

如果是动词,比如一些操作、一些动作,代表的是一种行为,如果是和实体或值对象密切相关的,也可以合并到某个实体或者值对象中。

但是,有些操作不属于实体或者值对象本身,或会涉及到多个领域对象,并且需要协调这些领域对象共同完成这个操作或动作,这时就需要创建领域服务来提供这些操作。

有些领域的操作是一些动词,并不能简单的把他们归类到某个实体或者值对象中。

领域的动作,从领域中识别出来之后,应该将它声明成一个服务,它的作用仅仅是为领域提供相应的功能。

简单理解: 就是跨多个领域对象的业务方法

当一些逻辑不属于某个实体时,可以把这些逻辑单独拿出来放到领域服务中。可以使用领域服务的情况:

  • 执行一个显著的业务操作
  • 对领域对象进行转换
  • 以多个领域对象作为输入参数进行计算,结果产生一个值对象

领域服务有两个特征:

    1. 操作代表了一个领域概念,且不是实体或者值对象的一个自然的部分;
  • 2)被执行的操作涉及领域中的其他对象;操作是无状态的。领域服务还有一个好处可以避免领域逻辑泄露到t应用层。

因为如果没有领域服务,那么应用层会直接调用领域对象完成本该是属于领域服务该做的操作。

此外,如果实体操作过多或者过大,为了避免臃肿,也可以使用领域服务来解决。

但是,不能把所有的东西都搬到领域服务里,过度使用可能会导致产生的太多的贫血对象。

理想的情况是没有领域服务,如果领域服务使用不恰当,慢慢又演化回了以前逻辑都在 service 层的局面

应用服务(Application Service)

应用层作为展现层与领域层的桥梁,是用来表达用例和用户故事的主要手段。

应用层通过应用服务接口来暴露系统的全部功能。

在应用服务的实现中,它负责编排和转发,它将要实现的功能委托给一个或多个领域对象来实现,它本身只负责处理业务用例的执行顺序以及结果的拼装。

通过这样一种方式,它隐藏了领域层的复杂性及其内部实现机制。

应用层相对来说是较“薄”的一层,除了定义应用服务之外,在该层我们可以进行安全认证,权限校验,持久化事务控制,或者向其他系统发生基于事件的消息通知,另外还可以用于创建邮件以发送给客户等。

领域服务和应用服务的不同:

  • 领域服务和应用服务是不同的,领域服务是领域模型的一部分,用来处理业务逻辑,而应用服务不是。
  • 应用服务是领域服务的直接客户,负责处理事务、安全等操作,它将领域模型变成对外界可用的软件系统。
  • 跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。

跨多个实体的业务逻辑通过领域服务来实现,跨多个聚合的业务逻辑通过应用服务来实现。

  • 比如有的业务场景需要同一个聚合的 A 和 B 两个实体来共同完成,我们就可以将这段业务逻辑用领域服务来实现;
  • 而有的业务逻辑需要聚合 C 和聚合 D 中的两个服务共同完成,这时你就可以用应用服务来组合这两个服务。

领域事件/领域命令

领域事件是一个领域模型中极其重要的部分,用来表示领域中发生的事件。

img

领域事件 = 事件发布 + 事件存储 + 事件分发 + 事件处理。

程序事件通常分为:系统事件、应用事件和领域事件。领域事件的触发点在领域模型中。

它的作用是将领域对象从对repository或service的依赖中解脱出来,避免让领域对象对这些设施产生直接依赖。

它的做法就是当领域对象的业务方法需要依赖到这些对象时,就发出一个事件,这个事件会被相应的对象监听到并做出处理。譬如跨限界上下文时,使用关键应用事件触发事件传递。

从尼恩的视角简单来说:领域事件是对 repository或service 的异步解耦。

在尼恩写的深度文章 京东一面:20种异步,你知道几种? 含协程 中,就有EventBus 这样的事件总线,完成模块之间的异步解耦,也有 RocketMQ这样的分布式消息组件,完成进程级别的异步解耦。

而领域事件 是设计维度的解耦。EventBus 、RocketMQ是实现层面的异步解耦,当然是先有设计,后有实现。

在DDD中,通过领域事件,一个领域模型可以忽略不相关的领域活动,同时明确领域专家要跟踪或希望被通知的事情,或与其他模型对象中的状态更改相关联。

下面简单说明领域事件:

  • 事件发布:构建一个事件,需要唯一标识,然后发布;
  • 事件存储:发布事件前需要存储,因为接收后的事建也会存储,可用于重试或对账等;
  • 事件分发:服务内直接发布给订阅者,服务外需要借助消息中间件,比如Kafka,RabbitMQ等;
  • 事件处理:先将事件存储,然后再处理。

比如下订单后,给用户增长积分与赠送优惠券的需求。如果使用瀑布流的方式写代码。一个个逻辑调用,那么不同用户,赠送的东西不同,逻辑就会变得又臭又长。

这里的比较好的方式是,用户下订单成功后,发布领域事件,积分聚合与优惠券聚合监听订单发布的领域事件进行处理。

在特定的领域由用户动作触发,表示发生在过去的事件,或者领域状态的变化。

比如:

  • 充值成功
  • 充值失败的事件。

资源仓储/资源库(Repository)

仓储(资源库)是用来管理实体的集合。

仓储介于领域模型和数据模型之间,主要用于聚合的持久化和检索。

仓储里面存放的对象一定是聚合,原因是domain是以聚合的概念来划分边界的;聚合作为一个整体概念,要么一起被取出来,要么一起被删除。

它隔离了领域模型和数据模型,以便我们关注于领域模型而不需要考虑如何进行持久化。

我们将暂时不使用的领域对象从内存中持久化存储到磁盘中。

当日后需要再次使用这个领域对象时,根据 key 值到数据库查找到这条记录,然后将其恢复成领域对象,应用程序就可以继续使用它了,这就是领域对象持久化存储的设计思想。

工厂(Factory)

工厂用来封装创建一个复杂对象尤其是聚合时所需的知识,作用是将创建对象的细节隐藏起来。

客户传递给工厂一些简单的参数,然后工厂可以在内部创建出一个复杂的领域对象然后返回给客户。

工厂(Factory)不是必须的,只有当创建实体和值对象复杂时,建议使用工厂模式。

工厂和资源库都是对领域对象生命周期的管理。

工厂负责领域对象的创建,用于封装复杂或者可能变化的创建逻辑。

资源库负责从存放资源的持久层获取、添加、删除或者修改领域对象。

以上内容,具体请参见 尼恩《DDD学习圣经》以及配套视频。

问题6:DDD建模与微服务架构设计的关系?

在这里插入图片描述

在这里插入图片描述

以上内容没有文字,具体请参见 尼恩《DDD学习圣经》以及配套视频。

问题7: 什么是 CQRS( Command Query Responsibility Segregation )?

CQRS(Command Query Responsibility Segregation)是一种简单的设计模式。

CQRS衍生与CQS,即命令和查询分离,CQS是由Bertrand Meyer所设计。

按照这一设计概念,系统中的方法应该分为两种:改变状态的命令和返回值的查询。‘

Greg young将引入了这个设计概念,并将其应用于对象或者组件当中,这就是今天所要将的CQRS。

CQRS背后的主要思想是应用程序更改对象或组件状态(Command)应该与获取对象或者组件信息(Query)分开。

具体来说:CQRS(Command Query Responsibility Segregation),Command 与 Query 分离的一种模式。

  • Command:命令则是对会引起数据发生变化操作的总称,即新增,更新,删除这些操作,都是命令
  • Query:查询则不会对数据产生变化的操作,只是按照某些条件查找数据

CQRS 的核心思想是将这两类不同的操作进行分离,可以是两个独立的应用,两个不同的数据源,也可以是同一个应用内的不同接口上。

CQRS架构模式,在DDD中是一种很常见的模式,它的用途在于将Command与Query功能进行分离,服务可以独立部署,也可以拆分部署。数据库可以使用一个,也可以读写分离。

在这里插入图片描述

从上图可看出,把数据的变更通过数据同步到另一个库用来查询数据,其实就是数据异构。

但这不是我们现在需要做的,我们是要利用CQRS的思想解决领域驱动中查询功能实现复杂的问题

CQRS 说白了,就是“数据查询”和“业务操作”分离。

在COLA 4.0之前,还有Command Bus和Query Bus 。Command Bus(命令总线):是一种接收命令并将命令传递给命令处理程序的队列。Query Bus(查询总线):是一种查询命令并将查询传递给查询处理程序的队列。

在COLA 4.0中,已经移除了Command Bus和Query Bus的处理,进一步简化了COLA架构。

具体的细节,具体请参见 尼恩《DDD学习圣经》以及配套视频。

问题8:什么是贫血领域模型?什么是充血模型?它们的优缺点是什么?

DDD四种模式

接下来,看看DDD四种模式

在这里插入图片描述

失血模型

领域模型中只有简单的get set方法,是对一个实体最简单的封装,其他所有的业务行为由服务类来完成。

pojo里边光秃秃的,get和set方法都没有

@Data
@ToString
public class User {
    private Long id;
    private String username;
    private String password;
    private Integer status;
    private Date createdAt;
    private Date updatedAt;
    private Integer isDeleted;
}

而且,其他所有的业务行为由服务类来完成,

public class UserService{
    public boolean isActive(User user){
        return user.getStatus().equals(StatusEnum.ACTIVE.getCode());
    }
}

贫血模型

贫血模型是指领域对象里只有get和set方法(POJO),所有的业务逻辑(不包含对象的状态变化在内),放在Business Logic层。

Domain Object(领域对象)模型包含对象属性的定义和操作对象属性的getter/setter方法并包含了对象的行为(例如:就像一个完整的人,具有一些属性如姓名、性别、年龄等,还具有一些能力,如走路、吃饭、恋爱等,这样才是一个完整的对象), 但不包含依赖Dao层(持久层)的业务逻辑。

这部分依赖于Dao层的业务逻辑将会放到Business Logic层(业务逻辑层)中的服务类来实现,组合逻辑也由服务类负责。

可以看出,贫血模型中的领域对象是不依赖于持久层的。

代码架构层次结构是: Client-> Business Facade Service -> Business Logic Service(Business Logic Service是依赖Domain Object的行为) -> Data Access Service

@Data
@ToString
public class User {
    private Long id;
    private String username;
    private String password;
    private Integer status;
    private Date createdAt;
    private Date updatedAt;
    private Integer isDeleted;
    
    public boolean isActive(User user){
        return user.getStatus().equals(StatusEnum.ACTIVE.getCode());
    }
    
    public void setUsername(String username){
        return username.trim();
    }
}


贫血模型在失血模型基础之上,领域对象的包含一些状态变化,但是停留在内存层面,不关心数据持久化。

贫血模型所有的业务逻辑都不包含在内而是放在Business Logic层。

img

贫血模型优点是系统的层次结构清楚,各层之间单向依赖,Client->(Business Facade)->Business Logic->Data Access Object。

可见,领域对象几乎只作传输介质之用,不会影响到层次的划分。

这就是 传统的 数据驱动的开发。

在使用Spring的时候,通常暗示着你使用了贫血模型,我们把Domain类用来单纯地存储数据,Spring管不着这些类的注入和管理,Spring关心的逻辑层(比如单例的被池化了的Business Logic层)可以被设计成singleton的bean。

假使我们这里逆天而行,硬要在Domain类中提供业务逻辑方法,那么我们在使用Spring构造这样的数据bean的时候就遇到许多麻烦,比如:bean之间的引用,可能引起大范围的bean之间的嵌套构造器的调用。

充血模型

在贫血模型基础上,负责数据的持久化。

@Data
@ToString
public class User {
    private Long id;
    private String username;
    private String password;
    private Integer status;
    private Date createdAt;
    private Date updatedAt;
    private Integer isDeleted;
    
    private UserRepository userRepository;
    
    public boolean isActive(User user){
        return user.getStatus().equals(StatusEnum.ACTIVE.getCode());
    }
    
    public void setUsername(String username){
        this.username = username.trim();
        userRepository.update(user);
    }
}

充血模型层次结构和上面的差不多,不过大多业务逻辑放在Domain Object里面,应用服务层 Business Logic只是简单封装部分业务逻辑以及控制事务、权限等,

这样层次结构就变成Client->(Business Facade)->Business Logic->Domain Object->Data Access Object。

img

它的优点是面向对象,Business Logic符合单一职责,不像在贫血模型里面那样包含所有的业务逻辑太过沉重。

胀血模型

service都不需要,所有的业务逻辑、数据存储都放到一个类中。

对于DDD来说,失血和胀血都是不合适的,

失血太轻量没有聚合,胀血那是初学者才这样写代码。

那么充血模型和贫血模型该怎么选择?

充血模型依赖repository接口,与数据存储紧密相关,有破坏程序稳定性的风险。

贫血模型与充血模型区别

区别 充血模型(DDD开发方式下 贫血模型(传统OOP开发方式下)
编码方式 以领域对象为主的行为状态转换 事务脚本(CRUD、判断、循环、检查等逻辑只是简单的方法级别拆分)
Service 跨领域逻辑的组合 事务脚本的对外包装
Domain 一个领域的entity集合包 VO/DTO
Entity 包含了一个领域里的状态、以及对状态的直接操作单元,具有自闭环的对象。 VO/DTO
ValueObject 简单的基本值对象,如电话号码、地址等,组成entity的基本单元 语言基本类型
Aggregation (有点类似数学中集合的定义)将实体和值对象划分为聚合并围绕着聚合定义边界。 多个VO/DTO
Aggregation Root 唯一可以给外界作为某个Aggregation引用的entity。 单个VO/DTO

优缺点

  • 贫血模型的优缺点
    • 优点1:很传统的编程思路,被许多程序员所掌握,许多教材采用的是这种模型,对于初学者,这种模型很自然,甚至被很多人认为是java中最正统的模型。
    • 优点2:思路清晰,事务边界清晰一般来说service的每个方法都可以看成一个事务,因为通常Service的每个方法对应着一个用例
    • 缺点:对象状态与行为分离,不能直观地描述领域对象。行为的设计主要考虑参数的输入和输出而非行为本身,不太具有面向对象设计的思考方式。
  • 充血模型的优缺点
    • 优点:贫血model偏重个性化,面向过程式。 充血偏共性化,采用OO设计,类拥有其属性及对应的行为,通过将职责分配到相应的模型对象或Service,可以很好的组织业务逻辑,因此非常适合于复杂的企业业务逻辑的实现,以及可复用程度比较高。
    • 缺点1:领域驱动建模要求对领域模型完整而透彻的了解,只给出一个用例的实现步骤是无法得到领域模型的,这需要和领域专家的充分讨论。错误的领域模型对项目的危害非常之大,而实现一个好的领域模型非常困难。
    • 缺点2:对象高度自洽的结果是不利于大规模团队分工协作。一个编程个体至少要完成一个完整业务逻辑的功能。对于单个完整业务逻辑,无法再细分下去了。

问题9:微服务一定要DDD,为什么?TDD和DDD 有何关系?

具体答案,请参见此文: https://mp.weixin.qq.com/s/80Gza3-9pO8bYlSJoEDVWg

问题10:DDD架构,如何落地?

具体答案,请参见此文: https://mp.weixin.qq.com/s/_NAHYDIvmT7EbffXkIpqYw

问题11:DDD 领域层,该如何设计?

具体答案,请参见此文:https://mp.weixin.qq.com/s/njBl791vFCd94UplPJEFKw

问题12:微服务如何拆分?原则是什么?

具体答案,请参见此文: https://mp.weixin.qq.com/s/bExjRkVbDLTV2Wf9G6dzrg

问题13:给一个需求,请用DDD设计出来

具体答案,请参见此文: https://mp.weixin.qq.com/s/izzUXIpVmURq6hTCEN0ARQ

参考文献

尼恩特别编著 《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落地:从阿里单据系统,看DDD在大厂如何落地?

DDD落地:有赞的生产项目,DDD如何落地?

DDD落地:从携程订单系统重构,看DDD的巨大价值

DDD落地:京东的微服务生产项目,DDD如何落地?

DDD落地:从阿里商品域,看DDD在大厂如何落地?

240Wqps,美团用户中台, 如何使用DDD架构?

DDD落地:爱奇艺打赏服务,如何DDD架构?

大厂痴迷DDD:从高德portal重构,看DDD的巨大价值

高开面试:给一个需求,请用DDD设计出来

大厂痴迷DDD:从高德portal重构,看DDD的巨大价值

说在最后:有问题找老架构取经

DDD 相关的面试题,是非常常见的面试题。也是核心面试题。

以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典》V174,在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

另外,如果没有面试机会,可以找尼恩来帮扶、领路。

  • 大龄男的最佳出路是 架构+ 管理
  • 大龄女的最佳出路是 DPM,

图片

女程序员如何成为DPM,请参见:

DPM (双栖)陪跑,助力小白一步登天,升格 产品经理+研发经理

领跑模式,尼恩已经指导了大量的就业困难的小伙伴上岸。

前段时间,领跑一个40岁+就业困难小伙伴拿到了一个年薪100W的offer,小伙伴实现了 逆天改命

技术自由的实现路径:

实现你的 架构自由:

吃透8图1模板,人人可以做架构

10Wqps评论中台,如何架构?B站是这么做的!!!

阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了

峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?

100亿级订单怎么调度,来一个大厂的极品方案

2个大厂 100亿级 超大流量 红包 架构方案

… 更多架构文章,正在添加中

实现你的 响应式 自由:

响应式圣经:10W字,实现Spring响应式编程自由

这是老版本 《Flux、Mono、Reactor 实战(史上最全)

实现你的 spring cloud 自由:

Spring cloud Alibaba 学习圣经》 PDF

分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)

一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)

实现你的 linux 自由:

Linux命令大全:2W多字,一次实现Linux自由

实现你的 网络 自由:

TCP协议详解 (史上最全)

网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!

实现你的 分布式锁 自由:

Redis分布式锁(图解 - 秒懂 - 史上最全)

Zookeeper 分布式锁 - 图解 - 秒懂

实现你的 王者组件 自由:

队列之王: Disruptor 原理、架构、源码 一文穿透

缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)

缓存之王:Caffeine 的使用(史上最全)

Java Agent 探针、字节码增强 ByteBuddy(史上最全)

实现你的 面试题 自由:

4800页《尼恩Java面试宝典 》 40个专题

免费获取11个技术圣经PDF:

标签:聚合,蚂蚁,对象,实体,业务,领域,面试,DDD
From: https://www.cnblogs.com/crazymakercircle/p/18183808

相关文章

  • mysql面试
    001Mysql如何实现索引机制MySQL中索引分三类:B+树索引,Hash索引,全文索引InnoDB索引和MySAM索引实现的区别是什么?MySAMMySAM索引文件和数据是分离的,使用B+树实现,主键索引和辅助索引实现一致,索引文件仅保存记录所以在页的指针(物理地址),通过这些地址来读取页,进而读取被索引的行 ......
  • C++面试考点
    c++基础1、malloc/free和new/delete的异同点?2、cpp程序运行时内存模型?答:代码区,只读的区域,是代码被翻译成二进制机器指令存储的地方;数据区,分为初始化数据区和未初始化数据区,用来存储全局变量和static变量的地方;堆区,动态内存分配的地方,new和malloc分配就是从此处分配;栈区,函数调用......
  • 编程:java 发送email程序:通用版本:借鉴“蚂蚁小哥 <antladdie@163.com>”的文章
     1packagecom.alibaba.otter.canal.adapter.launcher.loader;23importjava.text.SimpleDateFormat;4importjava.util.*;5importjavax.mail.*;6importjavax.mail.internet.*;78publicclassJavaxJavaMailClient{910private......
  • mysql面试题
    索引失效1.模糊查询2.使用函数和计算:3.不匹配的数据类型:4.使用OR条件的不同索引列:5.复合索引的最左前缀原则:6.索引列在条件中使用负向查询:如NOTIN、NOTEXISTS、NOTLIKE等负向查询条件可能会导致索引失效。7.索引列在条件中使用NULL:当索引列中包含NULL值,并且在查询条件......
  • 前端面试笔试题集锦
    今日有幸约面海尔,出了几道面试题,隐隐约约感觉做错了。后面复盘回忆,才发现确实是做错了,看来前端的自我修炼道路还是漫漫远兮,必将上下而求索!一日不练,技能生疏,终日不学,不思进取,罪过罪过!下面贴出被面到的几道题目:1.async和promise执行顺序asyncfunctionasync1(){console.log('......
  • 拿去面试!一个基于 DDD 的高性能短链系统
    众所周知,商城、RPC、秒杀、论坛、外卖、点评等项目早早就烂大街了,翻开同学的简历一看10个里面有9个是这些,翻遍全网再很难找到一个既有含金量又能看得懂的项目,针对此,我研发了这样一个可以快速上手又具有较多技术点的短链项目:高性能短链系统EZLink!技术栈如下:DDD架构Reac......
  • 9.数字马力面试
    9.1Java基础 9.1.1volatile的概述和原理在Java中volatile是一个防止指令重排以及保证可见性的关键字。如果我们将变量声明为volatile,那么就指示JVM这个变量共享且不稳定,每次从主存中进行读取。AQS的status就是使用volatile修饰的。借用Guide哥的图片: 如果......
  • 面试本质 & 注意事项
    面试面试本质上只有三个问题  这活你能干吗?(考核能力)  这活你爱干吗?(考核意愿)  我们能和你共事吗?(考核团队合作) 面试最后阶段——面试官问你还有什么需要了解的?  面试官会问这一点的另一个原因是,面试官要考察你加入公司的意愿。  提问这个问题还能看出求职者是不是真的......
  • 蓝桥杯-蚂蚁感冒
    长100厘米的细长直杆子上有n只蚂蚁。它们的头有的朝左,有的朝右。每只蚂蚁都只能沿着杆子向前爬,速度是1厘米/秒。当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。这些蚂蚁中,有1只蚂蚁感冒了。并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。请你计算,当所有蚂蚁......
  • 大数据面试SQL每日一题系列:最高峰同时在线主播人数。字节,快手等大厂高频面试题
    大数据面试SQL每日一题系列:最高峰同时在线主播人数。字节,快手等大厂高频面试题之后会不定期更新每日一题sql系列。SQL面试题每日一题系列内容均来自于网络以及实际使用情况收集,如有雷同,纯属巧合。1.题目问题1:如下为某直播平台各主播的开播及关播时间数据明细,现在需要计算该平......