插: AI时代,程序员或多或少要了解些人工智能,前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家(前言 – 人工智能教程 )

坚持不懈,越努力越幸运,大家一起学习鸭~~~

1 背景

通过营销活动实现客户/用户拉新、留存和促活是业界普遍采用的方法。为实现商户增长和留存,美团核心本地商业/商业增值技术部也构建了相应的营销系统来支撑商户的线上营销运营。在系统建设过程中,面临着业务体量大、行业跨度大、场景多样、客户结构复杂,需求多变等挑战。本文试图还原从0到1构建面向商户的营销系统过程中,并通过DDD(领域驱动设计)来应对系统设计和建设中遇到的业务复杂度高、需求多变、维护成本大等问题。

2 基本概念

软件系统的复杂性主要体现在三个方面。

  • 隐晦:一是抽象层面的隐晦,抽象系统时,每个人都有自己特定的视角,你需要站在对方的角度才能明白他为什么这么做;其次是实现层面的隐晦,代码是一种技术实现,通常与现实世界的业务概念脱节,无形中增加了理解成本。
  • 耦合:代码层面的耦合扩大了修改范围;模块层面的耦合需要跨模块/服务交互;系统层面的耦合则需要跨团队协作。从代码到模块再到系统,耦合的影响逐渐扩大,成本随之增加。
  • 变化:业务需求决定了系统功能,不同的用户需求不一样,不同的业务发展阶段需求在不断变化,系统功能要随着业务需求的变化不断调整,这时就涉及到系统改动的频次和范围。

图片
领域驱动设计(Domain-Driven Design,简称DDD)是一种软件设计方法,它通过将业务领域知识作为设计的核心,来应对软件系统的复杂性。DDD的目的是使软件设计更加贴近业务需求,提高软件的可维护性和可扩展性。然而,DDD的概念体系相当复杂,学习曲线较为陡峭。即使深入研究了DDD的两本经典著作,实际应用时仍可能感到资源不足。

领域驱动设计的核心概念

  1. 领域模型:领域模型是DDD的核心,它反映了业务领域中的概念和业务规则。2. 界限上下文:界限上下文定义了模型的适用范围,帮助团队明确不同领域模型的边界。3. 实体和值对象:实体具有唯一标识,而值对象则强调属性值,不具有唯一性。4. 聚合和聚合根:聚合是一组相关对象的集合,聚合根是聚合中的主要对象。5. 领域服务:领域服务封装了领域逻辑,不属于任何实体或值对象。6. 应用服务:应用服务协调领域对象,处理应用程序的用例。

学习DDD的挑战- 概念体系复杂:DDD包含了多个专业术语和概念,需要时间和实践来掌握。- 学习曲线陡峭:从理论到实践的转换需要深入理解DDD的各个组成部分。- 项目落地困难:将DDD应用于实际项目中可能会遇到诸多挑战,需要不断调整和优化。

如何有效学习DDD- 深入阅读经典著作:通过阅读DDD的经典书籍,理解其核心思想和方法。- 参与实际项目:通过实际项目来应用DDD,加深对概念的理解和运用。- 交流与分享:与DDD社区进行交流,分享经验和学习心得,获取反馈。

结论尽管DDD的学习过程可能充满挑战,但它为解决软件设计的复杂性提供了一种有效的途径。通过不断学习和实践,可以逐步掌握DDD,并将其应用于项目中,以提高软件的质量和效率。

图片

领域驱动设计(DDD)概述

1. 计算机语言的演进在早期,计算机创新主要集中于语言层面,致力于提供功能更强大的编程语言,以充分利用计算机的计算能力。

2. 面向对象语言的出现进入60年代,面向对象编程语言应运而生。它们通过封装、继承和多态等特性,极大提升了语言的表达力。

3. 面向对象分析与设计到了80年代,面向对象的分析与设计方法开始流行。这些方法解决了如何构建类模型的问题,帮助开发者更有效地使用面向对象语言实现系统。

4. 领域驱动设计(DDD)的诞生2000年,领域驱动设计方法应运而生。它通过深入分析业务,提取关键概念,并建立相应的领域模型。随后,利用面向对象的分析与设计方法,构建对应的类模型,实现了从物理世界到计算机世界的无缝映射。

5. 领域模型的重要性领域模型是DDD的核心,它不仅帮助开发者理解业务需求,还为软件系统的设计和实现提供了指导。通过领域模型,可以更准确地反映业务逻辑,提高软件的可维护性和可扩展性。

图片
领域是一个包含用户、问题和解决方案的综合体。在领域驱动设计中,我们关注的是特定业务背景下的领域知识,这些知识包括流程、规则和解决问题的方法。以下是对领域和领域驱动设计的详细阐述:

领域的定义1. 涉众域:领域中包含的用户群体,即涉众。2. 问题域:用户需要解决的业务问题或实现的业务价值。3. 解决方案域:针对问题域提出的解决方案。

领域驱动设计领域驱动设计是一种围绕领域知识来设计系统的软件设计方法。它强调对业务流程、规则和方法的深入理解,并将其转化为系统设计。

营销系统领域示例以营销系统为例,其服务的用户群体包括:- 运营人员- 销售人员- 电销人员- 商户

这些用户需要解决的核心问题包括:- 如何发放优惠券(发券)- 确定优惠券的接收对象(发给谁)- 定义优惠券的类型(发什么) 针对这些问题,营销系统提供的解决方案包括:- 通过营销活动来实现优惠券的发放,不同的活动类型对应不同的促销策略。- 确定目标人群,以决定优惠券的接收对象。- 定义权益,明确优惠券的种类,如红包、代金券、折扣券等。 通过这样的设计,营销系统能够满足不同用户的需求,实现业务目标。
图片

领域驱动设计落地指南

领域驱动设计(Domain-Driven Design, DDD)是一种软件开发方法论,强调以领域为中心,以业务专家的视角来设计软件系统。本文将从三个关键方面介绍如何将DDD理念落地到实际开发中:

1. 战略设计战略设计阶段是DDD的起点,主要任务包括:

  • 确定用例:明确系统需要满足的业务需求和用户行为。
  • 统一语言:建立领域专家与开发团队之间的共同语言,确保沟通的一致性。
  • 划分边界:识别系统中的不同领域,并为它们设定清晰的边界。

2. 战术设计战术设计是将战略设计阶段的概念模型转化为具体的类(代码)模型的过程。这一阶段的关键活动包括:

  • 概念模型转码:将领域概念转化为软件系统中的类和对象。
  • 类模型细化:确保每个类都具有明确的责任和行为,符合单一职责原则。

3. 代码架构代码架构阶段是将系统设计映射到实际的系统实现中。这一阶段的主要任务有:

  • 架构映射:将设计模型转化为代码结构,确保代码的组织和模块化。
  • 系统实现:根据架构设计,开发具体的功能实现。 通过这三个阶段的有序进行,可以确保DDD理念在软件开发过程中得到有效实施,从而构建出既符合业务需求又易于维护和扩展的软件系统。
    图片
    在进行战略设计之前,我们首先需要明确业务的运作方式,即业务的实践模式。以下是几种常见的方法来确定业务实践模式:
  1. 用例图:这是一种非常直观的方法,用于展示用户与系统的交互过程。2. 用户故事:在敏捷开发中广泛应用,它从用户(Who)、需求(What)和原因(Why)三个角度来描述业务需求。3. 交互原型:展示了用户操作的界面及其流程,但可能过于关注于用户体验而忽略了业务的底层逻辑。4. 事件风暴:专注于业务的底层逻辑分析,适用于大型且复杂的业务场景,但使用起来相对复杂。 每种方法都有其独特的优势和局限性,选择合适的方法对于战略设计的成功至关重要。
    图片

下图是营销系统的用例图(起初并没有这么完整,这是多次迭代后的结果):

图片
在确定业务玩法后,首要任务是统一语言和概念。以下是对概念抽取和统一语言的步骤:

  1. 概念抽取:从用例中识别出关键概念,这些概念是描述业务的核心元素。例如,活动规则可能在不同语境下有多种表述方式,如充值送规则、返还规则等。
  2. 概念甄别:对抽取出的概念进行筛选和整合,去除不准确或冗余的描述,保留真正反映业务本质的术语。例如,将不同的活动规则统一称为“档位”,以简化术语。
  3. 采纳业务术语:在统一语言时,优先使用业务人员习惯的术语。这样做有助于促进跨部门的沟通和理解,例如,将充值送活动描述为不同“档位”的红包赠送。
  4. 结构化表达:确保概念的表达具有条理性,例如,将活动规则按照充值金额和赠送红包的对应关系进行结构化描述。
  5. 推行统一语言:制定明确的语言规范,确保团队成员在交流时使用统一的术语,减少误解和沟通成本。 通过上述步骤,我们可以有效地统一业务和技术人员之间的语言,提高沟通效率,确保业务规则的准确传达和执行。
    图片

接着是明确概念的含义,概念由术语、Term(术语的英文版)和含义三部分构成。含义明确的术语就是统一语言,这些术语将用在日常需求沟通、产品文档,技术设计以及代码实现中。

图片
在进行概念建模的过程中,我们首先需要明确各个概念的定义。随后,识别这些概念之间的关联,例如一对一、一对多和多对多关系。这一步骤对于理解业务实体的核心属性和行为至关重要。通过建立清晰的概念模型,我们可以在业务需求讨论、产品和技术方案设计中使用统一的语言来描述,从而确保团队成员之间的沟通更加顺畅和准确。此外,精心抽取的概念和构建的概念模型能够更深入地反映业务本质,为后续的详细设计和实施提供坚实的基础。

概念建模步骤:

  1. 明确概念定义:理解并定义业务领域中的各个概念。2. 识别概念关系:分析概念之间的关联,如一对一、一对多和多对多。3. 确定核心属性和行为:确定每个业务实体的关键属性和行为。4. 建立概念模型:基于上述分析,构建一个反映业务本质的概念模型。5. 统一语言描述:在团队中使用统一的语言来描述概念模型,确保沟通一致性。6. 为后续设计打下基础:精心构建的概念模型为技术方案和产品的设计提供指导。

注意事项:- 确保概念定义的准确性和一致性。- 在识别概念关系时,注意区分不同类型的关联。- 概念模型应简洁明了,易于理解和沟通。- 概念模型应反映业务的核心需求和目标。

图片
在业务、产品和技术三个领域中,统一语言和概念模型对于确保需求的共识和沟通的一致性至关重要。缺乏统一性会导致沟通混乱,甚至产生错误。例如,在开发营销系统时,由于没有统一的语言来描述’商户’,不同领域使用了不同的概念,如资金域的’资金账户’、‘账号ID’和’资金账号’,商家域的’商家账号’、‘商家ID’、‘登录号’和’登录ID’,以及营销域中不同人使用的多样概念。这种不一致导致了沟通混乱,甚至在发放红包时出现了ID误用,红包发错的问题。 为了解决这一问题,我们对这些概念进行了梳理和统一。在营销域,我们决定只关注’资金账户’和’商家账号’两个概念。系统功能上,我们明确了使用’资金账户’或’商家账号’来发送红包,从而减少了出错的可能性。通过这种方式,我们确保了业务流程的顺畅和准确性。
图片
在软件开发过程中,我们经常面临将复杂概念转化为可执行代码的挑战。以下是对这一过程的系统化描述:

  1. 概念模型的构建:首先,我们需要构建一个概念模型,这张大网捕捉了概念之间的联系以及它们的关键属性。但请注意,概念模型本身并不直接对应于代码模型。
  2. 概念到代码的转换:要将概念模型转化为代码模型,我们需要进行进一步的拆解工作。这个过程涉及到化繁为简,将复杂的结构分解为更易于管理和实现的简单部分。
  3. 本源论的应用:本源论提供了一种哲学视角,认为世界的本质是简单的。在软件开发中,这意味着我们可以将复杂问题拆解为多个简单问题,从而简化开发过程。
  4. 康威原理的指导:康威原理指出,系统的架构往往受到组织内部沟通架构的影响。在系统实现阶段,我们首先需要确定系统的边界,然后根据这些边界来组织团队的分工和协作。
  5. 团队协作的组织:在确定了系统的边界和简单问题后,我们需要根据团队的资源和能力来组织分工协作。这有助于提高开发效率和质量。 通过上述步骤,我们可以更系统地将复杂的概念模型转化为实际的代码模型,并有效地组织团队资源以实现项目目标。
    图片
    业务流程拆解是实现系统化管理的重要手段。以下是根据美团技术内部分享的拆解方法,结合业务角度进行的系统化拆分策略:

一、基于涉众域拆解这种拆解方式按照用户相关性进行,不同的用户群体使用不同的系统功能。例如,CRM系统可能需要市场人员、销售人员和客服人员三类角色协同工作,以完成客户触达、签约合作和售后服务等职能。这种拆解方法简单明了,但可能存在功能重复建设的问题。

二、基于问题域拆解在这种拆解方式中,不同角色或用户面临的问题可能是相似的。例如,营销系统可能服务于销售、商户和销运等角色,但其核心是解决如何发放优惠券、发给哪些人群以及提供何种权益的问题。这种拆解方法较为抽象,可能在复用性上存在不足。

三、基于解决方案域拆解这种方式认为不同的问题可能共享相同的解决方案。例如,HR领域需要处理请假审批,财务领域需要处理报销流程,CRM领域需要处理客户资质审批,这些领域都可以通过构建一个通用的审批流引擎来统一解决。这种拆解方法最抽象,也最接近业务本质,但需要避免过度设计的风险。

通过以上三种方法,可以实现业务流程的系统化拆分,以提高效率和一致性。
图片

营销系统基于问题域拆解为五个子域(活动域,权益域,人群域,推送域,数据域),每个子域解决特定的问题,各子领域相对内聚和简单:

图片
在业务系统的运作中,各个子域需要相互协作以实现整体目标。这种协作依赖于上下文映射的明确定义。以下是对上下文映射及其在不同子域间协作中作用的详细描述:

  1. 子域间的协作需求: - 子域之间必须相互配合以保证业务流程的顺畅。
  2. 目标人群的识别: - 活动域关注两个主要目标群体:资金账户和商家账号。 - 资金账户代表已签约的商户,由财务域定义。 - 商家账号代表未签约的商户,由账号域定义。
  3. 概念的外部依赖: - 营销域并非原生地定义资金账户和商家账号这两个概念。 - 营销域需要依赖外部概念,并将其映射到自身域内。
  4. 映射实现方式: - 通过防腐层(Anti-Corruption Layer)实现外部概念与营销域的对接。 - 这种映射有助于维护系统的清晰界限,同时实现跨域的数据整合。
  5. 领域驱动设计中的映射关系: - 领域驱动设计中定义了九种上下游映射关系,但在此不展开详细讨论。 通过上述结构化的描述,我们可以清晰地理解上下文映射在不同子域间协作中的重要性及其实现方式。
    图片

下图是营销系统的整体上下文关系:

图片

战略设计迭代过程

战略设计并非一蹴而就,而是一个持续迭代的动态过程。以下是迭代过程的三个主要来源:

1. 用例精化在需求探讨中,用例会不断丰富和细化。随着对业务理解的加深,用例将更加具体和全面。

2. 需求变更业务的持续发展会带来需求的变化,这将直接影响用例和相关概念的内涵。随着需求的演变,概念模型也需要进行相应的调整和迭代。

3. 方案选型当产品、业务或技术发生较大变化时,可能需要采用不同的实现方式。这种变化会导致所采用的概念和底层模型发生根本性的变化。例如,在营销活动域的构建过程中,从参与规则到目标人群的转变,体现了概念和模型的迭代。

概念模型的验证方法

构建了统一语言和概念模型后,验证其有效性至关重要。以下是两种常用的验证方法:

场景走查通过将模型应用于所有相关场景,确保抽象出的概念模型和统一语言能够正确描述实际业务。

业务预判预判未来业务变化的方向,评估概念模型在变化发生时是否具有足够的灵活性和扩展性,以支持业务的发展。

案例分析以营销活动域的构建为例,从参与规则到目标人群的转变,展示了概念模型如何适应业务需求的变化,并实现战略设计的迭代。

结论战略设计是一个持续的过程,需要不断地根据用例精化、需求变更和方案选型进行迭代。同时,通过场景走查和业务预判来验证和优化概念模型,确保其与业务发展同步。

图片

4 战术设计实践

战略设计得到了概念模型,战术设计则是将概念模型映射为代码模型,有很多编程范式,比如事务脚本、表模式、面向对象,函数式等,最好的方式是面向对象的实现。

图片

从概念模型到对象模型的转换过程

1. 概念的分层与继承实现

概念模型是分层的,例如营销活动是一个泛化概念,它包括了多种具体活动,如充值送活动、消费返活动和买赠活动等。在对象模型的构建过程中,我们通过派生或继承的方式,将这些具体活动与泛化概念联系起来。

2. 概念关系与对象关系映射

概念之间的关系需要映射到对象模型中。以营销活动为例,它包含档位和库存等元素。在构建对象模型时,我们可以通过组合的方式实现这种包含关系,即档位对象和库存对象作为营销活动对象的属性。

3. 概念属性与行为的转换

概念的属性和行为可以直接转化为对象的属性和行为。同时,概念的状态机和生命周期也会转化为对象的状态机。

4. 对象的分类:实体与值对象

在对象模型中,存在两种类型的对象:实体和值对象。两者的主要区别在于是否具有统一的标识和独立的状态。实体对象具有唯一标识和自己的状态,而值对象则没有。
图片
在设计领域驱动设计(DDD)中的聚合根时,我们需要考虑多个因素来确定其粒度。以下是对聚合根设计的一些思考和原则:

聚合根设计原则

  1. 业务一致性:确保聚合根能够保证业务操作的一致性,例如,当库存或档位发生变更时,相关活动的状态也应相应更新。
  2. 数据完整性:聚合根应确保数据的完整性,避免出现孤立的数据,如没有档位的活动或没有库存的活动。
  3. 技术限制考虑:在设计聚合根时,需要考虑技术限制,如处理大量数据时可能需要采用特殊技术或架构。
  4. 业务逻辑封装:无论选择大聚合根还是小聚合根,业务逻辑都应得到妥善封装,以保持系统的清晰性和可维护性。

聚合根粒度决策

  • 小聚合根模式:每个对象对应一个聚合根,使得每个聚合根都相对简单。但需要通过领域服务来处理对象间的业务耦合。
  • 大聚合根模式:将所有相关概念(活动、库存、档位等)封装在一个聚合根中。这可能导致聚合根复杂度增加,但可以减少对象间交互的复杂性。

技术实现

  • 对于小聚合根模式,可以通过构建领域服务来封装跨聚合根的业务逻辑,处理如库存或档位变更时活动状态的更新。
  • 对于大聚合根模式,可以通过懒加载等技术手段来优化性能,尽管这可能会增加系统的复杂性。

结论

聚合根的设计应根据具体的业务需求和技术条件来决定。在保证业务一致性和数据完整性的同时,也要考虑到技术实现的可行性和系统性能。通过合理的聚合根设计,可以有效地支持业务操作,同时保持系统的可维护性和扩展性。
图片

如下图是营销系统的聚合根:

图片
在软件开发过程中,我们经常面临选择使用贫血模型还是充血模型的决策。尤其在使用Spring MVC框架时,由于其通常以单例模式运行,引入充血模型可能会增加开发和理解的复杂性。然而,有些领域逻辑并不适宜直接集成在聚合根中。例如,在存在多个充值送活动的情况下,用户只能参与优先级最高的活动。在这种情况下,虽然活动优先级的标识可以放在聚合根中,但选择优先级最高的活动这一逻辑,应通过领域服务来实现,以保持聚合根的清晰和职责单一。 聚合根的设计应遵循领域驱动设计原则,确保从概念模型到类模型再到代码实现的一致性。使用统一的语言来描述业务逻辑,有助于提高代码的可读性和可维护性。在实现代码时,应避免使用如’updateStatus()‘这样缺乏业务含义的方法名,而应使用’submitCampaign()’、‘approveCampaign()’、‘cancelCampaign()‘等能够直观反映业务操作的方法名。

代码实现与模型选择

  1. 模型选择考量:分析Spring MVC的运行模式,权衡贫血与充血模型的优缺点。2. 领域逻辑分离:将不适合集成在聚合根中的逻辑,如活动优先级选择,通过领域服务实现。

统一语言的重要性

  • 确保从概念到实现的一致性。- 提升代码的表达力和业务相关性。

代码实现的最佳实践

  • 避免通用方法名:不使用含义模糊的方法名,如’updateStatus()’。- 使用业务驱动的方法名:采用’submitCampaign()‘等明确反映业务操作的方法名。 通过上述实践,可以提高代码的质量和开发效率,同时也便于团队成员之间的沟通和协作。
    图片

5 代码架构实践

完成战术设计后,如何组织代码架构?无论是六边形架构,整洁架构还是洋葱架构本质上都是围绕着领域模型展开,应用层、基础设施层和外部接口都依赖领域模型:

图片

下图是我们团队的工程实践,与前面三个图本质上是一样的。领域层和应用层次放在中间(两者都属于领域逻辑),基础设施和用户接口依赖中间层:

图片

领域驱动设计(DDD)实践总结

1. 系统设计原则- 借鉴业界实践:对于常见的系统如CRM、HR、SCM等,应充分利用现有的行业经验和实践,避免重新发明轮子。- 统一语言:统一的语言是构建概念模型的基础,缺乏统一语言将导致代码模型的不可靠。- 团队协作:领域驱动设计是团队合作的过程,每个参与者都应成为领域专家,技术团队可以主导,但最终必须服务于产品和业务。- 持续迭代:领域模型虽相对稳定,但应随着业务理解的深入和业务变化而持续迭代。

2. 常见误区- 深陷概念体系:避免过度纠结于领域驱动设计的概念,如聚合根、值对象等,而忽视了从业务出发构建概念模型的重要性。- 设计领域模型:领域模型是通过战略设计步骤从业务中抽象出来的,而非凭空设计。- DDD的误解:即使了解DDD,也不能保证一定能产生优秀的领域模型,但可以减少设计上的弯路。

3. 设计的开始- 需求即设计:在讨论需求时,设计过程已经开始,统一语言是设计的重要组成部分。

4. 解决方案域的层次1. 功能模型:基于产品玩法,转化为用例,进一步提炼出功能模型。2. 概念模型:对功能模型进行抽象,形成统一的语言和概念模型。3. 代码模型:将概念模型映射为可执行的代码模型。4. 数据模型:设计业务数据的存储结构,以支持业务需求。

5. 设计陷阱- 过早关注数据:避免在功能模型阶段就急于设计数据模型,陷入CRUD模式。- 事务脚本陷阱:对复杂功能,避免陷入事务脚本的设计,因其维护成本较高。

6. 领域分类- 学科型领域:如财务、会计等,需要深入理解学科知识。- 实践型领域:如CRM、订单交易等,可借鉴前人经验,但也要结合自身业务的独特性进行探索。

以上内容是对领域驱动设计实践的总结和思考,旨在帮助团队更好地理解和应用DDD原则,避免常见陷阱,构建高质量的软件系统。
图片