领域驱动设计(DDD)的现代价值

引言在软件开发领域,领域驱动设计(Domain-Driven Design,DDD) 作为一种设计思想,已存在超过三十年。尽管在快速迭代的互联网开发模式中,DDD可能显得有些过时,但随着互联网企业深入实体经济,业务复杂性增加,DDD的重要性逐渐凸显。

问题随着业务的不断扩展,系统逐渐面临以下问题:

  1. 过度耦合:在业务初期,系统功能简单,易于管理。但随着时间推移,业务逻辑变得复杂,系统模块间的耦合加剧。这导致修改功能时,需要花费大量时间来理解其依赖关系,并难以预测修改可能带来的影响。

系统耦合示例以下是系统耦合的一个典型案例,展示了模块间如何相互依赖,影响系统的整体清晰度和可维护性。

[系统耦合图示或描述]

解决方案在实践中,我们可以通过DDD的思想来解决这些问题,具体方法包括但不限于:

  • 领域建模:深入理解业务领域,建立清晰的领域模型。
  • 限界上下文:明确系统的边界,划分不同的业务领域。
  • 实体与值对象:区分系统中的实体和值对象,合理组织数据结构。
  • 聚合根:识别并定义聚合根,简化数据操作和管理。 通过这些方法,我们可以提高系统的可维护性和可扩展性,应对日益复杂的业务需求。

订单服务接口设计问题

1. 问题概述订单服务接口不仅提供查询和创建订单的功能,还涉及订单评价、支付和保险等。由于订单表字段众多,维护代码时很容易出现改动一处影响全局的问题。尽管测试可以确保功能的完备性,但在多需求并行开发时,代码改动重叠和频繁修改问题仍然难以避免。

2. 问题根源根本原因在于系统架构不清晰,模块内聚度低,耦合度高。

3. 解决方案### 3.1 演进式设计根据演进式设计理论,系统设计应随系统实现的增长而自然演进,无需提前过度设计。敏捷实践中的重构、测试驱动设计和持续集成是应对混乱的有效手段。

3.1.1 重构重构是演进式设计中的关键,通过小步快跑的方式,在类和方法级别上进行,以改善局部设计而不改变系统行为。

3.1.2 测试驱动设计确保对系统的更改不会破坏现有功能。

3.1.3 持续集成为团队提供统一的代码库,减少集成问题。

3.2 领域驱动设计(DDD)DDD有助于解决领域模型到设计模型的同步和演化问题,将反映领域的设计模型转化为实际代码。

4. 贫血领域对象问题### 4.1 定义贫血领域对象指的是那些仅作为数据载体,缺乏行为和动作的领域对象。

4.2 影响这种对象导致以数据为中心的开发模式,忽视了面向对象理论的优势。

4.3 实例分析以抽奖平台为例,需求是按预设概率从奖池中抽取奖项。实现过程简单,但采用贫血模型会导致对象仅作为数据载体,缺乏行为。

5. 结论为避免代码腐败和无休止的重构循环,应采用DDD等方法,确保设计模型与业务需求紧密相连,提高系统的可维护性和可扩展性。

  • 设计AwardPool和Award两个对象,只有简单的get和set属性的方法

复制代码

class AwardPool {
    int awardPoolId;
    List<Award> awards;
    public List<Award> getAwards() {
        return awards;
    }

    public void setAwards(List<Award> awards) {
        this.awards = awards;
    }
    ......
}

class Award {
   int awardId;
   int probability;//概率

   ......
}

复制代码

  • Service代码实现

    设计一个LotteryService,在其中的drawLottery()方法写服务逻辑

AwardPool awardPool = awardPoolDao.getAwardPool(poolId);//sql查询,将数据映射到AwardPool对象
for (Award award : awardPool.getAwards()) {
   //寻找到符合award.getProbability()概率的award
}

业务逻辑与领域驱动设计(DDD)

1. 贫血模型与过程化设计的问题在业务逻辑较为简单时,采用贫血模型和过程化设计是可行的。但在业务逻辑变得复杂时,代码的意图会逐渐模糊,导致业务逻辑和状态散落在多个方法中,这种情况被称为由贫血症引起的失忆症。

2. 领域模型的优势采用领域模型的开发方式,将数据和行为封装在一起,并与现实世界中的业务对象相映射。这种模式下,各类对象具备明确的职责划分,领域逻辑分散到各个领域对象中,例如抽奖逻辑应放在AwardPool类中。

3. 应对软件系统的复杂性解决复杂和大规模软件的策略主要包括抽象、分治和知识。分治策略将问题空间分割成更小的子问题,便于解决;抽象策略简化问题空间,提高理解度;而DDD作为一种知识手段,指导我们如何进行抽象和分治。

4. DDD与微服务架构的协同微服务架构强调创建高内聚、低耦合的服务,与DDD中的限界上下文概念相契合。两者都从业务维度出发,通过分治来应对系统复杂度。

5. 架构设计的三个层面

  • 业务架构:根据业务需求设计业务模块及其关系。
  • 系统架构:设计系统和子系统的模块。
  • 技术架构:决定采用的技术及框架。 在实际开发中,这三种活动不一定有固定的先后顺序,但业务架构设计是关键,它决定了系统对业务变化的响应能力。

6. DDD与微服务的结合DDD的核心是将业务架构映射到系统架构上,使系统架构能够随着业务变化而调整。微服务则追求业务层面的复用,设计出与业务一致的系统架构,并在技术架构上实现模块间的充分解耦。

7. 结论DDD和微服务架构在追求业务维度上的统一,通过合理的分治和抽象,提高系统的可维护性和可扩展性。

如何实践领域驱动设计(DDD)

领域驱动设计(DDD)是一种软件开发方法,它通过将业务领域知识与软件设计紧密结合,实现系统的高内聚和低耦合。本文将通过抽奖平台的案例,详细介绍如何应用DDD来解构一个基于微服务架构的系统。

抽奖系统需求概览

抽奖系统主要服务于两类用户:运营人员和普通用户。

  • 运营人员:负责配置抽奖活动,面向特定用户群体,发放不同类型的奖品。
  • 普通用户:通过活动页面参与抽奖活动。

领域模型设计步骤

  1. 领域和限界上下文划分:根据需求,初步划分领域和限界上下文,确定它们之间的关系。
  2. 实体与值对象识别:在每个限界上下文中,识别实体和值对象。
  3. 聚合构建:对实体和值对象进行关联和聚合,确定聚合根。
  4. 仓储设计:为聚合根设计仓储,并考虑实体或值对象的创建方式。
  5. 实践与重构:在工程中实践领域模型,检验并优化模型。

战略与战术建模

  • 战略设计:高层次、宏观划分和集成限界上下文。
  • 战术设计:使用建模工具细化上下文。

领域与限界上下文

  • 领域:包含问题域和解系统,软件是现实世界的部分模拟。
  • 限界上下文:具有显式边界的特定职责,领域模型存在于边界内。

划分限界上下文的实践

划分限界上下文应基于语义边界,而非技术架构或开发任务。我们的做法是:

  • 从产品通用语言中提取术语,形成概念对象。
  • 观察对象间联系,从需求中提取动词,分析动作与对象的关系。
  • 将紧耦合的对象圈在一起,形成限界上下文。
  • 用语言描述限界上下文的职责,确保其清晰、准确、简洁、完整。

抽奖平台的限界上下文划分

根据业务特点,我们将抽奖平台划分为两个子域:

  • C端抽奖:面向普通用户,提供高频次的抽奖参与。
  • M端抽奖管理平台:面向运营人员,负责配置抽奖活动,低频但复杂。 通过这种划分,实现了两个子域的完全解耦,提高了系统的灵活性和可维护性。

抽奖活动限界上下文划分

根据M端领域和C端的限界上下文确认,我们对C端内部的限界上下文进行进一步划分,以满足产品需求。以下是对C端抽奖活动需求的概述和关键概念提取。

产品需求概述1. 抽奖活动设有限制条件,包括用户抽奖次数、活动时间等。2. 一个活动可包含多个奖品,可针对不同用户群体。3. 奖品具有配置信息,如库存、中奖概率、用户最多中奖次数等。4. 用户群体可通过多种方式区分,例如城市、新老客户等。5. 活动具备风控配置,限制用户参与频率。

关键概念提取

  • 抽奖限制:包括用户抽奖次数和活动时间限制。
  • 奖品设置:涉及奖品库存量和中奖概率等配置。
  • 用户群体:根据城市、新老客户等标准进行区分。
  • 风控配置:设置参与抽奖的频率限制。 通过对上述需求的分析,我们定义了抽奖活动的限界上下文,以确保产品功能的清晰和有效实施。

    抽奖系统设计概述 ================ 抽奖作为我们平台的核心业务,其设计过程中我们考虑了多个方面,包括奖品管理、用户参与以及风控等。以下是对整个抽奖系统设计的一个详细梳理。

抽奖与发奖的合并

  • 在设计初期,我们考虑将抽奖和发奖分为两个独立领域,但发现两者逻辑紧密相连,难以分割。因此,我们决定将它们合并为一个统一的抽奖领域。

  • 发奖逻辑相对简单,主要依赖第三方服务进行奖品的发放。

活动准入限制

  • 我们定义了活动准入的通用语言,包括活动的时间限制和参与次数等条件,以确保活动的公平性和有效性。

奖品库存管理

  • 库存管理关注点在于奖品的核销,且具有通用性,可以被其他业务使用。因此,我们将其定义为独立的库存领域。

风控上下文定义

  • 针对C端用户可能存在的刷单行为,我们根据产品需求定义了风控上下文,以确保活动的安全性。

计数上下文的引入

  • 由于多个领域都涉及到次数限制,我们引入了计数上下文来统一管理这些限制。

上下文映射图与康威定律

  • 康威定律指出,系统结构应与组织结构保持一致。我们通过DDD的限界上下文划分,确保团队结构与系统业务结构的一致性。

  • 明确上下文之间的关系,有助于任务的拆分和沟通的顺畅。

限界上下文之间的映射关系

  • 我们定义了多种映射关系,包括合作关系、共享内核、客户方-供应方开发等,以明确上下文间的交互方式。

  • 每种关系都有其特定的交互模式和适用场景,我们根据实际情况选择了最合适的关系类型。

抽奖平台上下文映射图

  • 最后,我们绘制了抽奖平台的上下文映射图,以直观展示各上下文之间的关系和交互模式。

  在抽奖上下文中,我们通过抽奖(DrawLottery)这个聚合根来控制抽奖行为,可以看到,一个抽奖包括了抽奖ID(LotteryId)以及多个奖池(AwardPool),而一个奖池针对一个特定的用户群体(UserGroup)设置了多个奖品(Award)。

  另外,在抽奖领域中,我们还会使用抽奖结果(SendResult)作为输出信息,使用用户领奖记录(UserLotteryLog)作为领奖凭据和存根。

  谨慎使用值对象

  在实践中,我们发现虽然一些领域对象符合值对象的概念,但是随着业务的变动,很多原有的定义会发生变更,值对象可能需要在业务意义具有唯一标识,而对这类值对象的重构往往需要较高成本。因此在特定的情况下,我们也要根据实际情况来权衡领域对象的选型。

  DDD工程实现

  在对上下文进行细化后,我们开始在工程中真正落地DDD。

  模块

  模块(Module)是DDD中明确提到的一种控制限界上下文的手段,在我们的工程中,一般尽量用一个模块来表示一个领域的限界上下文。

  如代码中所示,一般的工程中包的组织方式为{com.公司名.组织架构.业务.上下文.*},这样的组织结构能够明确的将一个上下文限定在包的内部。

import com.company.team.bussiness.lottery.*;//抽奖上下文
import com.company.team.bussiness.riskcontrol.*;//风控上下文
import com.company.team.bussiness.counter.*;//计数上下文
import com.company.team.bussiness.condition.*;//活动准入上下文
import com.company.team.bussiness.stock.*;//库存上下文

代码演示1 模块的组织

  对于模块内的组织结构,一般情况下我们是按照领域对象、领域服务、领域资源库、防腐层等组织方式定义的。

import com.company.team.bussiness.lottery.domain.valobj.*;//领域对象-值对象
import com.company.team.bussiness.lottery.domain.entity.*;//领域对象-实体
import com.company.team.bussiness.lottery.domain.aggregate.*;//领域对象-聚合根
import com.company.team.bussiness.lottery.service.*;//领域服务
import com.company.team.bussiness.lottery.repo.*;//领域资源库
import com.company.team.bussiness.lottery.facade.*;//领域防腐层

代码演示2:模块的组织结构

在软件开发中,模块的组织是至关重要的,它关系到代码的可维护性和扩展性。本文将详细介绍如何组织领域驱动设计的模块。

领域对象的构建

领域驱动设计(Domain-Driven Design, DDD)的一个核心问题是如何避免对象的贫血问题,即对象仅有数据而无行为。我们通过抽奖(DrawLottery)聚合根和奖池(AwardPool)值对象来具体展示这一概念。

抽奖聚合根(DrawLottery)

抽奖聚合根是抽奖活动的中心对象,它持有活动的唯一标识符(id)以及所有可用的奖池列表。其核心功能是选择奖池,具体通过chooseAwardPool方法实现。

奖池值对象(AwardPool)

奖池值对象代表可发放的奖品集合,它与抽奖聚合根紧密关联,共同作用于抽奖过程。

抽奖过程的实现

chooseAwardPool方法的实现逻辑如下:

  1. 接收一个抽奖发生场景(DrawLotteryContext),该场景携带用户抽奖时的相关信息,如用户的得分或所在城市。
  2. 根据场景信息,DrawLottery聚合根将匹配一个合适的AwardPool,确保用户能够获得相应的奖品。 这种方法确保了抽奖过程的灵活性和准确性,同时也体现了领域驱动设计中对象行为的重要性。

总结

通过上述介绍,我们可以看到领域对象不仅仅是数据的载体,更是业务逻辑的执行者。合理的模块组织和对象设计,可以大大提高软件系统的质量和可维护性。
复制代码

package com.company.team.bussiness.lottery.domain.aggregate;
import ...;

public class DrawLottery {
    private int lotteryId; //抽奖id
    private List<AwardPool> awardPools; //奖池列表

    //getter & setter
    public void setLotteryId(int lotteryId) {
        if(id<=0){
            throw new IllegalArgumentException("非法的抽奖id"); 
        }
        this.lotteryId = lotteryId;
    }

    //根据抽奖入参context选择奖池
    public AwardPool chooseAwardPool(DrawLotteryContext context) {
        if(context.getMtCityInfo()!=null) {
            return chooseAwardPoolByCityInfo(awardPools, context.getMtCityInfo());
        } else {
            return chooseAwardPoolByScore(awardPools, context.getGameScore());
        }
    }

    //根据抽奖所在城市选择奖池
    private AwardPool chooseAwardPoolByCityInfo(List<AwardPool> awardPools, MtCifyInfo cityInfo) {
        for(AwardPool awardPool: awardPools) {
            if(awardPool.matchedCity(cityInfo.getCityId())) {
                return awardPool;
            }
        }
        return null;
    }

    //根据抽奖活动得分选择奖池
    private AwardPool chooseAwardPoolByScore(List<AwardPool> awardPools, int gameScore) {...}
}

复制代码

代码演示3 DrawLottery

  在匹配到一个具体的奖池之后,需要确定最后给用户的奖品是什么。这部分的领域功能在AwardPool内。

复制代码

package com.company.team.bussiness.lottery.domain.valobj;
import ...;

public class AwardPool {
    private String cityIds;//奖池支持的城市
    private String scores;//奖池支持的得分
    private int userGroupType;//奖池匹配的用户类型
    private List<Awrad> awards;//奖池中包含的奖品

    //当前奖池是否与城市匹配
    public boolean matchedCity(int cityId) {...}

    //当前奖池是否与用户得分匹配
    public boolean matchedScore(int score) {...}

    //根据概率选择奖池
    public Award randomGetAward() {
        int sumOfProbablity = 0;
        for(Award award: awards) {
            sumOfProbability += award.getAwardProbablity();
        }
        int randomNumber = ThreadLocalRandom.current().nextInt(sumOfProbablity);
        range = 0;
        for(Award award: awards) {
            range += award.getProbablity();
            if(randomNumber<range) {
                return award;
            }
        }
        return null;
    }
}

复制代码

领域对象的行为特性

领域对象与传统的业务对象不同,它们不仅包含数据,还拥有行为。这使得对象更加完整,领域功能的内聚性更强,职责也更加明确。

资源库的作用

领域对象需要存储资源,资源的存储方式可以多样化,包括但不限于数据库、分布式缓存和本地缓存。资源库(Repository)是统一管理领域存储和访问的对象。在抽奖平台中,资源库的组织方式如下:

  1. 资源存储方式:列举了可能的存储手段,如数据库、分布式缓存等。
  2. 资源库定义:解释了资源库在领域模型中的角色和职责。
  3. 抽奖平台的实现:具体说明了在抽奖平台中资源库是如何被组织和使用的。 这种结构清晰地展示了领域对象和资源库的概念及其在实际应用中的运用。
    复制代码
//数据库资源
import com.company.team.bussiness.lottery.repo.dao.AwardPoolDao;//数据库访问对象-奖池
import com.company.team.bussiness.lottery.repo.dao.AwardDao;//数据库访问对象-奖品
import com.company.team.bussiness.lottery.repo.dao.po.AwardPO;//数据库持久化对象-奖品
import com.company.team.bussiness.lottery.repo.dao.po.AwardPoolPO;//数据库持久化对象-奖池

import com.company.team.bussiness.lottery.repo.cache.DrawLotteryCacheAccessObj;//分布式缓存访问对象-抽奖缓存访问
import com.company.team.bussiness.lottery.repo.repository.DrawLotteryRepository;//资源库访问对象-抽奖资源库

复制代码

代码演示5: Repository组织结构

概述

资源库(Repository)是对外提供整体访问的核心组件,它不仅聚合了不同资源库的数据信息,还负责资源存储的逻辑,例如实现缓存更新机制等。

抽奖资源库设计

在抽奖资源库的设计中,我们采用了一种策略,即屏蔽对底层奖池和奖品的直接访问。相反,我们通过聚合根来管理抽奖资源。这不仅提高了数据的安全性,也使得资源管理更为集中和高效。

代码示例以下是一个代码示例,展示了如何使用抽奖资源获取方法,这里采用的是常见的缓存旁路模式(Cache Aside Pattern)。

java// 示例代码略

职责明确与代码维护与传统将资源管理放在服务层的做法相比,将资源管理职责赋予Repository层,可以使得职责划分更为明确。这种设计不仅提升了代码的可读性,还增强了代码的可维护性。

结论通过将资源管理逻辑集中在Repository层,我们能够实现一个更加清晰、易于维护的系统架构。

复制代码

package com.company.team.bussiness.lottery.repo;
import ...;

@Repository
public class DrawLotteryRepository {
    @Autowired
    private AwardDao awardDao;
    @Autowired
    private AwardPoolDao awardPoolDao;
    @AutoWired
    private DrawLotteryCacheAccessObj drawLotteryCacheAccessObj;

    public DrawLottery getDrawLotteryById(int lotteryId) {
        DrawLottery drawLottery = drawLotteryCacheAccessObj.get(lotteryId);
        if(drawLottery!=null){
            return drawLottery;
        }
        drawLottery = getDrawLotteyFromDB(lotteryId);
        drawLotteryCacheAccessObj.add(lotteryId, drawLottery);
        return drawLottery;
    }

    private DrawLottery getDrawLotteryFromDB(int lotteryId) {...}
}

复制代码

代码演示6:DrawLotteryRepository

抗腐蚀层概述

抗腐蚀层,也被称作适配层,是软件设计中用于隔离不同上下文或模块的一层。它的主要作用是防止外部变化对内部系统造成影响。以下是引入抗腐蚀层的几种情况:

  1. 模型转换:当需要将外部上下文的模型转换为当前上下文能够理解的模型时。
  2. 团队协作:在不同团队之间进行协作,尤其是当存在供奉者关系时,引入抗腐蚀层可以避免外部变化对本团队的影响。
  3. 广泛使用:如果对外部上下文的访问在内部多个上下文中非常普遍,使用抗腐蚀层可以减少修改时的影响范围。 如果多个内部上下文需要访问外部上下文,可以考虑将抗腐蚀层放在一个通用的上下文中。

抽奖平台中的抗腐蚀层应用

在抽奖平台的设计中,我们创建了一个名为UserCityInfoFacade的抗腐蚀层,它用于处理来自外部用户城市信息上下文的访问。这在微服务架构中通常表现为用户城市信息服务。 以用户信息抗腐蚀层为例,它接收一个抽奖请求参数LotteryContext作为输入,并返回城市信息MtCityInfo作为输出。

结构化内容

  • 抗腐蚀层定义:介绍抗腐蚀层的概念及其在软件设计中的作用。
  • 使用场景:列举了引入抗腐蚀层的几种典型情况。
  • 应用示例:以抽奖平台的用户城市信息抗腐蚀层为例,说明其在实际应用中的设计和作用。
  • 参数与输出:描述了抗腐蚀层接收的输入参数和产生的输出结果。
    复制代码
package com.company.team.bussiness.lottery.facade;
import ...;

@Component
public class UserCityInfoFacade {
    @Autowired
    private LbsService lbsService;//外部用户城市信息RPC服务

    public MtCityInfo getMtCityInfo(LotteryContext context) {
        LbsReq lbsReq = new LbsReq();
        lbsReq.setLat(context.getLat());
        lbsReq.setLng(context.getLng());
        LbsResponse resp = lbsService.getLbsCityInfo(lbsReq);
        return buildMtCifyInfo(resp);
    }

    private MtCityInfo buildMtCityInfo(LbsResponse resp) {...}
}

复制代码
在软件开发中,领域服务扮演着至关重要的角色。它通过整合领域对象、资源库和防腐层等组件的行为,为其他上下文提供交互接口。以下是对领域服务的详细阐述:

领域服务概述

领域服务是领域模型中的一个重要组成部分,它负责协调领域对象之间的复杂业务逻辑。领域服务通常不持有状态,而是通过调用领域对象的方法来实现业务逻辑。

领域服务的职责

  1. 协调领域对象:领域服务将不同的领域对象串联起来,实现业务逻辑的流程。
  2. 资源管理:与资源库交互,处理数据的持久化和检索。
  3. 外部交互:通过防腐层与外部系统进行交互,确保系统的解耦。

领域服务示例

以抽奖服务(issueLottery)为例,领域服务的实现可以清晰地展示其职责。在实现过程中,我们通常会省略一些防御性逻辑,如异常处理和空值判断,以使核心逻辑更加突出。

实现步骤

  1. 定义领域服务接口:明确领域服务需要实现的功能。
  2. 实现业务逻辑:根据业务需求,调用领域对象的方法,实现具体的业务流程。
  3. 异常处理:虽然在示例中省略,但在实际开发中,需要对可能出现的异常进行处理。
  4. 与资源库交互:确保数据的正确存取。
  5. 防腐层的应用:通过防腐层与外部系统进行数据交换,保持系统的独立性和可维护性。

结论

领域服务作为领域模型的核心,其设计和实现直接影响到系统的可维护性和扩展性。合理地划分领域服务的职责,有助于构建一个清晰、高效的领域模型。
复制代码

package com.company.team.bussiness.lottery.service.impl
import ...;

@Service
public class LotteryServiceImpl implements LotteryService {
    @Autowired
    private DrawLotteryRepository drawLotteryRepo;
    @Autowired
    private UserCityInfoFacade UserCityInfoFacade;
    @Autowired
    private AwardSendService awardSendService;
    @Autowired
    private AwardCounterFacade awardCounterFacade;

    @Override
    public IssueResponse issueLottery(LotteryContext lotteryContext) {
        DrawLottery drawLottery = drawLotteryRepo.getDrawLotteryById(lotteryContext.getLotteryId());//获取抽奖配置聚合根
        awardCounterFacade.incrTryCount(lotteryContext);//增加抽奖计数信息
        AwardPool awardPool = lotteryConfig.chooseAwardPool(bulidDrawLotteryContext(drawLottery, lotteryContext));//选中奖池
        Award award = awardPool.randomChooseAward();//选中奖品
        return buildIssueResponse(awardSendService.sendAward(award, lotteryContext));//发出奖品实体
    }

    private IssueResponse buildIssueResponse(AwardSendResponse awardSendResponse) {...}
}

复制代码

代码演示8 LotteryService

  数据流转

数据流转与编码规范

1. 数据对象与服务在抽奖平台实践中,我们的数据流转遵循以下流程:

  • 信息传输对象(DTO):用于完成与外界的数据交互。
  • 领域对象(DO):作为领域内部的数据和行为载体。
  • 数据库持久化对象(PO):用于数据库资源的交互。 DTO与DO的转换在领域服务内进行,而DO与PO的转换则在资源库内完成。尽管这种编码规范可能增加了一次数据转换,但每种数据对象的职责更为明确,使得数据流转过程更加清晰。

2. 上下文集成集成上下文通常有多种手段,包括:

  • 开放领域服务接口
  • 开放HTTP服务
  • 消息发布-订阅机制 在抽奖系统中,我们主要采用开放服务接口进行交互。计数上下文作为一个通用上下文,为抽奖、风控、活动准入等提供了访问接口。此外,若需要在上下文之间进行集成并保持隔离和适配,可以引入防腐层的概念。

3. 领域分离与微服务架构在实施领域模型的过程中,我们采用了微服务架构风格。这与Vernon在《实现领域驱动设计》中描述的架构有所不同。具体差异可以通过阅读他的著作来了解。

在微服务架构中,领域服务是独立部署的,并且对外只暴露服务接口。业务逻辑完全依托于领域服务。与Vernon的著作相比,我们的应用服务层较为简单,主要负责获取来自接口层的请求参数,并调度多个领域服务以实现界面层功能。

总结通过上述分析,我们可以看到在抽奖平台的数据流转、上下文集成和领域分离中,采用了清晰的数据对象职责划分和微服务架构的应用,以实现更加高效和灵活的系统设计。


随着业务的持续发展,我们的系统逐渐成为业务核心。在这一过程中,应用服务虽然不直接承担领域逻辑,但它们通过组合多个领域服务,实际上已经蕴含了丰富的业务逻辑。当业务规模扩展到一定程度时,这种编排本身就成为了一种业务逻辑的体现。此外,为了确保系统的稳定性和性能,我们希望将这些措施集中管理,而不是分散在各个地方。 在这种背景下,应用服务的角色发生了转变。对内,它仍然是应用服务,负责协调和整合不同领域的服务。但对外,它已经具备了领域服务的特征,因此需要将其作为微服务进行暴露。这样,应用服务在内部和外部的角色和职责就更加明确,有助于构建一个清晰定义的限界上下文。

在设计软件系统时,架构的选择至关重要。根据团队和业务的具体情况,可以灵活选择不同的架构模式。例如,分层架构和CQRS架构都是可行的选项。以下是我根据实际业务经验,对抽奖应用服务的架构设计示例。

抽奖应用服务架构设计

  1. 领域服务定义
  • 抽奖服务:负责处理用户参与抽奖的逻辑。
  • 活动准入服务:确保用户符合参与活动的条件。
  • 风险控制服务:控和预防可能的风险行为。
  1. 系统架构组织
  • 客户端请求通过API网关进入系统。
  • API网关根据请求类型分发到相应的领域服务。
  • 领域服务处理业务逻辑后,返回结果给API网关。
  • API网关将结果返回给客户端。
  1. 领域服务集成
  • 各领域服务之间通过定义清晰的接口进行交互。
  • 确保服务解耦,便于独立开发和维护。
  1. 服务间通信
  • 采用消息队列或事件总线实现服务间的异步通信。
  • 保证系统的高可用性和扩展性。
  1. 数据一致性
  • 采用最终一致性模型,允许短期内的数据不一致。
  • 通过补偿事务或事件溯源等机制,保证长期的数据一致性。
  1. 安全性考虑
  • 所有服务都需进行安全审查,确保没有安全漏洞。
  • 实现访问控制和数据加密,保护用户数据安全。
  1. 性能优化
  • 对关键服务进行性能监控和优化。
  • 采用缓存、负载均衡等技术提高系统性能。
  1. 可扩展性设计
  • 服务设计时考虑横向扩展,以应对用户量的增长。
  1. 监控与日志
  • 实现全面的监控系统,及时发现并解决问题。
  • 记录详细的日志,便于问题追踪和系统优化。
  1. 容错性设计
  • 设计服务时考虑容错机制,确保系统的稳定性。 以上是抽奖应用服务的架构设计要点,具体的实现细节需要根据实际业务需求进一步细化和调整。
    复制代码
package ...;

import ...;

@Service
public class LotteryApplicationService {
    @Autowired
    private LotteryRiskService riskService;
    @Autowired
    private LotteryConditionService conditionService;
    @Autowired
    private LotteryService lotteryService;

    //用户参与抽奖活动
    public Response<PrizeInfo, ErrorData> participateLottery(LotteryContext lotteryContext) {
        //校验用户登录信息
        validateLoginInfo(lotteryContext);
        //校验风控 
        RiskAccessToken riskToken = riskService.accquire(buildRiskReq(lotteryContext));
        ...
        //活动准入检查
        LotteryConditionResult conditionResult = conditionService.checkLotteryCondition(otteryContext.getLotteryId(),lotteryContext.getUserId());
        ...
        //抽奖并返回结果
        IssueResponse issueResponse = lotteryService.issurLottery(lotteryContext);
        if(issueResponse!=null && issueResponse.getCode()==IssueResponse.OK) {
            return buildSuccessResponse(issueResponse.getPrizeInfo());
        } else {   
            return buildErrorResponse(ResponseCode.ISSUE_LOTTERY_FAIL, ResponseMsg.ISSUE_LOTTERY_FAIL)
        }
    }

    private void validateLoginInfo(LotteryContext lotteryContext){...}
    private Response<PrizeInfo, ErrorData> buildErrorResponse (int code, String msg){...}
    private Response<PrizeInfo, ErrorData> buildSuccessResponse (PrizeInfo prizeInfo){...}
}

复制代码

领域驱动设计在互联网业务系统的应用

引言领域驱动设计(Domain-Driven Design,简称DDD)是一种软件设计方法论,它强调以业务领域为中心,通过建立领域模型来指导软件开发。本文将探讨DDD在互联网业务系统中的实践应用。

DDD实践概述在互联网业务系统中,DDD的应用可以帮助我们更好地理解业务需求,构建出更符合实际业务的软件系统。通过分治的思想,将复杂系统分解为更小、更易于管理的部分。

抽象到具体

  • 抽象阶段:确定业务领域的关键概念和边界。
  • 具体实现:根据抽象阶段的成果,设计具体的领域模型。

系统解构

  • 合理划分:将系统分解成多个子系统,每个子系统负责一部分业务逻辑。
  • 高内聚低耦合:确保每个子系统的功能集中,减少系统间的依赖。

适用场景与取舍虽然DDD是一种强大的设计工具,但它并不适用于所有场景。例如,对于简单的系统或SmartUI等,可能不需要采用DDD。此外,在特定范围和具体场景下,贫血模型和演进式设计可能更为高效。

取舍原则

  • 实际情况:根据项目的具体需求和团队的实际情况来决定是否采用DDD。
  • 效率与效果:权衡DDD带来的设计优势与可能的复杂性增加。

DDD的实践要点DDD的核心在于构建高质量的领域模型,实现软件的高内聚和低耦合。开发者需要根据自己的理解和团队情况,灵活运用DDD的实践要点。

模型构建

  • 深入理解业务:深入分析业务需求,构建符合实际的领域模型。
  • 持续迭代:随着业务的发展,不断迭代和优化领域模型。

模型腐化问题在DDD的迭代过程中,模型可能会出现腐化现象。本文未详细讨论此问题,将在后续文章中进行深入探讨。

结语作者在领域驱动设计方面的理解可能存在局限,欢迎业界同仁提出宝贵意见,共同探讨,共同进步。

期待后续

  • 模型腐化:将在未来的文章中详细讨论DDD模型在迭代过程中可能出现的腐化问题。
  • 持续交流:期待与读者的持续交流和反馈,以促进知识的共享和成长。