Matrix Framework 设计与使用文档
目录
1. 框架概述
1.1 什么是 Matrix Framework
Matrix Framework 是一套基于 DDD(领域驱动设计)+ CQS(命令查询分离) 架构理念的企业级 Java 开发框架,专为复杂业务系统设计。框架提供了从领域建模、代码生成、数据持久化、消息通信到网关接入的完整技术栈。
1.2 设计理念
| 理念 | 说明 |
|---|---|
| DDD 分层 | 严格按照 Adapter / Application / Domain / Infrastructure 四层划分 |
| CQS | Command(写)走领域模型,Query(读)直查数据模型,读写分离 |
| 依赖倒置 | Domain 层只定义抽象(Gateway / Repository),实现位于 Infrastructure |
| 防腐层 | 通过 Gateway 接口隔离外部系统技术细节,保护领域模型纯净 |
| 插件化 | 核心能力通过 easy-plugin 模块支持 PF4J 插件扩展 |
1.3 技术栈
| 类别 | 技术 |
|---|---|
| 语言 | Java 17(编译目标,兼容 Java 8 语法) |
| 框架 | Spring Boot 2.7.x |
| ORM | MyBatis-Plus / TkMapper(双实现) |
| 消息队列 | RocketMQ / Kafka / RabbitMQ / AutoMQ / Redis / DBMS / ZeroMQ(七 connector 可选) |
| API | GraphQL / REST / gRPC |
| 规则引擎 | easy-rules(MVEL / JEXL / LiteFlow),easy-fsm(FSM),easy-flows(Warm-Flow 工作流) |
| 代码生成 | AVDL(Avro IDL)领域建模描述 |
| 对象存储 | easy-oss(MinIO 后端,SPI 多后端可扩展) |
| 分布式任务 | db-scheduler(嵌入式 SDK,MySQL 持久化) |
2. 架构设计
2.1 整体分层架构
2.2 DDD 模块分层
每个业务域(如 User、Asset、Alarm)都严格遵循以下模块划分:
| 模块 | 职责 | 依赖关系 |
|---|---|---|
| client | Cmd / Qry / Evt / 枚举定义 | 无内部依赖(最底层) |
| domain | 聚合/实体/值对象、领域服务、Gateway 抽象 | → client |
| application | AppService(跨聚合编排)、QueryHandler、EventHandler | → domain + client |
| infrastructure | Manager、Convertor、GatewayImpl(实现) | → domain |
| api | 对外 API 契约(Dubbo 等),给二方 / 三方团队使用 | 无内部依赖(同 client) |
| adapter-bff | 前端适配(GraphQL/REST)、用户态处理 | → application + domain |
| adapter-bg | 后端适配(MQ/Job) | → application + domain |
调用规则:
- 简单场景(单聚合、无跨域协调):Adapter → DomainService
- 复杂场景(跨聚合编排、多个领域服务协作):Adapter → AppService → DomainService
- 查询场景:Adapter → QueryHandler(Application 层)→ Manager(Infrastructure 层)
- 事件场景:MQ Consumer(Adapter 层)→ EventHandler(Application 层)→ DomainService
2.3 CQS 执行路径
写操作(Command)
读操作(Query)
读操作不走 Repository:
Repository是特殊的 Gateway,只供 DomainService 使用(写路径)。 Application 直接调用 Infrastructure 的Manager读取数据,读写隔离,避免读业务绑架写业务、影响核心写路径的稳定性。
3. 模块详解
3.1 框架模块结构
matrix-framework/
├── kernel-* # 内核层(强制 DDD/CQS 基础类)
│ ├── kernel-api/ # Result / PageResult / MultiResult
│ ├── kernel-annotation/ # DDD 建模注解(DomainObject/Command/Query 等,IDEA 插件配套)
│ ├── kernel-client/ # Cmd / Qry / Evt 基类
│ ├── kernel-common/ # JsonMapper / YamlMapper / 条件表达式 / 调度
│ ├── kernel-adapter/ # 异常包装 AOP / JWT / 权限
│ ├── kernel-domain/ # AggregateRoot / StatefulDomainService / FsmRegistry
│ ├── kernel-dal/ # 数据访问层(mp / tk / es 实现)
│ └── kernel-dependencies/ # BOM 版本管理
│
├── components/ # 组件层(可插拔能力)
│ ├── easy-messaging/ # 消息多后端抽象(7 connector)
│ ├── easy-event-bus/ # 领域事件总线
│ ├── easy-flows/ # 工作流引擎(Warm-Flow 薄封装,含可选设计器)
│ ├── easy-fsm/ # 有限状态机(cola-statemachine 封装)
│ ├── easy-rules/ # 规则引擎(MVEL / JEXL / LiteFlow)
│ ├── easy-store/ # 通用定义存储(内存/文件/Redis,可监听)
│ ├── easy-graphql/ # GraphQL 服务端执行引擎
│ ├── easy-idgen/ # 分布式 ID 生成器(雪花/Leaf)
│ ├── easy-lock/ # 分布式锁(Redis + JDBC 双实现)
│ ├── easy-validate/ # 流式校验器
│ ├── easy-plugin/ # PF4J 插件化框架
│ ├── easy-rbac/ # RBAC 权限控制(可选 starter)
│ ├── easy-cache/ # 多级缓存(L1+Caffeine L2+Redis)+ 广播失效
│ ├── easy-oss/ # 对象存储(MinIO 后端,SPI 多后端可扩展)
│ ├── easy-schedule/ # 分布式定时任务(db-scheduler 嵌入式)
│ └── easy-socketio/ # Socket.IO 实时推送(握手鉴权 SPI + 房间协议 + Redis 集群模式)
│
└── middlewares/ # 中间件层(面向外部接入)
├── easy-gateway/ # API 网关(GraphQL 入口)
└── easy-notification/ # 实时通知中心(基于 easy-socketio 重写:推送 API + chat 事件)
3.2 Kernel 内核模块
kernel-client — 领域基础类
提供 DDD + CQS 所需的所有基类和接口:
| 类/接口 | 用途 |
|---|---|
BaseCommand | 命令基类,继承 HasTransactor(操作人信息) |
BaseQuery / BasePageQuery | 查询基类,支持分页 |
BaseDomainEvent | 领域事件基类 |
BaseDO | 数据对象基类 |
Pagination | 分页结果封装 |
HasTransactor | 操作人信息接口(userId、tenantId 等) |
HasBound | 数据绑定接口 |
使用示例:
// 定义命令
@Data
public class CreateUserCmd extends BaseCommand {
private String name;
private String email;
}
// 定义查询
@Data
public class PageUserQry extends BasePageQuery {
private String name;
}
// 定义领域事件
@Data
public class UserCreatedEvt extends BaseDomainEvent {
private String userId;
private String name;
}
kernel-common — 公共工具
| 模块 | 说明 |
|---|---|
util.JsonMapper | JSON 序列化/反序列化工具 |
util.HttpClientUtil | HTTP 客户端工具 |
condition.* | 条件表达式 AST(Compare / Join / Func) |
time.DateUtil | 日期工具 |
parameter.Parameters | 参数处理 |
scheduling.RedisScheduler | Redis 调度器 |
kernel-dal-mp — MyBatis-Plus 数据访问
核心类 RepositorySupport 提供了完整的 CRUD + 分页能力:
| 方法 | 说明 |
|---|---|
insert(entity, hasTransactor) | 插入,自动填充操作人 |
update(entity, hasTransactor) | 更新,自动填充操作人 |
deleteById(id, hasTransactor) | 删除,自动填充操作人 |
findById(id) | 根据 ID 查询 |
findOne(wrapper) | 条件查询单条 |
list(wrapper) | 条件查询列表 |
page(page, wrapper) | 分页查询 |
toPagination(page, convertor) | 将 MyBatis-Plus Page 转为框架 Pagination |
kernel-dal-tk — TkMapper 数据访问
与 kernel-dal-mp 功能相同,基于 TkMapper 实现。项目可根据需要选择使用。
3.3 Components 组件层
easy-messaging — 消息总线(通用消息)
提供通用的消息发送/消费能力,支持 7 种消息中间件:
| Connector | 配置前缀 | 说明 |
|---|---|---|
| RocketMQ | easy.rocketmq.* | 阿里开源消息队列 |
| Kafka | easy.kafka.* | Apache Kafka |
| RabbitMQ | easy.rabbitmq.* | AMQP 消息队列 |
| AutoMQ | easy.automq.* | 云上 Kafka 兼容队列 |
| Redis | - | 基于 Redis Pub/Sub |
| DBMS | - | 基于数据库表轮询 |
| ZeroMQ | easy.zeromq.* | 轻量级嵌入式消息队列 |
核心接口:
BackendProducer— 消息生产者(同步/异步/单向,有序/无序)BackendConsumer— 消息消费者(注册监听器、启动消费)MessageListener<T>— 消息监听器基类@Consumer— 消费者注解(指定 topic、tag 等)
使用示例:
// 发送消息
@Autowired
private BackendProducer producer;
public void sendMessage() {
Result result = producer.syncSend("order.created",
new GenericMessage<>(orderData));
}
// 消费消息
@Consumer(topic = "order.created")
public class OrderCreatedListener extends MessageListener<OrderCreatedEvent> {
@Override
public void onMessage(Headers headers, OrderCreatedEvent message) {
log.info("收到订单创建事件: {}", message);
}
}
easy-event-bus — 领域事件总线
专为 DDD/CQS 架构设计的事件总线,用于领域事件的发布与订阅:
配置前缀只有一个
cqrs.*,且只描述「聚合类型 → Topic」的通道映射;各 MQ 的连接参数属于 easy-messaging 的 connector 前缀(easy.rocketmq.*/easy.kafka.*/easy.zeromq.*/easy.rabbitmq.*/easy.automq.*)。 完整配置见 §6.1 与 easy-event-bus USAGE。
| 配置项 | 说明 |
|---|---|
cqrs.channels[*].aggregates | 该通道承载的聚合类型名列表 |
cqrs.channels[*].topic | 映射到的后端 topic / channel |
cqrs.channels[*].eventClass | 可选,通道事件基类全限定名 |
cqrs.default-channel | 未命中映射时的兜底通道 |
消费语义:单个 handler 异常只记日志、不阻断同一消息的其他 handler;幂等默认关闭(
DuplicateMessageDetector默认 Noop,防重由业务侧可靠手段承担)。详见 USAGE §6/§8。
核心概念:
DomainEventPublisher— 领域事件发布器DomainEventDispatcher— 领域事件分发器MessageProducerImplementation— 消息生产者接口(各 Connector 实现)MessageConsumerImplementation— 消息消费者接口(各 Connector 实现)ChannelMapping— 聚合类型 → Topic 映射
使用示例:
// 在 DomainService 中发布事件
@DomainService(domain = "User")
public class UserDomainService extends BaseDomainService<User, UserRepository> {
@CommandHandler(name = "createUser")
public User createUser(CreateUserCmd cmd) {
User user = convertor.toDomain(cmd);
repository.insert(user, cmd);
// 发布领域事件:首参是聚合根类型(决定通道映射),不是事件类型
publish(User.class, user.getUserId(),
new UserCreatedEvt(user.getUserId(), user.getName()));
return user;
}
}
// 在 ApplicationService 中订阅事件
@ApplicationService(domain = "User")
public class UserAppService extends BaseAppService {
@EventHandler
public void handleUserCreated(UserCreatedEvt event) {
log.info("用户创建事件: userId={}", event.getUserId());
}
}
easy-flows — 工作流引擎(Warm-Flow)
基于 Warm-Flow 的轻量、可内嵌工作流引擎薄封装,ORM 对接 kernel-dal-mp(MyBatis-Plus),
作为 DDD 的流程 gateway 在基础设施层接入,不包含业务 Service / Cmd / Qry。
流程定义为 JSON,表数量少(7 张):
| 能力 | 说明 |
|---|---|
WorkflowEngine | 引擎能力门面,返回 Result<T> |
DefaultWorkflowEngine | 委托 Warm-Flow 原生 Service(Def/Ins/Task/HisTask/User)的默认实现 |
FlowTask | 引擎层任务视图(待办 / 已办查询结果) |
StoreDefinitionLoader | 从 easy-store 加载流程定义并部署(支持热更新) |
操作流 —— 统一为 Result<T>:deploy(json) / start(...) / complete(...) /
reject(...) / transfer(...) / cancel(...) / terminate(...);待办、已办分页查询。
扩展点桥接(遵循「不造并行 SPI」,直接桥接 Warm-Flow 原生扩展点):
| Warm-Flow 扩展点 | easy-flows 处理 |
|---|---|
PermissionHandler | ✅ PermissionBridge:由 CurrentUserProvider 扩展点(extends org.pf4j.ExtensionPoint)提供 kernel-client.User 解析当前办理人 |
GlobalListener | ✅ FlowEventListener:任务完成 → 发布 FlowTaskFinishedEvent(BaseDomainEvent) |
DataFillHandler | ⛔ 默认实现已满足,无需桥接 |
依赖方向:
easy-flows仅依赖内核数据(kernel-api/kernel-client/kernel-common/kernel-dal-mp)与组件(easy-plugin/ 可选easy-store、easy-rules),不反向依赖接入层 (kernel-adapter)。「当前用户从哪来」抽象为CurrentUserProvider扩展点,由使用方在 infrastructure 内实现并提供;未提供时@ConditionalOnBean不触发,办理人桥接自动降级。
使用方接入示例(提供当前用户来源):
public class GatewayUserProvider implements CurrentUserProvider {
@Override
public User currentUser() {
// 内部访问使用方自己的用户上下文(如 adapter 的 GatewayContext)
...
}
}
流程设计器(可选):引入
warm-flow-plugin-ui-sb-web(optional 依赖,版本走 BOM)即可激活免搬家的可视化设计器——WarmFlowUiConfig把/warm-flow-ui/**代理到 jar 内前端资源,WarmFlowController提供/warm-flow/**REST。需放行这两类路径;办理人/类别/表单三扩展点由使用方实现HandlerSelectService/CategoryService/FormPathService。详见 easy-flows USAGE。
easy-fsm — 有限状态机
基于 cola-statemachine 的状态机薄封装,用于聚合根内部的状态流转(单实体状态):
| 组件 | 说明 |
|---|---|
Fsm / FsmBuilder | 状态机定义与执行 |
State / Event / Transit | 状态、事件、迁移三元组 |
FsmRegistry | 状态机注册表(kernel-domain) |
visitor.PlantUMLVisitor | 导出 PlantUML 状态图 |
easy-rules — 规则引擎
| 组件 | 说明 |
|---|---|
RulesExecutor / EngineExecutor | 规则执行器 |
engine.MVELRuleExecutor / JEXLRuleExecutor / LiteFlowExecutor | 三种实现 |
loader.RulesLoader / MVELRulesLoader | 规则加载 |
config.EngineAutoConfiguration | 多引擎实例自动配置(依赖 easy-store) |
配置示例:
# 规则引擎实例配置
easy.engines.instances[0].beanName=myRuleEngine
easy.engines.instances[0].type=rules
easy.engines.instances[0].implementation=mvel
easy.engines.instances[0].enabled=true
easy.engines.instances[0].namespace=test
easy-store — 通用定义存储
通用的「定义 / 配置」存取框架,供规则引擎、工作流等复用作为定义来源:
| 组件 | 说明 |
|---|---|
ConfigStorage / WatchableConfigStorage | 定义存储 / 可监听存储 |
ConfigRepository | 存储仓库接口 |
repository.FileRepository / RedisRepository / InMemoryRepository | 文件 / Redis / 内存实现 |
ConfigChangeEvent / ConfigChangeListener / WatchHandle | 配置变更监听 |
easy-graphql — GraphQL 服务端
提供 GraphQL 的注解驱动开发。无自动配置,运行时通过 easy-gateway 的 GraphQlConfig 手动装配。
| 注解 | 用途 |
|---|---|
@GqlComponent | GraphQL 组件(DataFetcher 承载类) |
@GqlFetcher | DataFetcher 定义 |
@GqlMutation | Mutation 定义 |
@GqlSubscription | Subscription 定义 |
@GqlRepository | 仓储定义 |
@GqlConnector | 连接器定义(@connect 自研链路) |
@GqlArgument | DataFetcher 参数绑定 |
@GqlDirective | 自定义 Directive 定义 |
@GqlBatching / @BatchLoaderType | 批量加载定义 |
核心类:
ExecutionGraphQlService/GraphQlSource/DefaultExecutionGraphQlService— 执行引擎入口GqlExecutionConfigurer/GqlExecutionDetectionSupport— 注解扫描与自动接线FederationSchemaFactory— Apollo Federation schema 合成observation/GraphQlObservationInstrumentation— 可观测性 Instrumentation(metrics/tracing,待接入)data/connect/*— @connect 自研链路(HTTP/Dubbo/gRPC/Spring/GraphQL 5 种 Connector)
v1.2.0 清理:删除了废弃的 pagination 链(
data/pagination/整包 + 依赖它的 7 个 query 孤儿类 + gateway 死方法),删除 support 孤儿(DocumentSource/FutureUtil 等 4 个)。注解包、@connect 链路、observation 包均保留。
easy-cache — 多级缓存
L1 本地 + L2 分布式的两级缓存,支持 Redis Pub/Sub 广播失效保持多节点一致。
缓存层级:
| 层级 | 实现 | 说明 |
|---|---|---|
| L1 LOCAL | Caffeine | JVM 内本地缓存,读写最快 |
| L2 REMOTE | Redis | 分布式缓存,跨节点共享 |
核心注解:
@EasyCacheable— 声明式缓存@EasyCacheEvict— 声明式失效@EasyCachePut— 声明式更新
配置:
easy.cache.enabled=true
easy.cache.broadcast.enabled=true
easy.cache.broadcast.channel=easy:cache:evict
# Caffeine L1
easy.cache.local.max-size=10000
easy.cache.local.expire-after-write=300s
v1.2.0 修复:广播订阅改为懒初始化,不再在
@PostConstruct里硬连 Redis。首次调用缓存失效时才尝试订阅,Redis 不可达则静默降级为单机模式,启动日志干净无大段异常堆栈。
easy-oss — 对象存储
后端中立的对象存储客户端门面,复用「门面 → 抽象代理 → SPI → Connector」分层,业务只依赖 ObjectStorage 接口,后端可替换:
| 组件 | 说明 |
|---|---|
ObjectStorage / DefaultObjectStorage | 存储门面 / 默认实现(key 生成 + 委托后端) |
BackendObjectStorage | 后端 SPI,storage/minio/MinioObjectStorage 为首个实现 |
ObjectKeyFactory / DefaultObjectKeyFactory | 对象 key 生成({tenant}/{endpoint}/{yyyy/MM/dd}/{uuid}-{name}) |
FileObject | 上传返回值(objectName / bucket / size / metadata) |
OssProperties | easy.oss.* 配置(含 minio.endpoint/access-key/secret-key) |
能力:上传(putString/putBytes/putStream/putFile)、下载、元数据、预签名 URL(直下/直传)、删除、桶管理与对象列表。MinIO 客户端为 optional scope,未配置时组件自动关闭。
配置:
easy.oss.enabled=true
easy.oss.default-bucket=matrix
easy.oss.minio.endpoint=http://127.0.0.1:9000
easy.oss.minio.access-key=admin
easy.oss.minio.secret-key=password
中间件服务端(规划):浏览器直传、统一鉴权、URL 代理三个阶段列入演进规划,本期(客户端组件)未实现。
easy-schedule — 分布式定时任务
基于 db-scheduler 的嵌入式分布式定时任务组件。不部署独立调度服务,依赖 MySQL 单表持久化,通过 DB 行锁保证集群单实例执行。
特性:
| 特性 | 说明 |
|---|---|
| Cron 周期 | @Job(cron = "0 */5 * * * *") 注解驱动 |
| 一次性/延迟 | fireOnce() 动态触发,支持 POJO payload |
| 分片并行 | @Job(shardCount = 3) 多节点同时跑不同分片 |
| 集群单实例 | MySQL scheduled_tasks 表 + 行锁,多节点抢占 |
| 无 ZK/Redis | 仅依赖 MySQL |
使用示例:
@Component
public class ExampleJobs {
@Job(name = "clock", cron = "0 */1 * * * *", desc = "每分钟示例任务")
public void clock(ExecutionContext ctx) {
log.info("tick, instanceId={}", ctx.getInstanceId());
}
@Job(name = "shard-example", cron = "0 */2 * * * *", shardCount = 3)
public void shardExample() {
// 内部按 shardIndex/shardTotal 路由,各分片并行执行
}
}
配置:
easy.schedule.enabled=true
easy.schedule.threads=10
easy.schedule.polling-interval=5s
MySQL DDL
-- db-scheduler 引擎表(组件固定使用 sch_tasks 表名)
CREATE TABLE sch_tasks (
task_name VARCHAR(100),
task_instance VARCHAR(100),
task_data LONGBLOB,
execution_time DATETIME(6),
picked TINYINT(1),
picked_by VARCHAR(50),
last_success DATETIME(6),
last_failure DATETIME(6),
consecutive_failures INT,
last_heartbeat DATETIME(6),
version BIGINT,
priority SMALLINT,
PRIMARY KEY (task_name, task_instance),
INDEX idx_execution_time_picked (execution_time, picked)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- easy-schedule 动态 cron 配置表
CREATE TABLE sch_job_config (
job_code VARCHAR(96) PRIMARY KEY,
cron VARCHAR(64) NOT NULL COMMENT '6 位 cron 表达式',
enabled TINYINT(1) DEFAULT 1,
shard_count INT DEFAULT 1,
payload_json JSON COMMENT '默认 payload(可选)',
description VARCHAR(256),
owner VARCHAR(64),
created_at DATETIME,
updated_at DATETIME,
INDEX idx_enabled (enabled)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
版本说明:v1 支持 cron + 一次性 + 分片并行。v2 规划 DAG 编排(jgrapht)、执行历史记录表、管理台可视化。
easy-idgen — ID 生成器
提供分布式 ID 生成:
| 实现 | 配置前缀 | 说明 |
|---|---|---|
| Snowflake | matrix.id-generator.snowflake.* | 雪花算法 |
| Leaf | - | 美团 Leaf 号段模式 |
| Simple | - | 本地简单 ID(测试用) |
配置示例:
matrix.id-generator.snowflake.enabled=true
matrix.id-generator.snowflake.worker-id=1
easy-lock — 分布式锁
注解驱动的分布式锁:
@Locked(expression = "#userId", retry = @Retryable(interval = 100))
public void doSomething(String userId) {
// 被保护的代码
}
easy-validate — 流式校验器
提供流畅的校验 API:
validate(user)
.notNull("name")
.length("email", 5, 100)
.regex("phone", "^1[3-9]\\d{9}$")
.throwIfInvalid();
easy-plugin — PF4J 插件扩展
基于 PF4J(Plugin Framework for Java)的插件化能力(Spring Boot Fat-Jar 场景二次封装):
FatJarPluginManager— 胖包插件管理器(重写 Loader / Repository / DescriptorFinder 三大工厂)FatJarPluginLoader— 插件加载器(直接使用 Spring Boot 主 ClassLoader)PluggableUtil/ExecutionUtil— 扩展旁路缓存 + 节点路径(Pattern)匹配执行Pattern— ACCURATE / CHILDREN / DESCENDANTS / WILDCARD 匹配策略
升级策略:依赖已回退到官方
pf4j-spring 0.8.0/pf4j 3.8.0,原定制逻辑以同 FQCN shadow 类内置在 easy-plugin 源码(Extension/ExtensionDescriptor/AbstractExtensionFinder/SpringPluginManager/FileUtils),由 classpath 优先级覆盖官方 jar,摆脱对定制 jar 的依赖。Fat-Jar 模式不支持插件热部署(需重启应用)。
3.4 Middlewares 中间件层
easy-gateway — API 网关
提供协议适配、路由、限流、鉴权等网关能力。
easy-notification — 实时通知中心
基于 easy-socketio 的实时通知中间件:服务器装配、握手鉴权(AuthListener SPI)、三级房间协议(user/broadcast/product)、Redis 分布式均由组件层提供,中间件只含业务层——NotificationPusher 推送门面(toUser / toProduct / broadcast)与 chat 事件处理器。独立部署形态(fatjar + main 入口)。详见 设计文档 与 使用文档。
4. 快速上手
4.1 创建一个 Matrix 项目
步骤 1:创建父项目
<!-- pom.xml -->
<parent>
<groupId>io.matrix</groupId>
<artifactId>components</artifactId>
<version>1.2.1-SNAPSHOT</version>
</parent>
步骤 2:创建标准模块结构
your-project/
├── your-client/ # Cmd/Qry/Evt 定义
├── your-domain/ # 领域模型
├── your-application/ # 应用服务
├── your-infrastructure/ # 基础设施实现
├── your-api/ # 对外 API
├── your-adapter-bff/ # 前端适配
├── your-adapter-bg/ # 后端适配
└── pom.xml # 父 POM
步骤 3:添加核心依赖
<!-- 内核模块 -->
<dependency>
<groupId>io.matrix</groupId>
<artifactId>kernel-client</artifactId>
</dependency>
<dependency>
<groupId>io.matrix</groupId>
<artifactId>kernel-common</artifactId>
</dependency>
<dependency>
<groupId>io.matrix</groupId>
<artifactId>kernel-dal-mp</artifactId> <!-- 或 kernel-dal-tk -->
</dependency>
<!-- 消息总线(根据需要选择 Connector) -->
<dependency>
<groupId>io.matrix</groupId>
<artifactId>easy-event-bus</artifactId>
</dependency>
<dependency>
<groupId>io.matrix</groupId>
<artifactId>easy-messaging</artifactId>
</dependency>
<!-- Connector 依赖(根据选择的消息中间件) -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>3.4.1</version>
</dependency>
4.2 参考实现:iot-care 项目
iot-care(ic-backend 模块)是一个完整的 Matrix 框架实战项目(独立仓库,需另行获取),包含:
| 领域 | 聚合根 | 说明 |
|---|---|---|
| User | Tenant、Customer、User | 多租户用户体系 |
| Asset | Asset、Device | 资产与设备管理 |
| Alarm | Alarm | 告警与状态流转 |
| Dashboard | Dashboard | 数据仪表盘 |
| Widget | Widget | 仪表盘组件 |
| EntityView | EntityView | 跨实体视图 |
项目结构参考:
ic-backend/
├── eager-client/ # Cmd/Qry/Evt 定义
├── eager-domain/ # 领域模型 + Gateway 抽象
├── eager-application/ # AppService 用例编排
├── eager-infrastructure/ # Manager + Convertor + GatewayImpl
├── eager-api/ # 对外 API
├── eager-adapter-bff/ # GraphQL 前端适配
├── eager-adapter-bg/ # MQ 后端适配
└── design/
├── local/ # AVDL 领域建模文件
└── sql/ # 数据库初始化脚本
5. 核心组件使用指南
5.1 领域建模(AVDL)
Matrix 框架使用 AVDL(Avro IDL)进行领域建模。建模文件存放于 design/local/ 目录。
文件结构:
design/local/
├── User.avdl # 用户领域
├── Asset.avdl # 资产领域
├── Alarm.avdl # 告警领域
└── ...
AVDL 三段式结构:
// ============== 类型协议区 ==============
@namespace("com.example.user.domain")
protocol UserTypes {
@DomainObject(domain = "User", type = AGGREGATE_ROOT)
record User {
@DomainId
string userId;
string name;
string email;
}
enum UserAuthority { TENANT_ADMIN, CUSTOMER_USER }
}
---
// ============== 领域服务区 ==============
@namespace("com.example.user.service")
protocol UserDomainService {
@DomainService(domain = "User")
interface UserDomainService {
@CommandHandler(name = "createUser")
User createUser(CreateUserCmd cmd);
@CommandHandler(name = "updateUser")
User updateUser(UpdateUserCmd cmd);
}
}
---
// ============== 应用服务区 ==============
@namespace("com.example.user.service")
protocol UserApplicationService {
@ApplicationService(domain = "User")
interface UserAppService {
@QueryHandler(name = "pageUser")
Pagination<User> pageUser(PageUserQry qry);
}
}
5.2 领域服务实现(DomainService)
@Service
@DomainService(domain = "User")
@RequiredArgsConstructor
public class UserDomainService extends BaseDomainService<User, UserRepository> {
private final UserDomainConvertor convertor;
@CommandHandler(name = "createUser", desc = "创建用户", steps = {
@Step(name = "参数校验"),
@Step(name = "构建聚合根"),
@Step(name = "持久化"),
@Step(name = "发布事件")
})
public User createUser(CreateUserCmd cmd) {
// 1. 参数校验:BaseService 继承 ValidateSupport,提供
// validate(entity, Consumer<ValidatorDelegate>) 与 validate(entity, yavi Validator<T>) 两种形式,
// 用法见 easy-validate USAGE
// 2. 构建聚合根
User user = convertor.toDomain(cmd);
// 3. 持久化:repository 由 BaseDomainService 注入(@Autowired(required = false)),
// insert / update 等方法在业务侧 UserRepository 上声明
repository.insert(user, cmd);
// 4. 发布领域事件:首参为聚合根(决定 aggregateType),事件之间不共享事务外副作用
publish(user, new UserCreatedEvt(user.getUserId(), user.getName()));
return user;
}
}
5.3 应用服务实现(AppService)
@Service
@ApplicationService(domain = "User")
@RequiredArgsConstructor
public class UserAppService extends BaseAppService {
private final UserRepository repository;
@QueryHandler(name = "pageUser", desc = "分页查询用户")
public Pagination<User> pageUser(PageUserQry qry) {
return repository.pageUser(qry);
}
@EventHandler
public void handleUserCreated(UserCreatedEvt event) {
log.info("处理用户创建事件: userId={}", event.getUserId());
}
}
5.4 仓储实现(Repository + Manager)
Domain 层接口:
public interface UserRepository extends BaseRepository<User> {
Boolean insert(User user, HasTransactor hasTransactor);
Boolean update(User user, HasTransactor hasTransactor);
User findByUserId(String userId);
Pagination<User> pageUser(PageUserQry qry);
}
BaseRepository<T>是空标记接口(只做类型约束,便于BaseDomainService<T, R>约束泛型), CRUD 方法由业务侧按需声明,实现在 Infrastructure 的UserInfraRepository+UserManager。
Infrastructure 层 Manager:
@Component
@RequiredArgsConstructor
public class UserManager extends RepositorySupport<UserMapper, UserEntity> {
private final UserInfraConvertor convertor;
public Pagination<User> pageUser(PageUserQry qry) {
Page<UserEntity> page = new Page<>(qry.getPage(), qry.getSize());
QueryWrapper<UserEntity> wrapper = new QueryWrapper<>();
if (StrUtil.isNotBlank(qry.getName())) {
wrapper.like("name", qry.getName());
}
wrapper.orderByDesc("created_time");
return toPagination(this.page(page, wrapper), convertor::toUserDomainList);
}
}
Infrastructure 层 Repository 实现:
@Component
@RequiredArgsConstructor
public class UserInfraRepository implements UserRepository {
private final UserInfraConvertor convertor;
private final UserManager manager;
@Override
public Pagination<User> pageUser(PageUserQry qry) {
return manager.pageUser(qry);
}
@Override
public Boolean insert(User user, HasTransactor hasTransactor) {
return manager.insert(convertor.toUserEntity(user), hasTransactor);
}
}
5.5 Convertor 转换(MapStruct)
@Mapper(componentModel = "spring")
public interface UserInfraConvertor {
UserEntity toUserEntity(User domain);
User toUserDomain(UserEntity entity);
List<User> toUserDomainList(List<UserEntity> entities);
}
5.6 前端适配(Adapter-BFF)
BFF 主协议是 GraphQL:由
easy-graphql(注解驱动执行引擎)+easy-gateway(GraphQlConfig 装配)承载, 注解清单见 §3.3 easy-graphql 与 GRAPHQL_COMPONENT_TRIAGE.md。下面给一个纯 REST 的等价写法:
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserRestController {
private final UserAppService userAppService;
@PostMapping("/page")
public Result<PageResult<User>> pageUser(@RequestBody PageUserQry qry) {
Pagination<User> page = userAppService.pageUser(qry);
return Result.success(PageResult.of(page.getItems(), page.getTotal()));
}
}
5.7 MQ 后端适配(Adapter-BG)
@Component
@Consumer(topic = "telemetry.submitted")
public class TelemetryConsumer extends MessageListener<TelemetrySubmittedEvt> {
private final RuleChainAppService ruleChainAppService;
@Override
public void onMessage(Headers headers, TelemetrySubmittedEvt message) {
log.info("收到遥测数据: {}", message.getDeviceId());
ruleChainAppService.handleTelemetrySubmitted(message);
}
}
6. 配置参考
6.1 消息总线配置
配置分两层:事件总线只描述「聚合类型 -> Topic」的通道映射(唯一前缀 cqrs.*);各 MQ 后端连接参数由
easy-messaging 的 connector 前缀承载。两者相互独立,可任意组合。
事件总线:通道映射(easy-event-bus)
# 聚合类型 -> Topic 通道映射
cqrs.channels[0].aggregates[0]=Device
cqrs.channels[0].topic=device_events
# 可选:该通道的事件基类全限定名
cqrs.channels[0].event-class=io.matrix.event.DeviceEvent
# 未命中映射时的兜底通道(同样是 Channel 结构)
cqrs.default-channel.topic=matrix_default_events
各 MQ 后端连接参数(easy-messaging connector)
# ZeroMQ
easy.zeromq.enabled=true
easy.zeromq.pub-address=tcp://*:5555
easy.zeromq.sub-address=tcp://localhost:5555
# Kafka
easy.kafka.enabled=true
easy.kafka.bootstrap-servers=localhost:9092
# RocketMQ
easy.rocketmq.enabled=true
easy.rocketmq.server-url=localhost:9876
# RabbitMQ
easy.rabbitmq.enabled=true
Redis / DBMS connector 无独立配置类,随对应基础设施装配启用。
6.2 ID 生成器配置
matrix.id-generator.snowflake.enabled=true
matrix.id-generator.snowflake.epoch=1784476800000
matrix.id-generator.snowflake.worker-id=1
matrix.id-generator.snowflake.worker_id_bits=2
matrix.id-generator.snowflake.sequence_bits=1
matrix.id-generator.snowflake.allow-clock-backward=true
matrix.id-generator.snowflake.max-clock-backward-ms=5
6.3 规则引擎配置
# 文件存储配置
easy.engines.storages[0].bean-name=exampleGqlStorage
easy.engines.storages[0].repository-bean-name=fileRepository
easy.engines.storages[0].enabled=true
easy.engines.storages[0].namespace=example-gql
# 规则引擎实例
easy.engines.instances[0].beanName=myRuleEngine
easy.engines.instances[0].type=rules
easy.engines.instances[0].implementation=mvel
easy.engines.instances[0].enabled=true
easy.engines.instances[0].namespace=test
easy.engines.instances[0].repositoryBeanName=fileRepository
6.4 数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/ic-eager?useSSL=true&characterEncoding=utf8&serverTimezone=Asia/Chongqing
spring.datasource.username=root
spring.datasource.password=xxx
spring.datasource.hikari.connectionTimeout=10000
7. 最佳实践
7.1 分层约束
| 规则 | 原因 |
|---|---|
| Domain 层不依赖任何外部技术框架 | 保护领域模型不受技术变迁影响 |
| Domain 层只能定义 Gateway/Repository 抽象 | 依赖倒置,具体实现放 Infrastructure |
| Adapter 层不写业务规则 | 只做协议适配、参数校验、用户态处理 |
| Application 层不做状态校验 | 只编排用例,业务规则放 Domain |
| 单聚合场景直接调用 DomainService | 无需经过 AppService,减少不必要的封装 |
| 跨聚合场景必须经过 AppService | 保证跨域协调在同一层完成,避免领域耦合 |
| Entity 不回渗到 Domain 层 | 防腐层隔离 |
7.2 命名约定
| 类型 | 命名后缀 | 示例 |
|---|---|---|
| 命令 | Cmd | CreateUserCmd |
| 查询 | Qry | PageUserQry |
| 领域事件 | Evt | UserCreatedEvt |
| 聚合根 | 名词 | User、Device |
| 领域服务 | DomainService | UserDomainService |
| 应用服务 | AppService | UserAppService |
| 仓储接口 | Repository | UserRepository |
| 仓储实现 | InfraRepository | UserInfraRepository |
| Manager | Manager | UserManager |
| 转换器 | Convertor | UserInfraConvertor |
| 持久化实体 | Entity | UserEntity |
| 监听器 | Listener/Consumer | UserCreatedListener |
7.3 聚合设计原则
- 聚合边界内强一致:同一聚合内的对象在同一个事务中更新
- 跨聚合用事件:跨聚合的协作通过领域事件异步完成
- 聚合引用用 ID:聚合之间通过 ID 引用,不直接持有对象引用
- 聚合大小适中:避免上帝聚合,也避免过度碎片化
7.4 消息总线选型指南
| 场景 | 推荐 Connector | 原因 |
|---|---|---|
| 生产环境高吞吐量 | Kafka | 高吞吐、持久化、消费者组 |
| 企业级功能(事务消息、定时消息) | RocketMQ | 功能最全 |
| 小型部署 / 开发环境 | ZeroMQ | 无需额外部署中间件 |
| 已有 Redis 基础设施 | Redis | 复用现有资源 |
| 极简场景(无额外中间件) | DBMS | 基于数据库表 |
7.5 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
BackendConsumer bean not found | 消息 Connector 未激活 | 检查是否添加了对应的 Connector 依赖和配置 |
ConsumeConfig must be set | @Consumer 注解未生效 | 确保监听器类被 Spring 扫描,且有 @Component 注解 |
| 循环依赖 | 错误的分层引用 | 检查 Domain 是否引用了 Application |
| 编译时类找不到 | 模块依赖顺序问题 | 从父项目执行 mvn clean install -DskipTests |
Stream.toList() 找不到 | Java 8 限制 | 改为 .collect(Collectors.toList()) |
Pagination.of() 参数类型错 | MyBatis-Plus 返回 long | 使用 (int) 强转 |
无效的目标发行版: 17 | 本地 JDK 不是 17 | export JAVA_HOME=$(/usr/libexec/java_home -v 17) |
| Lombok 编译 crash | Lombok < 1.18.20 不兼容 JDK 17 | 升到 1.18.32 或更高 |
javax.annotation.* 类找不到 | JDK 11+ 移除了 javax.annotation | 显式声明 javax.annotation-api 1.3.2(provided) |
| easy-cache 启动大段 WARN | Redis 没起,@PostConstruct 硬连抛异常 | v1.2.0 已改为懒初始化,首次广播时才尝试订阅 |
| easy-cache 跨节点缓存永远不一致(隐蔽 bug) | 手动 new RedisMessageListenerContainer() 没调 afterPropertiesSet() 导致 Subscriber 未创建 | v1.2.0 已修复:先 afterPropertiesSet() 再 start() |
7.6 JDK 17 迁移注意事项(SB 2.7 下)
| 项 | 说明 |
|---|---|
| 语言等级 | 编译目标 17,但 SB 2.7 仍是 javax 包,不涉及 jakarta 迁移 |
| Lombok | 必须 ≥ 1.18.20,推荐 1.18.32 |
| javax.annotation-api | 1.3.2 provided,kernel-domain 仍在用 @Resource |
| slf4j-api | 建议 ≥ 1.7.36(某些 JDK 17 版本有兼容 bug) |
| db-scheduler | v16 硬要求 JDK 17+,正好匹配 |
| maven.compiler.compilerVersion | deprecated 属性,可删;source/target 走版本号即可 |
| guava | 框架已清理,用 java.util.function.Function 替代 |
| pac4j-core 4.5.6 | 框架已清理,全仓零引用 |
附录
A. 完整项目依赖图
B. 相关资源
| 资源 | 位置 |
|---|---|
| 框架规范(铁律 + 架构约定) | AGENTS.md |
| 组件使用文档 | docs/components/ |
| 领域建模注解(Avro 转换规范) | ddd-annotations.md |
| 插件扩展指南 | pf4j-extension-guide.md |
| iot-care 实战项目 | 独立仓库(外部参考实现,非本框架依赖) |
C. 架构演进 TODO
| # | 模块 | 待办项 |
|---|---|---|
| 1 | kernel-adapter | 添加幂等提交功能(防重复提交),基于 Token + Redis 去重机制 |
| 2 | components/easy-notify | 新建通知组件(邮件/短信/站内信多通道,区别于已建的 easy-notification 实时推送) |
已落地:
components/easy-oss—— 文件存储组件(MinIO 后端),客户端能力已完成;浏览器直传 / 统一鉴权 / URL 代理等中间件服务端能力列入easy-oss演进规划。