跳至主要內容

【luckydraw-ddd】领域开发01-DDD项目环境构建

holic-x...大约 14 分钟后端luckydraw-ddd

【luckydraw-ddd】领域开发01-DDD项目环境构建

1.环境、配置、规范说明

开发环境

JDK 1.8
SpringBoot 2.6.0
Dubbo 2.7.10
DB-ROUTER 自研分库分表路由组件,带着你一起写个SpringBoot Starter
vue 开发H5大转盘抽奖
微信公众号 对接提供API,回复抽奖
Docker 本地和云服务
其他所需环境:mysql\kafka\zk\redis\xxl-job

工程说明

名称系统作用
分布式核心功能服务系统Lotteryopen in new window提供抽奖业务领域功能,以分布式部署的方式提供 RPC 服务。
网关API服务Lottery-APIopen in new window网关服务,提供;
H5 页面抽奖、公众号开发回复消息抽奖
C端用户系统lottery-frontopen in new windowvue H5 lucky-canvas 大转盘抽奖界面,讲解 vue 工程创建、引入模块、开发接口、跨域访问和功能实现
B端运营系统Lottery-ERPopen in new window满足运营人员对于活动的查询、配置、修改、审核等操作
分库分表路由组件db-router-spring-boot-starteropen in new window本项目依赖自研分库分表组件,需要下载后构建 开发一个基于 HashMap 核心设计原理,使用哈希散列+扰动函数的方式,把数据散列到多个库表中的组件,并验证使用。
自研数据库路由组件,可构建到本地仓库进行应用
测试验证系统Lottery-Testopen in new window用于测试验证RPC服务、系统功能调用的测试系统。

2.DDD+RPC架构搭建

搭建说明

项目说明

项目说明说明
项目分支dev_220101_01_initProject
项目描述基于DDD架构模型,初始化搭建工程结构

​ 搭建说明:对比传统的Springboot单体项目构建可参考Spring官网(https://start.spring.io)提供的脚手架工程进行构建,此处项目依赖DDD四层架构进行模块化拆分结合RPC组件应用进行框架构建

DDD分层架构

​ DDD(Domain-Driven Design 领域驱动设计)是由Eric Evans最先提出,目的是对软件所涉及到的领域进行建模,以应对系统规模过大时引起的软件复杂性的问题。整个过程大概是这样的,开发团队和领域专家一起通过通用语言(Ubiquitous Language)去理解和消化领域知识,从领域知识中提取和划分为一个一个的子领域(核心子域,通用子域,支撑子域),并在子领域上建立模型,再重复以上步骤,这样周而复始,构建出一套符合当前领域的模型

2-01

学习说明

​ 掌握DDD分层架构概念、模块分层的职责,以及RPC层单独拆分的目的等相关概念,一开始接触可能会有点懵,后续结合项目应用时间进行分析,提出问题、重温概念、分析问题并解决(目前概念梳理还是比较模糊,暂不纠结概念,结合实践项目操作再回过头去结合自己的理解重温概念)


​ 结合MVC概念了解DDD四层架构以及对应模块的内容

​ DDD充血模型结构在独立领域开发和引用RPC框架不同场景下的优缺点

​ (先提出问题,待后续实践扩展!!)


贫血模型&充血模型设计对比

DDD

PO、VO、DO、DTO

PO:persistent object 持久对象

  • 有时也被称为Data对象,对应数据库中的entity,可以简单认为一个PO对应数据库中的一条记录。
  • 在Mybatis持久化框架中与insert/delet操作密切相关。
  • PO中不应该包含任何对数据库的操作。

POJO :plain ordinary java object 无规则简单java对象

VO:value object 值对象 / view object 表现层对象

  • 主要对应页面显示(web页面/swt、swing界面)的数据对象。
  • 可以和表对应,也可以不,这根据业务的需要。
  • 可以细分包括 req、res

DO(Domain Object):领域对象,就是从现实世界中抽象出来的有形或无形的业务实体。通常可以代替部分 PO 的职责。

DTO(TO):Data Transfer Object 数据传输对象

  • 用在需要跨进程或远程传输时,它不应该包含业务逻辑。
  • 比如一张表有100个字段,那么对应的PO就有100个属性(大多数情况下,DTO内的数据来自多个表)。但view层只需显示10个字段,没有必要把整个PO对象传递到client,这时就可以用只有这10个属性的DTO来传输数据到client,这样也不会暴露server端表结构。到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO。

项目初始化

​ 引入lottery项目,配置JDK、Maven,待项目加载完成即可

开发环境配置说明
JDK1.8.0_151
Maven3.5.2
Mysqlmysql-8.0

常见问题

【1】Maven引入

​ File->Setting->Maven->“配置maven安装目录、自定义setting.xml文件所在路径和仓库映射”

​ 如果遇到项目依赖引入问题,则依次检查maven依赖引入是否正常,可调整本地仓库镜像为相应的阿里云镜像,或者查对应本地仓库依赖加载是否正常,部分依赖下载失败则可删除相关内容重新下载即可(清理对应文件夹中以.lastUpdated为后缀的文件(可批量搜索删除),随后重新尝试导入)

阿里云仓库镜像配置

# maven配置主要关注的点:本地仓库路径指向、国内镜像配置
<!-- 1.本地仓库位置 -->
 <localRepository>本地仓库路径指向</localRepository>

<!-- 2.国内镜像资源数据下载库(解决默认的中央仓库连接异常、网络访问超时问题) -->
	 <!-- 阿里云仓库 -->
	<mirror>
		<id>alimaven</id>
		<mirrorOf>central</mirrorOf>
		<name>aliyun maven</name>
		<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
	</mirror>

重复引入依赖失败,检查仓库内容,清理.lastUpdated文件随后重新导入

​ 可结合项目中飘红的依赖,自行进入指定jar指向仓库位置清理.lastUpdated文件

​ windows资源搜索较慢,可使用everything工具快速检索.lastUpdated文件並直接清理

image-20220115175358916

3.广播模式RPC过程调用

项目说明说明
项目分支dev_220101_02_buildFramework
(基于dev_220101_01_initProject分支构建新的分支结构)
项目描述构建工程完成RPC接口的实现和调用
更新说明

学习说明

知识点扩展

【1】RPC概念

​ RPC(Remote Procedure Call,远程服务调用):可以简要概述为分布式应用场景衍生的产物(分布式系统通信的解决方案)

​ 传统简单单体应用工程应用直接通过http等协议完成应用间的通信;在针对一些高并发场景单个A应用并不能满足X应用承载的系统流量,无法横向扩容,则需将A应用扩出n个A应用从而满足流量调用,在这个过程中则借助RPC层构建通信,通过这种分散方式实现流量分散。一般RPC框架会有服务降级、流量控制的功能以保证服务的高可用,在“微服务”场景中可借助RPC构建不同服务之间跨语言跨平台的相互调用

image-20220116230234217


​ 待扩展:可了解常用RPC框架(例如Netty)的一些设计原理和问题解决方案,手写扩展RPC框架


开发过程

开发步骤说明

1.数据表创建:抽奖活动表activity创建

2.项目配置:pom文件、mybatis插件配置

3.配置广播模式Dubbo

4.搭建测试工程调用RPC

数据表创建:抽奖活动表activity创建

【1】构建过程

​ 创建ddd-lottery数据库,插入activity数据表:用于配置抽奖活动的总表,用于存放活动信息,包括:ID、名称、描述、时间、库存、参与次数等

CREATE TABLE `activity` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `activity_id` bigint(20) NOT NULL COMMENT '活动ID',
  `activity_name` varchar(64) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '活动名称',
  `activity_desc` varchar(128) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '活动描述',
  `begin_date_time` datetime DEFAULT NULL COMMENT '开始时间',
  `end_date_time` datetime DEFAULT NULL COMMENT '结束时间',
  `stock_count` int(11) DEFAULT NULL COMMENT '库存',
  `take_count` int(11) DEFAULT NULL COMMENT '每人可参与次数',
  `state` tinyint(2) DEFAULT NULL COMMENT '活动状态:1编辑、2提审、3撤审、4通过、5运行(审核通过后worker扫描状态)、6拒绝、7关闭、8开启',
  `creator` varchar(64) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT '创建人',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_activity_id` (`activity_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='活动配置';

【2】问题说明

mysql建表语句问题排查

​ 此处注意mysql数据库版本问题,可在mysql命令窗口执行select version();指令查看mysql版本,所以在执行上述语句的时候会出现下述问题

image-20220115213051751

​ 问题分析:mysql版本升级5.6.5以下(低版本)(原有版本是5.5.57)默认值不支持datetime default设置CURRENT_TIMESTAMP 默认值,因此需要提升mysql版本或者相应调整datetime类型相应字段的默认值(设定为NULL,设定trigger触发时间填充),随后重新导入

​ 升级mysql版本(腾讯云宝塔升级mysql:先备份原有数据库,随后升级mysql到8.0版本),相应调整项目内容:

  • lottery-interfaces中pom.xml文件的mysql-connector-java版本

  • mybatis配置文件对应调整:

    5.7以下版本:数据库连接驱动-com.mysql.jdbc.Driver

    8.0版本:数据库连接驱动-com.mysql.cj.jdbc.Driver、url-时区设定(serverTimezone=Asia/Shanghai)

项目配置:pom文件、mybatis插件配置

模块化工程构建:工程模块说明

工程工程类型说明
lottery-主工程pom父工程构建,通过modules引入子工程
lottery-application/应用层,引用:domain
lottery-commonjar通用包,引用:
lottery-domain/领域层,引用:infrastructure
lottery-infrastructurejar基础层,引用:
lottery-interfaceswar接口层,引用:applicationrpc
lottery-rpcjarRPC接口定义层,引用:common
【1】构建过程

pom文件:项目模块maven依赖相关构建

lottery-common:packing为jar,build参数构建
lottery-infrastructure:packing为jar,build参数构建,引入相关依赖
lottery-interfaces:packing为war,build参数构建,引入相关依赖
lottery-rpc:packing为jar,build参数构建,引入相关依赖
lottery:packing为pom,build参数构建,modules引入子工程(主工程中不作依赖引入,避免多模块依赖引入混乱、冲突,但可设定控制依赖版本)

mybatis插件配置

  • 引入mybatis-spring-boot-starter依赖
# lottery-interface工程(程序出口)中引入mybatis等相关依赖配置
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.1.5-SNAPSHOT</version> 
</dependency>
  • 配置application.yml文件中mybatis(数据库访问链接、mapper扫描路径、mybatis配置文件路径)

    注意mysql版本对应配置

# lottery-interfaces工程下application.yml编辑(通过副配置文件切换实现不同环境下配置文件的版本管理)
server:
  port: 80

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://127.0.0.1:3306/bugstack?useUnicode=true
    driver-class-name: com.mysql.jdbc.Driver

  # 副配置文件激活
  profiles:
    active: dev

mybatis:
  mapper-locations: classpath:/mybatis/mapper/*.xml
  config-location:  classpath:/mybatis/config/mybatis-config.xml

配置广播模式Dubbo

【1】构建说明

​ 广播模式Dubbo构建意义:最早 RPC 的设计和使用都是依赖于注册中心,需要把服务接口信息在程序启动的时候,推送到一个统一的注册中心,在其他需要调用 RPC 接口的服务上再通过注册中心的均衡算法来匹配可以连接的接口落到本地保存和更新。这样的标准的使用方式可以提供更大的连接数和更强的负载均衡作用(目前状态尽可能减少学习成本,后续可调整为注册中心模式Dubbo

​ Dubbo:https://dubbo.apache.org

yml配置

# lottery-interfaces工程下application.yml编辑(核心配置可放置在主配置文件,副配置文件适配不同环境下配置的扩展)

# Dubbo 广播方式配置
dubbo:
  application:
    name: DDD-Lottery
    version: 1.0.0
  registry:
    address: N/A #multicast://224.5.6.7:1234
  protocol:
    name: dubbo
    port: 20880
  scan:
    base-packages: cn.itedus.lottery.rpc

​ 广播模式的配置唯一区别在于注册地址,registry.address = multicast://224.5.6.7:1234,服务提供者和服务调用者都需要配置相同的📢广播地址。或者配置为 N/A 用于直连模式使用

配置参数说明
application配置应用名称(name)和版本(version)
protocol配置的通信协议和端口
scan相当于 Spring 中自动扫描包的地址,将此包下的所有 rpc 接口都注册到服务中

定义和开发RPC接口

【1】构建过程

​ 由于 RPC 接口在通信的过程中,需要提供接口的描述文件,即接口的定义信息

工程说明代码引入
lottery-common引入相关常量类和响应结果封装类
lottery-infrastructure引入Activity实体和对应dao操作接口封装IActivityDao
lottery-interfaces引入mybatis相关配置(mapper文件)、定义

IActivityBooth接口定义:封装创建、查询、修改、审核相关的接口

// lottery-rpc 定义接口
public interface IActivityBooth {
    ActivityRes queryActivityById(ActivityReq req);
}

ActivityBooth接口开发:实现IActivityBooth

// lottery-interfaces 接口实现
@Service
public class ActivityBooth implements IActivityBooth {

    @Resource
    private IActivityDao activityDao;

    @Override
    public ActivityRes queryActivityById(ActivityReq req) {

        Activity activity = activityDao.queryActivityById(req.getActivityId());

        ActivityDto activityDto = new ActivityDto();
        activityDto.setActivityId(activity.getActivityId());
        activityDto.setActivityName(activity.getActivityName());
        activityDto.setActivityDesc(activity.getActivityDesc());
        activityDto.setBeginDateTime(activity.getBeginDateTime());
        activityDto.setEndDateTime(activity.getEndDateTime());
        activityDto.setStockAllTotal(activity.getStockAllTotal());
        activityDto.setStockDayTotal(activity.getStockDayTotal());
        activityDto.setTakeAllCount(activity.getStockAllTotal());
        activityDto.setTakeDayCount(activity.getStockDayTotal());

        return new ActivityRes(new Result(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo()), activityDto);
    }

}

# 在lottery-infrastructure中定义IActivityDao接口,在lottery-interfaces层中封装实现(resources下配置mapper文件)
【2】问题说明

搭建测试工程调用RPC

测试步骤说明

  • 将lottery-rpc导入本地仓库并检查

  • 搭建RPC工程测试接口

  • 启动lottery-interfaces工程,测试接口访问

【1】构建过程

​ 初始化一个springboot项目,引入RPC接口配置和相同广播模式的调用

1.将lottery-rpc导入本地仓库并检查

​ 方式1:通过idea的terminal窗口借助maven指令导入

​ 方式2:选定lottery主工程右键Run Maven选项->install,检查日志一一排查问题

​ 构建完成可检查本地仓库是否正常相关的文件和自定义依赖

image-20220117003434378

image-20220117003412808

2.POM 引入lottery-rpc的jar依赖

// 此处版本依赖相关需与lottery-rpc中的定义一一对应
				<!-- 引入lottery-rpc -->
        <dependency>
            <groupId>cn.itedus.lottery</groupId>
            <artifactId>lottery-rpc</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

3.配置广播模式Dubbo

# 配置application.yml文件(端口号设定与lottery-interfaces工程中的server不同,避免冲突)
server:
  port: 8081

# Dubbo 广播方式配置(保证与Dubbo接口提供者一致)
dubbo:
  application:
    name: Lottery
    version: 1.0.0
  registry:
    address: N/A # multicast://224.5.6.7:1234 
  protocol:
    name: dubbo
    port: 20880

4.创建单元测试类,测试RPC接口

​ 在test区域中创建ApiTest测试类

image-20220117005755566

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Reference(interfaceClass = IActivityBooth.class, url = "dubbo://127.0.0.1:20880")
    private IActivityBooth activityBooth;

    @Test
    public void test_rpc() {
        ActivityReq req = new ActivityReq();
        req.setActivityId(100001L);
        ActivityRes result = activityBooth.queryActivityById(req);
        logger.info("测试结果:{}", JSON.toJSONString(result));
    }

}
【2】问题说明

maven install失败

​ 一开始直接对lottery-rpc进行导入,install失败检查日志如下图所示,提示lottery-rpc工程中引入了lottery-common因此需要先将lottery-common导入本地仓库,但还是出现相关错误提示

image-20220117001919368

​ 实际上针对多模块工程构建,初始化应对lottery主工程做install操作,maven多模块项目构建,父maven项目会自动构建子maven项目,因此先执行lottery父工程的mvn install操作,随后检查仓库中的依赖模块是否正常构建。初始化成功之后,后续子模块的更新则只需要单独对指定的子模块进行clean install操作即可。其概念类似于springboot的spring-boot-starter-parent

接口测试

测试说明

  • 启动lottery-interfaces工程

  • 测试lottery-test工程中的test_rpc方法

    访问出错,则依次根据日志排查工程配置问题:一般常见错误数据库连接、dubbo配置

​ 测试前先往activity表中填充数据,否则测试的时候会出现空指针异常(可通过打断点分析进行测试)

image-20220117012657848

总结

学习内容

  • DDD基本概念简单过滤

  • 基于DDD的lottery项目构建,基础技术栈知识点扩展

  • RPC工程的配置和应用

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3