最近一直在进行公司整体项目的业务架构重构工作,已解决这几年整个系统复杂度指数上升问题,同时进行合理的业务架构落地以保证系统可以快速迭代。这里做一些整理和总结。

既然说到业务架构设计,先看下我们经常听到的的业务架构方法都有那些:

  1. 分层架构(Layered Architecture):将系统划分为不同的层,例如表示层(Presentation Layer)、业务逻辑层(Business Logic Layer)和数据访问层(Data Access Layer)。每一层都有其独立的责任和功能,通过定义清晰的接口实现层与层之间的解耦。
  2. 领域驱动设计(Domain-Driven Design,简称DDD):将系统设计和组织以核心领域模型为中心。通过分析和理解业务领域,将其抽象为领域模型,将模型作为设计和开发的核心,从而实现业务逻辑的灵活性和可扩展性。
  3. 微服务架构(Microservices Architecture):将系统拆分为一系列小型、自治的服务,每个服务专注于一个特定的业务功能。每个服务可以独立部署、扩展和升级,通过轻量级的通信机制实现服务之间的协作。
  4. 事件驱动架构(Event-Driven Architecture):基于事件的消息传递和处理机制,通过发布-订阅模式实现系统的松耦合。系统中的各个组件通过发布事件和订阅事件的方式进行通信,从而实现异步、可扩展和灵活的架构。
  5. 服务导向架构(Service-Oriented Architecture,简称SOA):将系统的功能划分为一系列可重用的服务,并通过服务接口进行通信和集成。每个服务代表一个特定的业务功能,通过松耦合的方式实现系统的灵活性和可扩展性。

其实真正在落地过程中,你会发现,基本上不会出现说你是用的就是其中某一种方法,本质都是针对不同场景,考虑各个方法论的使用集合。举个例子:

你使用微服务架构,就不会使用分层架构了吗?这两种架构理论,就是就是视角不同 – 横向和纵向视角:

  • 横向视角:每一层都有哪些服务类型,比如数据访问层:可以包括ADB的访问、MySQL的访问、MongoDB的访问等等;比如业务逻辑层,可以有订单服务、支付服务、会员服务等等
  • 纵向视角:也是上面的例子,分层分好了,每一层应该如何切分服务?

再说下领域驱动设计和微服务架构:他们在某些概念上存在一些重合。它们都是面向复杂业务系统的设计方法,旨在提高系统的可扩展性、可维护性和灵活性。尽管如此,它们还是有一些区别:

  • 领域驱动设计(DDD)是一种设计方法论,关注的是通过深入理解业务领域,将业务逻辑和领域模型融入系统设计中。DDD强调将核心业务模型(领域模型)置于系统的中心,通过建立丰富的领域模型和充分的业务上下文边界,来驱动系统的设计和开发。DDD强调团队之间的领域专家和开发人员之间的协作,以确保业务需求能够准确地反映在系统设计中。
  • 微服务架构是一种架构风格,强调将系统划分为一系列小型、自治的服务,每个服务专注于一个特定的业务功能。微服务架构追求松耦合和高内聚的服务边界,每个服务可以独立开发、部署、扩展和升级。微服务通过轻量级的通信机制进行交互,如使用RESTful API或消息队列。微服务架构可以更好地应对复杂系统的变化和扩展需求,同时也提供了更高的灵活性和可维护性。

尽管DDD和微服务架构存在一些重叠,但它们的关注点略有不同。DDD更加注重领域模型的建立和业务驱动的设计方法,而微服务架构更加注重系统的分解和服务边界的定义。在实践中,可以结合使用DDD和微服务架构来设计和开发复杂的业务系统,以实现更好的系统可扩展性和灵活性。

微服务架构最好的对比是单体架构,借用下 Martin Fowler 微服务的经典文章(参考: https://martinfowler.com/articles/microservices.html)

先说下为什么我们偏偏就要拆单体,转微服务?

  • 业务过于复杂:要处理一个细分行业从产品设计,到销售跟进,到客户使用,到客户服务、到供应商管理以及中间涉及的全部工作。再过去单体架构的执行下,会导致服务的耦合越来越重,基本上快发展到牵一发而动全身的情况了
  • 团队的发展:整个团队也进行了快速扩张,从之前10人左右的小团队,迅速扩张为了70人左右的一个中型团队,原有的开发模式依然不再试用。有个知识点就是著名定律 【康威定律】:

任何组织在设计一个系统(广义的定义)时,都会产生一个设计,其结构是该组织的通信结构的副本。

                                                                                                                                   *梅尔文-康威*

看下这个路径:

  • 因为业务复杂,单体耦合过于严重,系统难于快速响应业务需求
  • 业务需要系统快速发展支持业务链的有效运转,需要系统快速升级,带来团队快速扩张

以上两点,导致我们需要尽快转型微服务架构。这里要强调一点:

!!架构不是设计出来的,架构是演进出来的!!

从单体拆分微服务,在业务架构上最大的挑战是什么?我想,是服务边界的划分、是高内聚低耦合的实现。为了保证达到这个要求,我们自然而然的将视线转到了领域驱动设计(Domain-Driven Design,DDD)

终于到开始扣题了😂

为什么领域驱动设计和微服务架构比较搭呢?

它们可以相互搭配使用,互相增强系统的设计和开发过程。下面是领域驱动设计和微服务架构搭配使用的一些原因:

  1. 限界上下文的划分:领域驱动设计中的一个重要概念是限界上下文(Bounded Context),它将系统的领域模型划分为多个边界明确的上下文,每个上下文都负责特定的业务领域。微服务架构也是通过将系统拆分为多个独立的服务来提供解耦和自治性。限界上下文和微服务的划分方式相似,可以相互协调,实现领域驱动设计中的上下文边界和微服务架构中的服务边界一致性。
  2. 高内聚和低耦合:领域驱动设计强调在每个限界上下文内部实现高内聚的领域模型,而微服务架构鼓励通过拆分系统为独立的服务来实现低耦合。这两个概念的结合可以促使每个微服务专注于特定的业务领域,实现高内聚和松耦合的设计,从而提高系统的可维护性和可扩展性。
  3. 团队自治和快速交付:微服务架构的一个重要特点是允许团队独立开发、部署和扩展各自的微服务。而领域驱动设计强调将业务专家和开发团队紧密合作,培养团队对业务领域的深入理解。将领域驱动设计与微服务架构结合使用,可以促进团队自治和快速交付,每个团队可以独立开发和演进自己的限界上下文,提高开发效率和响应能力。
  4. 水平扩展和弹性:微服务架构支持水平扩展,可以根据负载情况和需求独立扩展各个微服务。领域驱动设计通过限界上下文的划分和领域模型的精细化设计,使得各个上下文可以独立扩展,从而实现系统的弹性和可伸缩性。

看上面说到的每一点,是不是都命中我们要解决的问题!

下面就从领域驱动设计的核心概念出发,我们一起看一下如何做吧。

什么是领域驱动设计

领域驱动设计(Domain-Driven Design,简称DDD)是一种软件设计和开发方法论,旨在通过深入理解和建模业务领域,将业务知识和业务逻辑贯穿于整个系统的设计和开发过程中。

官方定义:领域驱动设计是一种以复杂领域建模为核心的设计方法,它将软件系统的设计和实现与业务专家紧密结合,通过深入理解业务领域的核心概念和规则,来构建能够满足业务需求的高质量软件系统。

浅显易懂的解释:领域驱动设计是一种通过理解业务领域的核心概念和规则来设计软件系统的方法。它关注的是将真实世界的业务问题和解决方案映射到软件系统中。通过与业务专家紧密合作,我们可以共同理解业务需求并将其转化为可执行的代码。这种方法强调将业务领域的专业知识和业务规则反映在系统设计中,以确保系统能够准确地满足业务需求。

领域驱动设计的核心目标是构建一个紧密与业务领域对应的系统,使得业务逻辑更加清晰、可维护和扩展。通过将业务专家的知识与开发团队的技术能力结合起来,可以建立一个更符合实际业务需求的高质量软件系统。

领域驱动设计的核心概念

  1. 领域(Domain):指业务领域,包括业务的核心概念、规则和业务流程。领域是领域驱动设计的核心,我们需要深入理解业务领域,与业务专家进行合作,以便将领域知识融入系统设计中。
  2. 领域模型(Domain Model):领域模型是对业务领域的抽象和映射。它由领域对象(Domain Objects)和领域之间的关系组成,反映了业务概念、行为和规则。领域模型是领域驱动设计的核心成果之一,它帮助我们理解和描述业务需求,指导系统的设计和开发。
  3. 聚合(Aggregate):聚合是一组相关的领域对象的集合,它们一起形成一个有内聚性的单元。聚合定义了内部的一致性边界和一些约束规则,封装了领域对象的状态和行为。聚合是领域模型中的重要组织单位,通过聚合根(Aggregate Root)来访问和管理聚合内的对象。
  4. 实体(Entity):实体是具有唯一标识的领域对象,它有自己的生命周期和状态。实体通常具有可变的属性和行为,可以通过标识来唯一地区分和识别。
  5. 值对象(Value Object):值对象是没有唯一标识的领域对象,它们通过其属性来定义和区分。值对象通常是不可变的,它们的相等性通过属性的值来判断,而不是通过标识。
  6. 聚合根(Aggregate Root):聚合根是聚合中的一个特殊实体,作为聚合的入口点。它负责保护聚合内的一致性和约束规则,并与外部系统进行交互。通过聚合根,我们可以管理聚合内的对象,保持聚合的完整性。
  7. 领域事件(Domain Event):领域事件表示在业务领域中发生的有意义的事情。它们可以用来记录和通知系统中其他部分的状态变化,促进领域模型的协作和反应。领域事件有助于解耦和异步处理领域逻辑。
  8. 限界上下文(Bounded Context):限界上下文是领域驱动设计中的一个关键概念,它定义了在系统中的一个特定领域范围内的边界。每个限界上下文都有自己的领域模型、语言和规则。限界上下文之间通过明确定义的接口进行交互,帮助团队在大型系统中分解和组织领域模型。

是不是都太抽象了,不好理解,下面以电子商务平台为例,结合领域驱动设计的核心概念,来说明每个概念在实际业务场景中的应用:

  1. 领域(Domain):电子商务领域包括商品、订单、用户等核心概念。每个概念都有自己的属性和行为,以及与其他概念之间的关系。
  2. 领域模型(Domain Model):我们可以定义一个订单(Order)的领域模型。订单模型可以包括订单编号、下单时间、支付状态等属性,以及添加商品到订单、计算订单总额等行为。
  3. 聚合(Aggregate):在电子商务平台中,订单(Order)可以作为一个聚合,它可以包含多个相关的领域对象,如订单项(OrderItem)、收货地址(ShippingAddress)等。订单聚合通过聚合根(Order)来管理和维护其内部对象的一致性。
  4. 实体(Entity):在订单聚合中,订单项(OrderItem)可以作为一个实体。每个订单项都有唯一的标识(如商品ID),并具有自己的属性(如商品数量、单价)和行为(如计算小计金额)。
  5. 值对象(Value Object):收货地址(ShippingAddress)可以作为一个值对象。它没有唯一的标识,而是通过其属性(如姓名、地址、邮编)来定义和区分。
  6. 聚合根(Aggregate Root):订单(Order)作为聚合根,负责保护聚合内的一致性和约束规则。它提供了管理订单项、更新订单状态等行为,以及与用户、支付等外部系统进行交互的接口。
  7. 领域事件(Domain Event):在电子商务平台中,可以定义一个订单已创建的领域事件(OrderCreatedEvent)。当订单成功创建后,系统可以发布这个事件,其他部分可以订阅该事件并作出相应的反应,如发送确认邮件、更新库存等。
  8. 限界上下文(Bounded Context):在大型电子商务系统中,可以根据不同的子领域划分多个限界上下文,如商品管理上下文、订单管理上下文。每个上下文都有自己的领域模型和语言,通过明确定义的接口来交互和集成。

通过以上实际业务场景的说明,我们可以看到领域驱动设计的核心概念如何应用于电子商务平台。这些概念帮助我们理解业务需求,建立领域模型,并通过聚合、实体、值对象等概念来组织和管理业务逻辑。限界上下文则帮助我们将系统分解为多个独立的领域,每个领域负责处理特定的业务功能,通过明确的上下文映射来协调它们之间的交互和集成。

例如,当用户在电子商务平台上下单时,可以通过以下步骤应用领域驱动设计的核心概念:

  1. 用户创建一个订单,填写订单的收货地址和商品信息。
  2. 在订单领域模型中,订单作为聚合根,包含订单编号、下单时间等属性,以及添加商品到订单、计算订单总额等行为。
  3. 订单项作为订单的实体,每个订单项有自己的商品ID、数量和单价等属性,并提供计算小计金额的行为。
  4. 收货地址作为订单的值对象,没有唯一标识,通过属性来定义和区分。
  5. 当订单创建成功后,系统发布订单已创建的领域事件,其他部分可以订阅该事件并作出相应的反应,如发送确认邮件、更新库存等。
  6. 在整个系统中,可以划分商品管理上下文、订单管理上下文等不同的限界上下文,每个上下文负责处理特定的业务领域,有自己的领域模型和语言。

通过领域驱动设计的方法,我们能够更加准确地理解业务需求,将业务知识融入系统设计中,并构建出具有高内聚性、松耦合性的软件系统。这种方式可以提升系统的可维护性、扩展性和可理解性,更好地满足复杂业务需求。

再来看一下示例代码,来实现订单域的定义,他包括:

  • 实体:订单、订单项
  • 值对象:收货地址
  • 聚合根:订单
// 实体 - 订单项
public class OrderItem {
    private String productId;
    private int quantity;
    private BigDecimal unitPrice;

    // 构造函数、Getter和Setter方法省略

    public BigDecimal calculateSubtotal() {
        return unitPrice.multiply(BigDecimal.valueOf(quantity));
    }
}

// 值对象 - 收货地址
public class ShippingAddress {
    private String fullName;
    private String address;
    private String postalCode;

    // 构造函数、Getter和Setter方法省略
}

// 聚合根 - 订单
public class Order {
    private String orderId;
    private LocalDateTime orderDateTime;
    private List<OrderItem> orderItems;
    private ShippingAddress shippingAddress;

    // 构造函数、Getter和Setter方法省略

    public BigDecimal calculateTotalAmount() {
        BigDecimal totalAmount = BigDecimal.ZERO;
        for (OrderItem item : orderItems) {
            totalAmount = totalAmount.add(item.calculateSubtotal());
        }
        return totalAmount;
    }
}

// 订单域的行为: 订单创建服务
public class OrderCreationService {
    public void createOrder(List<String> productIds, List<Integer> quantities, ShippingAddress shippingAddress) {
        // 根据产品ID查询商品信息,创建订单项
        List<OrderItem> orderItems = new ArrayList<>();
        for (int i = 0; i < productIds.size(); i++) {
            String productId = productIds.get(i);
            int quantity = quantities.get(i);
            // 查询商品信息,获取单价等
            BigDecimal unitPrice = getProductUnitPrice(productId);
            OrderItem orderItem = new OrderItem(productId, quantity, unitPrice);
            orderItems.add(orderItem);
        }

        // 创建订单
        Order order = new Order(generateOrderId(), LocalDateTime.now(), orderItems, shippingAddress);

        // 执行订单创建后的业务逻辑,如发送确认邮件等

        // 保存订单到数据库
        saveOrder(order);
    }

    private BigDecimal getProductUnitPrice(String productId) {
        // 查询数据库或调用其他服务获取商品的单价
        // 省略具体实现
        return BigDecimal.valueOf(10.0);
    }

    private String generateOrderId() {
        // 生成订单编号
        // 省略具体实现
        return "123456";
    }

    private void saveOrder(Order order) {
        // 将订单保存到数据库
        // 省略具体实现
        System.out.println("订单已保存:" + order.getOrderId());
    }
}

订单就定义完成了,下面看下应用:

// 使用示例
public class Main {
    public static void main(String[] args) {
        // 创建订单项列表
        List<String> productIds = Arrays.asList("P001", "P002");
        List<Integer> quantities = Arrays.asList(2, 3);

        // 创建收货地址
        ShippingAddress shippingAddress = new ShippingAddress("John Doe", "123 Main St", "12345");

        // 创建订单
        OrderCreationService orderCreationService = new OrderCreationService();
        orderCreationService.createOrder(productIds, quantities, shippingAddress);
    }
}

通过以上示例代码,我们可以看到领域驱动设计的核心概念在电子商务平台订单创建场景中的应用。订单项作为实体,具有自己的属性和行为;收货地址作为值对象,通过属性定义;订单作为聚合根,管理和维护订单项和收货地址等对象;订单域的行为用来协调整个订单创建过程,并与外部系统进行交互。

限界上下文(Bounded Context)是领域驱动设计中的一个重要概念,它通过明确定义的接口来划分系统中的不同领域,并协调它们之间的交互。下面是一个简化的代码示例,展示如何使用界限上下文在电子商务平台中处理商品管理和订单管理的不同领域:

// 商品管理上下文
public class ProductContext {
    public void addProduct(String name, BigDecimal price) {
        // 商品添加的业务逻辑
        // 省略具体实现
    }

    public void updateProductPrice(String productId, BigDecimal newPrice) {
        // 更新商品价格的业务逻辑
        // 省略具体实现
    }
}

// 订单管理上下文
public class OrderContext {
    public void createOrder(List<String> productIds, List<Integer> quantities, String customerId) {
        // 订单创建的业务逻辑
        // 省略具体实现
    }

    public void cancelOrder(String orderId) {
        // 取消订单的业务逻辑
        // 省略具体实现
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        ProductContext productContext = new ProductContext();
        OrderContext orderContext = new OrderContext();

        // 商品管理上下文的使用示例
        productContext.addProduct("iPhone", BigDecimal.valueOf(999.99));
        productContext.updateProductPrice("P001", BigDecimal.valueOf(899.99));

        // 订单管理上下文的使用示例
        List<String> productIds = Arrays.asList("P001", "P002");
        List<Integer> quantities = Arrays.asList(2, 3);
        String customerId = "C001";

        orderContext.createOrder(productIds, quantities, customerId);
        orderContext.cancelOrder("O001");
    }
}

在上述示例中,我们定义了两个上下文:商品管理上下文(ProductContext)和订单管理上下文(OrderContext)。每个上下文都负责处理特定领域中的业务逻辑。

通过界限上下文的划分,我们可以将商品管理和订单管理两个不同的领域进行解耦。开发团队可以在每个上下文中专注于实现和维护相应领域的业务逻辑,而不会相互干扰。

在示例中,我们分别使用了商品管理上下文和订单管理上下文的方法来添加商品、更新商品价格、创建订单和取消订单。每个上下文中的具体实现可以根据实际需求进行编写。

通过界限上下文的使用,我们能够更好地组织和划分业务逻辑,使系统更具模块化和可扩展性。每个上下文都可以独立进行开发、测试和维护,提高团队的协作效率和代码的可维护性。

界限上下文定义好以后,在不同的界限上下文之间进行交互时,有一些常见的原则可以遵循:

  1. 明确接口:每个界限上下文应该明确定义自己的接口,包括输入参数、返回结果和可能的异常情况。这样可以确保上下文之间的通信是清晰和一致的。
  2. 显式发布和订阅:界限上下文之间的交互可以通过事件的发布和订阅来实现。一个上下文可以发布事件,而其他上下文可以订阅这些事件并做出相应的反应。这种松耦合的方式可以减少上下文之间的直接依赖。
  3. 共享内核:在某些情况下,多个上下文可能需要共享某些公共的模型、代码库或数据库。在这种情况下,可以通过共享内核的方式来实现。共享内核是一组被多个上下文共享的通用代码和资源。
  4. 限制上下文间的直接依赖:尽量避免上下文之间的直接依赖关系,以减少耦合性。如果一个上下文需要使用另一个上下文的功能,可以通过接口调用、事件发布等间接方式进行通信,而不是直接依赖。
  5. 上下文映射:在不同的上下文之间建立明确的映射关系,可以帮助理解和管理它们之间的交互。映射关系可以通过上下文映射图、接口文档等方式进行记录和共享。

好了,到这里已经把领域驱动设计的核心概念草稿式的过了一遍,真正落地时,我们还遇到了过一些好玩的事儿,也是在做领域驱动设计时要注意的:

  • 明确术语(产研内各个角色的统一,以及产研外和业务沟通的术语统一):
    • 记得我们系统中有好多业务域都有叫“订单”的东西。每次聊到订单,我们都需要在明确一下,说的是那个业务域的订单。很费劲, 后来就索性仅保留主订单域的订单,其他业务域类似订单的东西,都进行了重新命名
    • 还有些内容,产研的叫的名字和实际业务使用方叫的名字不一致。比如当时我们有个实体叫“方案”,业务使用方也有个东西叫“方案”。经过多次沟通,才发现,这完全是两个概念。这种术语,也需要统一

参考

最后修改日期: 2023年9月16日

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。