【luckydraw-ddd】领域开发01-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
工程说明
名称 | 系统 | 作用 |
---|---|---|
分布式核心功能服务系统 | Lottery | 提供抽奖业务领域功能,以分布式部署的方式提供 RPC 服务。 |
网关API服务 | Lottery-API | 网关服务,提供; H5 页面抽奖、公众号开发回复消息抽奖 |
C端用户系统 | lottery-front | vue H5 lucky-canvas 大转盘抽奖界面,讲解 vue 工程创建、引入模块、开发接口、跨域访问和功能实现 |
B端运营系统 | Lottery-ERP | 满足运营人员对于活动的查询、配置、修改、审核等操作 |
分库分表路由组件 | db-router-spring-boot-starter | 本项目依赖自研分库分表组件,需要下载后构建 开发一个基于 HashMap 核心设计原理,使用哈希散列+扰动函数的方式,把数据散列到多个库表中的组件,并验证使用。 自研数据库路由组件,可构建到本地仓库进行应用 |
测试验证系统 | Lottery-Test | 用于测试验证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)去理解和消化领域知识,从领域知识中提取和划分为一个一个的子领域(核心子域,通用子域,支撑子域),并在子领域上建立模型,再重复以上步骤,这样周而复始,构建出一套符合当前领域的模型
学习说明
掌握DDD分层架构概念、模块分层的职责,以及RPC层单独拆分的目的等相关概念,一开始接触可能会有点懵,后续结合项目应用时间进行分析,提出问题、重温概念、分析问题并解决(目前概念梳理还是比较模糊,暂不纠结概念,结合实践项目操作再回过头去结合自己的理解重温概念)
结合MVC概念了解DDD四层架构以及对应模块的内容
DDD充血模型结构在独立领域开发和引用RPC框架不同场景下的优缺点
(先提出问题,待后续实践扩展!!)
贫血模型&充血模型设计对比
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,待项目加载完成即可
开发环境 | 配置说明 |
---|---|
JDK | 1.8.0_151 |
Maven | 3.5.2 |
Mysql | mysql-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
文件並直接清理
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构建不同服务之间跨语言跨平台的相互调用
待扩展:可了解常用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版本,所以在执行上述语句的时候会出现下述问题
问题分析: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-common | jar | 通用包,引用:无 |
lottery-domain | / | 领域层,引用:infrastructure |
lottery-infrastructure | jar | 基础层,引用:无 |
lottery-interfaces | war | 接口层,引用:application 、rpc |
lottery-rpc | jar | RPC接口定义层,引用: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,检查日志一一排查问题
构建完成可检查本地仓库是否正常相关的文件和自定义依赖
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测试类
@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导入本地仓库,但还是出现相关错误提示
实际上针对多模块工程构建,初始化应对lottery主工程做install操作,maven多模块项目构建,父maven项目会自动构建子maven项目,因此先执行lottery父工程的mvn install操作,随后检查仓库中的依赖模块是否正常构建。初始化成功之后,后续子模块的更新则只需要单独对指定的子模块进行clean install操作即可。其概念类似于springboot的spring-boot-starter-parent
接口测试
测试说明
启动lottery-interfaces工程
测试lottery-test工程中的
test_rpc
方法访问出错,则依次根据日志排查工程配置问题:一般常见错误数据库连接、dubbo配置
测试前先往activity表中填充数据,否则测试的时候会出现空指针异常(可通过打断点分析进行测试)
总结
学习内容
DDD基本概念简单过滤
基于DDD的lottery项目构建,基础技术栈知识点扩展
RPC工程的配置和应用