Spring 系统设计实战——为你的应用定义领域

24 阅读33分钟

在本章中,我们将首先识别系统中最有价值的功能。鉴于产品团队通常有一长串需要实现的需求,我们的首要任务是先交付价值最高的功能。接下来,我们将探索需要处理的领域,这将指导我们需要开发的 API。最后,我们将概述这些领域之间随时间变化的信息流,为在项目中实现用例创建一张清晰的地图。

本章将涵盖以下主题:

  • 确定优先交付的功能
  • 为应用定义领域和边界
  • 为领域定义合适的服务
  • 制作领域图
  • 跨服务对活动进行排序

本章是从完整需求到代码之间的关键桥梁。在这里,我们开始对系统进行建模。我们将教授如何从需求中提取系统的关键组件(我们称之为领域),以及如何创建图表以便与非程序员讨论系统编程时更易理解。本章内容将帮助你在架构讨论中吸引跨学科团队的参与,更好地阐述需求。

掌握本章提供的工具和技能,将为你成为一名受人尊敬的架构师提供巨大优势——能够将业务需求转化为简单而全面的系统设计。如果你掌握了这些方法,就已经踏上了成功的领导职业道路。

你感到兴奋吗?我知道我很兴奋。那我们开始吧!

技术要求

在本章中,你需要使用两个简单的工具来完成各节提出的分析:

  • draw.io:一个在线绘图工具
  • PlantUML:一个用于绘制时序图的工具,我们将在最后几节中使用

你可以在这个 GitHub 仓库中访问本章的所有代码:
github.com/PacktPublis…

确定优先交付的功能

如果你曾在任何公司工作过——无论是初创公司还是大型科技公司——你都会发现这种情况很熟悉:待办事项(backlog)永远做不完。产品团队能够设想的功能几乎没有上限。如果你还没在大公司工作过,backlog 本质上是一份功能和创意的清单,等待开发团队进行评估、优化和开发。

为什么 backlog 总是会变得如此庞大?

首先,作为有创造力的人类,如果有足够时间,你总能不断回答关于客户不同生活场景的三个问题:

  • 这种情况是个问题吗?
  • 如果在这种情况下做 X,会怎样?
  • 在这种情况下还能做些什么?

这种持续的创造力保证了我们总能发现新的待解决问题、新的用例和可能有益的新功能场景。随着 backlog 的不断扩大,关键问题自然成为:如何选择最先交付的功能?

关于无用功能的重要警告

许多公司在选择最有价值的功能时苦苦挣扎。随着潜在功能数量增加,选择最有用功能的可能性直接取决于产品团队评估和比较不同功能创意价值的能力。

随着敏捷方法的广泛采用,我注意到产品领域中出现了一种“新功能综合症”。这种综合症的产生,是因为敏捷方法强调快速发布新功能,这促使团队不断创造新功能,有时甚至是“只是为了创新”。换句话说,由于团队嵌入在优先频繁发布的流程中,他们会不断产出新功能——即使这些功能可能并不特别有用。

这种情况常常发生,因为很多团队缺乏批判性思维能力,无法判断不同功能对市场的实际价值。结果是,各种市场的产品每天都被加入大量无关功能。

现在我们已经探讨了一些重要问题,让我们回答另一个重要问题:我们应优先考虑哪条业务需求?

优先考虑业务需求的标准

作为与产品团队合作的开发者,我经常讨论被要求开发的功能的相关性。我使用几个关键标准和问题来评估某次发布对市场的价值。我发现,仅仅在产品会议中提出这些问题,就会促使产品团队重新考虑计划发布功能的可行性和优先级。这类讨论对于确保每个功能都能真正增加价值并符合市场需求至关重要。

1. 与愿景对齐

作为开发者,你需要理解公司的愿景:公司希望给世界带来什么?希望在市场中产生什么影响?在此背景下,你被要求开发的功能是否实现了这个承诺?或者该功能对客户是否无关紧要?

我观察到很多公司会出现以下情况:

  • 产品战略频繁变化,通常是由于关键管理人员离职或加入公司带来不同想法。
  • 因预算使用压力而匆忙交付项目,这通常源于担心资金未使用会带来负面影响。
  • 追逐市场趋势却缺乏明确战略,仅仅因为某功能或产品在市场上流行而开发它。

这些行为可能会分散资源,使真正创新或必要的项目受到影响,从而开发出可能对公司或客户没有真正价值的功能。

开发新功能时,最相关的因素是它将解决的具体问题。因此,有必要与产品团队进行深入讨论。如果提议的功能不能解决明确问题或与公司长期愿景不符,就必须提出担忧。明确指出该功能可能不适合发布,或者至少需要优化,直到与公司长期目标明确对齐。这样可以确保每次开发都能对公司目标和客户需求产生实质性贡献。

2. 市场规模

评估潜在功能时,必须衡量该功能可能覆盖或吸引的市场规模。即便问题高度相关且符合公司愿景,其对客户群体的影响也是重要考量。

  • 能解决普遍问题的功能自然会吸引更多用户。
  • 如果功能针对的用例并非普遍需要,则其对整体市场的贡献有限。
  • 若有可能调整功能以吸引更广泛用户群,建议尝试这种优化,以最大化功能的市场潜力和公司价值。

一个重要例外:如果公司认为针对小众、专业市场仍能产生可观收入,那么整体市场规模的重要性就降低。这种策略有助于公司在市场中占据利基位置,并可能获得忠实用户群。

3. 问题出现频率

决定是否开发功能的另一个关键标准是:客户遇到该问题的频率。分析问题出现的频率——每天、每周、每月或每年——判断客户是否有足够机会频繁使用产品,或功能仅为一次性解决方案。

产品应尽量融入用户的日常行为中。如果功能能促使客户更频繁使用产品,则是强候选功能。

4. 问题严重程度

评估新功能潜力时,需考虑它解决问题的严重程度:

  • 问题是否足够重要,能激励用户使用功能?
  • 问题是否迫切,功能的存在是否能驱动用户使用产品?

高强度或明显痛点的问题,更容易促使用户采取行动。有效缓解这些问题的功能,可以增强用户依赖和忠诚度。

5. 市场购买力

评估目标客户是否有能力购买产品至关重要。许多初创公司开发产品面向不太可能支付高额费用的客户群。确保目标客户不仅有需求,而且愿意为解决方案付费。

关键问题包括:

  • 客户是否已经为解决该问题花费金钱?
  • 他们是否主动寻找解决方案?
  • 是否有证据显示客户愿意为此投资?

6. 交付成本

开发功能的时间、金钱及资源成本直接影响其可行性。考虑开发成本时,需要问:

  • 开发成本是多少?
  • 有无降低成本的方法?
  • 团队是否有足够时间、技能、技术和人力完成项目?

若成本过高,可能需要重新评估或延迟开发。

7. 投资回报估算

评估功能开发是否值得投入资源。新功能预计收入必须足够显著,以配合公司规模和投入资源。例如,年收入 4000 万美元的公司,如果某功能前几年仅带来 10 万美元收入,可能就不值得投入。

8. 获取成本

产品可扩展性不仅取决于质量,还取决于营销策略的成本效益。考虑:

  • 获得一个客户的成本是多少?
  • 功能是否提高产品吸引力?
  • 功能能否提升转化率,降低获取成本?

9. 交付影响

评估功能的实际影响:

  • 功能能为客户带来多大变化?
  • 是完全解决问题,还是仅部分缓解?
  • 效果能持续多久?

10. 依赖关系映射

考虑软件是否已有基础,能让用户轻松受益于新功能。通过依赖关系映射,确保功能引入时机合适且有效。

11. 隐性问题

有些问题用户未意识到,但会影响功能采纳与有效性。开发新功能时需提出问题:

  • 有多少潜在用户意识到他们面临的问题?
  • 我们能多容易向用户展示解决方案必要性?
  • 我们有哪些策略让用户认识到问题?

12. 用户体验影响

过多功能可能导致客户体验下降。需确保新功能易于理解并与现有产品无缝集成,不影响整体体验。

关键问题包括:

  • 添加此功能会提升整体客户体验吗?
  • 用户能注意到新功能吗?是否与其他功能和用户旅程整合良好?
  • 功能会不会让用户困惑?
  • 功能是否与现有功能冲突?

同时,应收集使用数据,以确保随功能增加用户体验不受影响。

13. 独特性

模仿竞争对手功能是常见策略,但可能导致市场功能雷同,抑制创新。确保功能难以被免费工具复制,可通过提供更优功能、体验或独特服务实现差异化。

以上标准可帮助开发者与产品团队一起,筛选出既有价值又可行的优先功能,从而最大化产品对客户和市场的影响。

排序优先级

上一节中讨论的是我过去用来帮助团队优先排序产品需求的最有效标准。把这些标准整理成易记的形式非常有用,你可以直接应用。通过多年经验,我才意识到在选择开发哪个功能时,这些问题确实非常关键。

那么,如何应用这些标准呢?没有放之四海而皆准的方法。每个项目可能面临不同挑战,有些标准在特定情况下比其他标准更为重要。然而,一个通用方法是:确保产品在每个标准上的得分尽可能高,从而帮助你识别最值得开发的功能。

如果你面对一长串潜在功能,可以尝试按照我们列出的每个标准对每个功能进行评分,然后按照总分从高到低排列。这样,你就能大致判断哪些功能对客户最重要。

HomeIt 功能对比与优先级示例

接下来,我们将以 HomeIt 初创公司的主要功能为例,理解如何根据各标准对功能进行评分。你可能会疑惑“得分”是什么意思,但别担心,随着示例,你会理解。我们将使用前面章节提到的标准。请注意,这些解释带有主观性,可供讨论参考。这只是评分背后思路的示例:

房产搜索(Property Search)

  • 评分:愿景 Vision: 10, 市场规模 Market Size: 10, 问题频率 Problem Frequency: 10, 市场购买力 Market Purchase Power: 10, 支付意愿 Willingness: 10, 预估成本 Estimated Cost: 10, 预估 ROI Estimated ROI: 10, 获取成本 Cost of Acquisition: 10, 交付影响 Impact of Delivery: 10, 依赖关系 Mapping Dependencies: 10, 隐性问题 Invisible Problems: 10, 用户体验 UX Impact: 10, 独特性 Uniqueness: 1,总分 121。

房产搜索是系统核心,因为每个租户都会使用它,本身就是在寻找新房产。唯一得分低的标准是“独特性”。即便如此,我们仍可设想房产搜索可以以独特方式运作,从而超越竞争对手。

中介合作(Realtor Partnership)

  • 评分:Vision: 10, Market Size: 10, Problem Frequency: 3, Market Purchase Power: 10, Willingness: 5, Estimated Cost: 5, Estimated ROI: 8, Cost of Acquisition: 10, Impact of Delivery: 6, Mapping Dependencies: 4, Invisible Problems: 4, UX Impact: 6, Uniqueness: 10,总分 91。

中介合作的整体评分较低。很多租户和房东其实不关心中介,因为他们可以直接沟通(导致“问题频率”低)。同时,有些人不愿意通过中介交易,降低了“支付意愿”。引入中介可以提高系统可扩展性(房东不方便时,中介可代为操作),提升 ROI 至 8。然而,很多人不理解中介作用,降低“隐性问题”评分至 4。同时,由于引入中介需要系统已有基础,依赖关系评分也降至 4。

消息功能(Messaging)

  • 评分:Vision: 10, Market Size: 10, Problem Frequency: 10, Market Purchase Power: 10, Willingness: 10, Estimated Cost: 10, Estimated ROI: 10, Cost of Acquisition: 10, Impact of Delivery: 10, Mapping Dependencies: 7, Invisible Problems: 10, UX Impact: 10, Uniqueness: 1,总分 118。

每个人都需要沟通,所以消息功能在几乎所有标准上都得高分,除了“独特性”和“依赖关系”。即便如此,我们仍可思考在 HomeIt 系统中如何以独特方式实现消息功能。

功能优先级路线图

根据示例评分,按总分排序得到路线图:

  1. 房产搜索:121
  2. 消息功能:118
  3. 中介合作:91

显然,搜索功能必须优先,因为所有功能都依赖它。采用这种方法,即使某些功能暂时不在开发范围内,也能做出合理取舍。看到评分后,可以自问:HomeIt 是否真的需要中介功能?

通过按评分排序的功能列表,团队可以为未来几个月甚至几年进行规划。一个好做法是按季度选择开发功能,规划季度里程碑,有助于向所有利益相关者清晰传达产品方向。

在功能优先级和里程碑路线图确定后,我们可以将注意力转向软件建模。至此,我们正式完成了业务需求的处理、整理、扩展和探索。你现在拥有我喜欢使用的最佳工具,帮助产品团队决定哪些功能应开发,以及如何以简化开发者工作的方式描述这些功能。

接下来,让我们进入软件建模的世界。我相信你对接下来的内容一定很期待!

为你的应用定义领域与边界

一旦你决定了下一个要开发的功能,就该识别构成实际系统的构建块了。这就是我们开始揭示软件建模过程中关键组件的地方。

在本节中,我将演示如何识别需要编码的关键对象,以及如何确定你将要实现的各种服务。

什么是产品领域及其重要性

领域(Domain)可以被视为你在软件中需要表达的专业业务知识。换句话说,无论你使用 Spring 还是其他工具或技术栈构建系统,都应以最佳方式体现你能接触到的顶尖专家在该产品领域的专业能力。

在软件中,一个表达良好的领域应包含两个不可或缺的要素:

  1. 设计良好的实体(Entities) :一组精心打磨的对象,准确反映业务和现实世界中的概念或事物。
  2. 设计良好的服务(Services) :通过这些服务,对实体进行处理——创建、经历不同生命周期阶段,以及执行满足业务需求的操作。

注意
提供优质软件的基础是实体和服务的良好建模。实现这一目标的前提是拥有编写良好的业务需求。这也凸显了前两章中讨论的工具和技术的重要性。即使是市场上最优秀的公司,也可能在其预期实现的需求中存在重大差距。你需要合适的工具来彻底分析这些需求,确保能够以最佳方式表达,从而简化实体和服务的提取。

本节总结了与领域建模相关的关键步骤。当然,也有专门书籍深入讲解这一主题。我推荐的基础书籍是 Eric Evans 的《Domain-Driven Design – Tackling Complexity in The Heart of Software》。

接下来,让我们看一下识别和实现所需实体与服务的步骤。

识别通用概念并消除用例冗余

在领域建模过程中,第一步是识别在不同用例中频繁出现的名词或实质性术语。这些关键术语将构成构建软件的核心词汇。

以 HomeIt 系统为例,一些需要通过领域建模表达的关键概念包括:

  • 用户、租户、房东和中介
  • 租赁房产、租赁提案、租赁协议、合作提案
  • 支付
  • 声誉

衡量概念重要性的一种有效方法是观察该术语在不同功能请求中出现的频率。出现频率越高,说明其在系统功能中的重要性和核心地位越高。

在建模过程中,你可能会遇到不同术语交替使用以描述同一概念的情况。例如,在 HomeIt 系统中,“landowners(土地所有者)”、“real estate owners(房产所有者)”和“proprietors(业主)”都可能表示我们所说的房东。

作为开发者,你的职责之一是识别这些同义词,并与产品团队一起标准化术语。例如,可以统一使用 “landlords”,所有出现 “proprietors” 的地方改为 “landlords”,以保持术语一致性。

相反,也可能出现一个名词指代多个不同概念的情况。例如,“user” 在不同用例和功能请求中可能指房东、中介或租户。遇到这种歧义时,需要回顾文档,确定具体上下文,并为每种情况指定更精确的术语。这有助于消除混淆,确保每个术语准确反映其在系统中的含义。

为产品设置和定义领域

如果你有效地消除了需求文档中的歧义和冗余,识别软件所需的实际领域就变得简单了。基本上,只需列出产品需求中最常出现的名词即可。

此时,请以非常简单的方式理解领域:将每个名词或概念视为一个独立领域。在 HomeIt 系统中,我们之前提到的每个关键字都代表一个不同领域。例如,我们会有租户领域(Tenants Domain)、房东领域(Landlords Domain)、租赁提案领域(Rental Proposal Domain)等。这种方法简化了领域建模的初步步骤,并为后续开发奠定了清晰基础。

设定领域组成、边界和限制

识别了各个领域后,关键是考虑每个领域的边界和组成部分。这个阶段更偏向哲学讨论而非技术,可能会引发广泛讨论,因为不同团队成员对模型结构可能有不同看法。

利用你的批判性思维,需要定义每个领域的实际职责。这些职责应基于产品团队使用的语言及其描述用例的方式。

在上一章中,我们使用工具深入分析需求,为流程设计输入和输出。这些输入输出以及概念间的关系将帮助我们了解每个领域的组成。此步骤对于建立清晰的领域边界至关重要,并确保每个领域在系统中有效履行其角色。

以 HomeIt 系统为例:

  • 一个租赁房产包含关键信息,如媒体(图片、视频)、地址、房间数量、面积等。
  • 租户可能有一个或多个支付信息对象,以适应多种支付方式。

理解领域组成将引导我们明确接下来的方向。以下四组关键问题可帮助界定领域边界:

  1. 我在定义哪个领域/概念?该领域的用途是什么?
  2. 该领域包含哪些信息?由哪些信息组成?
  3. 该领域不包括哪些内容?哪些应排除,因为它属于其他领域或为了设定边界?
  4. 该领域与哪些其他领域相关?这些关系如何表达或说明?

示例:HomeIt 中的中介领域(Realtors Domain)

  1. 定义的领域/概念是什么?用途?

    • 中介(Realtors)是促进房产租赁、收取佣金的用户。该领域用于管理与租赁相关的交互,搭建租户与房东之间的沟通桥梁。
  2. 该领域包含哪些内容?

    • 中介应有个人联系信息以便沟通,并维护其管理的租赁提案列表。
    • 中介应与每个用户(租户、房东)保持独立的沟通渠道,以保证交流清晰且私密。
  3. 该领域不包括哪些内容?

    • 中介不会直接拥有可用性日历,该日历与每份租赁协议绑定。但中介可通过管理协议访问相关日历,也可汇总多个协议生成综合日历。这样可避免中介领域杂乱,并保持其核心职责。
  4. 该领域与哪些其他领域相关?如何表达?

    • 中介与租户、房东、合作协议及租赁房产领域相关:

      • 与租户沟通租赁机会和解决问题
      • 与房东签订合作协议,授权管理房产并代表房东操作
      • 通过管理租赁提案和协议与租赁房产相关

通过这种方式,我们可以提出并优化领域设计,这在团队讨论中经常占据重要位置。随着讨论推进,理解更加清晰,有助于团队自信地记录领域及其相互关系。

建模完成后,一些领域可能会作为独立微服务实现,另一些可能合并到同一微服务中,或仅作为其他领域的属性存在。这种结构化方法让我们更接近实际编码阶段。

在进入下一节前,请思考 HomeIt 系统中的领域。除了上述领域,你还会添加哪三个领域?如何回答这些新领域的关键问题?思考完后,再继续下一节,我们将进一步优化每个领域的服务。这一练习有助于加深你对领域驱动设计(DDD)基础的理解,为后续实践步骤做好准备。

为你的领域定义合适的服务

太好了!我们现在已经掌握了如何识别系统中的关键领域。基本方法是仔细分析需求描述中的名词,并理解这些名词之间的关系。

建议你和团队一起进行真实系统分析,就像福尔摩斯一样。目标是梳理需求文档,将其提炼为一组核心词汇,这些词汇能够有效代表整个系统。

识别出不同领域后,下一步是了解这些领域的行为。这涉及识别领域执行的动词或操作。这是一个关键阶段,我们开始发现与每个领域相关的操作或功能——本质上就是关键服务。理解这些服务非常重要,因为它们是系统功能和交互的核心。

什么是服务?为什么要关注服务

在本章中,我们将服务定义为可以在特定领域内执行的任何操作。例如,在 HomeIt 系统中,“合作提案(Partnership Proposal)”是一个领域。在该领域内可以进行各种操作,如创建、发送、拒绝或接受提案。每个操作都被视为合作提案领域内的服务。

理解这些服务至关重要。忽视这些具体领域操作可能导致开发者出现重大盲点。我的一位同事常说:“编程的奇迹可以让一切成为可能。”然而,如果我们不清楚领域应支持的操作,这些“奇迹”就会失去奇迹般的效果。

自 RESTful API 标准出现以来,许多开发者致力于设计“完美 API”。后续章节会详细讨论如何构建结构良好的 API。但需要注意的是,许多开发者容易陷入只表示基础对象和资源的误区。这里的“基础”指最直观的对象,如产品、客户、购物车等。他们往往过度关注在 URL 中映射关键领域资源,而忽略了操作和动作在 API 中的表达。这会导致额外逻辑被加入到基础资源上,随着时间推移,代码会变得混乱。

缺乏表达力的 API 随着时间推移无法充分体现系统的动作,可能导致:

  • 代码混乱
  • 参数过多
  • 类臃肿
  • 少量端点承担过多职责

忽视服务设计经常导致所谓的“意大利面条代码(spaghetti code)”。就像吃意大利面一样,即使有叉子,也很难控制和操作食物。类似地,意大利面条代码因线条缠绕、多个职责集中在少数端点和方法中而难以管理,使得理解和维护代码几乎不可能。

相反,通过仔细识别和列出领域应表达的关键操作,你可以将这些动作更容易地划分到代码的不同部分。这种分离提高了隔离性,使代码更易于管理。随着我们在后续章节编写服务,为每个领域建模关键操作的优势将变得显而易见。当前章节的重点是理解如何有效记录不同服务。

如何正确建模和记录领域服务

在软件中对领域进行优质服务建模时,以下四个步骤至关重要:

  1. 基本操作(Basic actions)

    • 定义领域将支持的基本操作,通常称为 CRUD(Create、Read、Update、Delete)操作。这些操作构成领域内交互的骨干。
  2. 特殊操作(Special actions)

    • 识别并记录领域需要支持的特殊操作,超出基本 CRUD 操作。这些可能涉及业务逻辑特有的操作或领域独特功能。
  3. 操作拆分为新领域(Actions to new domains)

    • 评估领域内的操作是否可以作为新的独立领域进行服务。这有助于进一步模块化架构,提升可扩展性和可维护性。
  4. 输入、过程、业务规则和输出描述

    • 对每个服务详细描述所需输入、执行过程、适用业务规则以及期望输出。此文档确保服务实现与集成的清晰性和一致性。

在下一节中,我们将通过 HomeIt 系统的“合作提案服务(Partnership Proposal)”练习服务建模。

合作提案服务建模示例

为了有效建模 HomeIt 系统中合作提案领域的服务,可按以下步骤操作:

1. 基本操作

  • 创建(Create)

    • 输入:租赁房产 ID、中介 ID、由中介撰写的介绍信
    • 输出:新创建的合作提案领域对象
  • 删除(Delete)

    • 系统实现软删除(标记删除),在前端不可见,但数据保留以保持一致性
    • 输入:合作提案 ID
  • 读取(Read)

    • 输入:合作提案 ID
    • 输出:与该 ID 关联的所有详细信息
  • 更新(Update)

    • 仅允许更新合作提案状态,如从“开放”更新为“接受”或“拒绝”
    • 输入:新状态及合作提案 ID

2. 特殊操作

  • 发送合作提案:创建时自动发送,因此整合在创建流程中
  • 消息服务:如需在合作提案上下文中实现中介与房东的即时沟通,需要实现消息服务

3. 拆分为新领域

  • 消息作为新领域:将消息定义为合作提案的新领域对象,每条消息属于特定提案的消息列表
  • 对消息执行基本 CRUD 操作,反映双方交互
  • 消息作为合作提案领域的子对象存储

通过这种方法,合作提案领域职责清晰,并为可能的扩展(如消息服务)提供了可行方案。此方法不仅简化系统维护,还提升功能和用户体验。

本节展示了如何识别领域的关键服务及如何正确建模。通过结构化定义操作和考虑新领域,可以确保系统架构稳健、可扩展且易管理,同时为未来的功能增强和维护提供明确路径。这符合领域驱动设计(DDD)的基本原则,即优先在模型中反映真实业务对象。

接下来,我们将开始布局系统领域组件,以及不同领域随时间的交互方式。

绘制你的领域图

事情是这样的:如果你在处理多个领域,并且发现它们数量很多、跟踪起来开始变得困难,那么通过可视化方式进行映射会容易得多。正如我们在第一章讨论的原因,视觉化表示可以加快每个人对领域的理解。

绘制领域图的过程非常简单。你只需画出简单的方框,每个领域指向它与之存在某种关系的其他领域。制作这种可视化图有多种方式,这里我仅提供一个示例,展示我们迄今讨论过的 HomeIt 系统中的多个领域。图 3.1 使用了我之前推荐的免费 draw.io 应用创建。

image.png

正如你所看到的,我在箭头上添加了动词,表示每个领域都是在与其他领域的关系中发挥作用。这个领域图帮助我们识别系统中的关键流程,并提供了对我们想要构建内容的整体视图。而且,如你所见,我们可以更容易地在一页上概念化整个系统。

从这个领域设计中,我们可以提取微服务、API 合约、类、属性、流程以及许多其他组件,而这些都将由我们的 Spring Framework 来实现。

注意:我在图中绘制的虚线箭头是为了更容易理解“租赁提案(Rental Proposal)”与“租赁物业(Rental Property)”之间的关系。

我还希望你注意另一点:Counteroffer(反报价)领域是通过探索在租赁提案流程中允许租户和房东协商租金价格的可能性而提取出来的。这个领域实际上代表了我们可以提供的一项重要服务,以便促进价格谈判,而价格谈判是现实世界商业活动中的关键部分。此外,这个价格谈判功能还有助于在合同最终确定后追踪付款金额。

在这个 HomeIt 领域图示例中,我省略了前几章讨论过的几个领域和用例,例如调解(Mediation)功能。我这样做是为了简化图示,使我们有一个更简单的示例供参考,同时留出空间让你练习自己的技能,以加深理解。

在进入下一章之前,我希望挑战你去表示我有意省略的其他领域。你会如何为这些额外功能设计领域图?

在你考虑好自己的 HomeIt 或任何其他系统的领域图之后,我们将继续学习如何跨领域和服务对活动进行时间序列建模,也就是使用时序图(Sequence Diagram)。

跨服务活动的时序

在深入分析需求后,我们现在理解了系统的整体结构。我们刚刚构建的领域图可以作为微服务建模的基础。然而,一个关键且及时的问题仍然存在:

领域之间如何随时间互动?此外,我们如何以最简单的方式表达这些随时间的交互,让所有团队都清楚要构建什么?

租赁提案(Rental Proposal)时序图

为了解决这些问题,我们引入时序图。在我参与的各种编程项目中,我发现这种图在解释系统流程如何工作——特别是信息如何从一个服务传递到另一个服务——以及让团队高效协作方面,效果非常显著。

请参见图 3.2 进行说明:

image.png

这个时序图展示了 HomeIt 核心系统的运行方式。值得注意的是,图的两侧展示了不同的参与者:左侧是租户,右侧则是房东和房产经纪人(Realtor)。

请注意,这些参与者之间的交互并不涉及直接的个人沟通。箭头仅指向各个领域,表明交互是通过系统的结构化领域进行的。这种设计确保了领域能够促进并反映现实场景。例如,租户搜索物业、与房东协商报价以及通过银行账户处理付款,这些都是自然的流程。系统的设置使交互清晰有序,反映了房地产交易中的实际步骤。

在图中,我重点标出了三个关键领域:Rental Properties(租赁物业)、Rental Proposal(租赁提案)和 Payments(支付)。这些领域有效地展示了系统中的搜索、谈判和支付流程。

每个时序图都按照动作发生的顺序进行结构化,时间从上到下沿箭头方向推进。这种设置使得服务间消息交换非常容易理解。箭头总是从发送请求的系统指向接收请求的系统,从而明确通信方向。

虽然我们可以讨论这些领域的创建以及图中组件的具体顺序,但需要注意的是,不同的开发者可能会根据自身对流程的理解选择最适合的布局来绘制图表。这种灵活性是正常且预期的,它允许开发者根据自己的需求和理解调整图表。

在此示例中,我隐式地表示系统内的通知在各用户之间来回交换。观察这个时序图的结构可以发现,通知服务是关键的,并被不同系统使用。因此,合理的做法是考虑将 Notification(通知)作为一个新领域引入。这将需要对领域图和时序图进行一些修改。然而,为了节省篇幅,并且因为在书页上展示完整的时序图会显得过于复杂,我将邀请你自行进行修改:你可以重新设计这些图,将通知服务纳入其中。

在这个示例图中,很多细节被省略。例如,支付系统如何确定金额,或者支付系统如何与银行接口执行交易,这些都未显示。这提出了一个重要问题:时序图中应包含多少细节?答案是,细节的多少取决于是否能有效地与查看该时序图的相关利益方进行沟通。细节层次取决于你正在处理的具体功能以及图表的受众。

现在我们理解了什么是时序图,接下来让我们学习如何轻松创建一个时序图。

使用 PlantUML 构建时序图

使用 PlantUML 创建时序图是可视化系统内部交互的强大方式。PlantUML 多年来一直是市场上受欢迎的工具,以其简单性和高效性在复杂系统建模中表现出色。

要构建类似的图表,可以使用 plantuml.com 提供的免费在线应用。该工具允许你通过编写简单脚本快速生成时序图(以及其他类型的图)。作为示例,你可以参考我用来渲染前面讨论的时序图的脚本:示例脚本链接

要重新创建前面讨论的时序图,你只需将提供的代码复制并粘贴到 plantuml.com 的文本区域中,在线工具会根据你的脚本生成对应的时序图。

PlantUML 还可以绘制其他类型的图表,但我在此主要用于绘制时序图。互联网上有大量教程,教你如何探索这个强大且免费的工具的更多功能。

总结

本章我们学习了如何有效地对功能进行优先级排序,从而制定产品路线图;以及如何识别和表达软件所需的领域,理解和定义这些领域至关重要,它们起到了将健全需求与成功开发软件桥接的作用。一位有见识的工程师曾强调,在软件开发中花时间讨论命名规范非常重要,因为这些名称从根本上决定了功能和系统的最终结果。恰当的命名能够显著促进软件构建的成功。

此外,我们还解决了跨领域活动的时序问题,为用例建立完整流程。这种时序分析让我们更清楚地了解哪些系统和服务需要实现,从而优化理解和规划过程。

在进入下一章之前,我建议你实践本章讨论的概念。你可以将这些策略应用到 HomeIt 系统的某些用例中,或者尝试对你当前系统的功能优先级、领域识别和时序图建模进行练习。

在彻底分析了业务需求后,我希望你已经准备好进入下一章,在那里我将介绍如何整体选择技术和 Spring 项目来构建软件,以满足特定需求。这将涉及非功能性需求(NFR)的探索,以及它们如何影响技术选择和系统架构设计。