DDD之限界上下文(Bounded Context)和通用语言(Ubiquitous Language)

领域驱动设计系列文章,点击上方合集↑

1. 上下文

上下文(Context)指在某个时间、地点或场合下,事物所处的背景和环境等相关信息。上下文可以提供额外信息,帮助我们更加深入地理解某个事物或事件和人言行背后的意图。

假设你正在与朋友交谈,在对话中提到了"他"这个词,没有明确指出是指谁。在这种情况下,我们需要根据对话的上下文来理解"他"指的是谁。

2. 生活中的例子

你是一名学生,你有学校、家庭和社交圈这三个不同的上下文。在学校这个上下文中,你与老师和同学们一起学习、参加考试。在家庭这个上下文中,你与家人们共同生活,遵循家庭规则和价值观。在社交圈这个上下文中,你与朋友们相处,参加各种活动。

每个上下文都有自己的规则、角色和活动。在学校上下文中,你需要遵守学校的纪律,按照课程表上的安排学习不同的学科。在家庭上下文中,你可能有家务事要做,需要与家人共同协作来完成日常任务。在社交圈上下文中,你可能需要遵循社交礼仪和相应的行为准则,在朋友之间建立互信和良好的关系。

学校  
├── 学习  
│   ├── 学科  
│   │   └── 课程表  
│   └── 考试  
├── 遵守纪律  
│   └── 学校规则  
└── 与老师和同学相处  
    ├── 学习合作  
    └── 互相帮助  
        └── 解答问题  
家庭  
├── 与家人共同生活  
│   └── 遵循家庭规则和价值观  
├── 做家务  
│   └── 与家人分担任务  
└── 与家人的协作  
    └── 完成任务  
社交圈  
├── 与朋友相处  
│   └── 建立互信和良好关系  
├── 参加活动  
│   ├── 活动安排  
│   └── 活动类型  
├── 遵循社交礼仪和行为准则  
└── 与朋友共同参与活动

3. 限界上下文

限界上下文(Bounded Context)是一个用来界定领域模型的显性边界,它定义了一个特定的领域或子域内部的语言、规则和概念。在限界上下文内,领域模型的含义和一致性是有效的,而在不同的限界上下文之间,可以有不同的语言和规则。限界上下文帮助了解系统中不同组成部分的含义,并促使团队更好地进行沟通和协作。

换句话说,限界上下文定义了一个明确的边界,将一个复杂的领域划分成多个相对自治的部分。每个限界上下文都有自己的领域模型和语言,用于描述和解决特定上下文中的业务问题。每个上下文都可以独立地进行开发和演化,同时需要与其他上下文进行良好的集成和协作。

限界上下文的划分是为了更好地应对领域的复杂性,使团队能够更加专注和理解特定领域内的业务。通过限界上下文的定义,不同团队成员可以在各自的上下文中使用一致的语言和概念,从而提高开发效率和系统的可维护性。

以电商系统为例,我们可以根据不同业务领域划分多个限界上下文:

  1. 商品上下文(Product Context):管理商品的发布、分类、库存、价格等信息。包含商品上下架、库存管理、价格策略等功能。

  2. 订单上下文(Order Context):处理订单的创建、支付、发货、退换货等逻辑。包括购物车、订单详情、物流配送、退款流程等功能。

  3. 用户上下文(User Context):处理用户的注册、登录、个人信息管理等操作。包括用户账号、个人资料、地址管理等功能。

  4. 营销上下文(Marketing Context):管理营销活动的配置和执行。包括促销活动、优惠券、折扣策略等功能,用于吸引和留住用户。

  5. 仓储物流上下文(Warehouse and Logistics Context):处理库存管理、仓储物流、配送等操作。包括商品入库、出库、库存调配等流程。

  6. 支付上下文(Payment Context):处理支付操作和接入不同支付渠道。包括支付方式选择、支付接口集成、支付状态管理等功能。

  7. 售后服务上下文(Customer Service Context):负责处理客户的售后服务。包括投诉、退款、维修等操作,以提高客户满意度。

每个限界上下文都可以有自己的领域模型、聚合根、服务和数据库,每个限界上下文也可以有自己的团队负责独立开发和维护。

4. 限界上下文与微服务

在微服务中,限界上下文将成为服务的边界,每个服务都代表一个限界上下文,只需关注该领域的业务需求和职责。服务之间通过API和异步消息进行通信,通过限界上下文可以很好地管理和维护服务之间的依赖关系。

5. 通用语言

通用语言(Ubiquitous Language)是指在整个领域模型中共享的、一致的语言和术语。这种语言被所有团队成员(包括开发人员、领域专家和利益相关者)都理解和使用,以便更好地沟通、共享知识和解决业务问题。

假设电商系统项目组的成员在一起讨论问题:

  • 程序员A:“我们购买商家的产品”
  • 程序员B:“用户买商品”
  • 产品经理:“我们采购店家商品”
  • 测试人员:“用户购买物品”

以上大家其实要表达的意思都是一样的,但是不统一。

假设我们建立了通用语言,那么项目组成员在一起讨论的时候就会都这么说:“客户购买商家的商品”,不管是产品、商品、还是物品我们都叫“商品”,并且将主谓宾明确了!

“客户购买商家的商品”这句话不管懂不懂技术,大家都能理解,但是对于开发人员在后续的开发就会很容易并顺其自然的建立如下模型和实现如下代码:

  • 实体类:客户 Customer
  • 实体类:商家 Merchant
  • 实体类:商品 Product
  • 方法:购买商品 purchaseProduct
public void purchaseProduct(Customer customer, Merchant merchant, Product product) {
    // 执行购买商品的逻辑
    // ...
}

6. 通用语言与限界上下文

限界上下文定义了一个特定的领域范围,而通用语言是在这个上下文中被共享和理解的一种语言。每个限界上下文都有自己的通用语言,用于描述和解决特定上下文中的业务问题。比如“账户”这个概念在用户上下文、支付上下文是完全不一样的。

在限界上下文里才有通用语言。

举个例子:商品上下文中的商品和库存上下文中的商品,他们在各自的上下文都可以叫做商品(Product),但是意义不一样,我们从具体的数据库建模字段去理解:

商品上下文:

  • id: 商品唯一标识符
  • name: 商品名称
  • sku: 商品SKU编码
  • price: 商品价格
  • description: 商品描述
  • weight: 商品重量
  • category_id: 商品分类ID
  • brand_id: 品牌ID
  • created_at: 商品创建时间
  • updated_at: 商品最近更新时间
  • status: 商品状态(未上架、已上架、已下架等)

库存上下文中:

  • id: 库存项唯一标识符
  • product_id: 商品ID
  • quantity: 当前库存量
  • reserved_quantity: 当前已预留的库存量
  • last_stocked_at: 最近入库时间
  • last_reserved_at: 最近预留时间
  • last_sold_at: 最近售出时间
  • status: 库存状态(有库存、已售罄)

商品上下文的商品侧重于商品信息价格这些内容;而库存上下文中的商品关键在于库存项,包含有关特定商品的信息和当前库存水平等。甚至在两张数据库表有完全一样的字段status,但是表示的含义完全不一样。

商品和库存在各自的上下文中具有不同的属性和状态,并且可以根据具体的需求进行定制和扩展。这种分离使得各个团队成员能够更好地对特定的领域进行建模和理解。

商品上下文中的商品和库存上下文中的商品是不一样的,但是他们可以通过商品id进行映射关联。

7. 结语

限界上下文的界限划分和通用语言的规定确实需要一定的时间和精力进行,但它们对于项目的长期成功和可维护性非常重要,因此这些投入是非常值得的。

在实践中,限界上下文的划分可以根据项目的初期情况和需求进行灵活调整。如果项目的紧迫性要求快速上线或项目团队人手有限,那么最初的上下文划分可能不会非常清晰和细致,比如:商品信息和库存信息可能存储在同一个表product中。然而,这只是一个暂时的妥协,随着项目的演化和成熟,我们可以随时进行上下文的重新划分和优化。

关键是,在项目开始之初,建立一个共同的理解和协作机制,明确限界上下文和共享的通用语言。限界上下文的划分应该基于业务领域的特点和不同业务能力的边界,以确保每个上下文都具有清晰而独特的职责。

通用语言的规定则是为了确保团队成员之间的沟通和理解的一致性。通过明确定义和共享领域特定的术语和概念,团队成员能够更好地理解业务需求,并在设计和开发中使用统一的语言。

随着项目的演化,团队应该不断地回顾和优化限界上下文的划分和通用语言的规定。这些优化应该基于实际的业务需求和系统状况,以确保系统的灵活性和扩展性。通过持续地关注和改进限界上下文和通用语言,团队能够更好地适应需求变化,并以更有效的方式设计和开发系统。


关注微信公众号:“小虎哥的技术博客”,让我们一起成为更优秀的程序员❤️!

你可能感兴趣的:(领域驱动设计,DDD,领域驱动设计,限界上下文,通用语言)