Remesh 框架详解:基于 CQRS 的前端领域驱动设计方案

一、概述

Remesh 是一款基于 CQRS(命令查询职责分离)模式前端领域驱动设计(DDD)框架,核心用于解决大型前端应用的状态管理混乱、业务逻辑与视图耦合严重 的问题

该框架在 Vue 和 React 生态中均可使用,核心差异点如下:

  • Vue 需使用 2.7 及以上版本 (支持 Composition API ),且需通过 remesh-vue 进行专属适配改造
  • React 生态可直接集成 remesh 核心包,无需额外适配
  • 两者的核心思想、Domain 层设计逻辑完全一致,仅 View 层与框架的交互 Hooks 有细微差异

Remesh 的核心价值是将 "业务逻辑" 与 "视图展示" 彻底解耦,实现大型应用的可维护性、可扩展性和团队协作效率提升

二、核心概念

Remesh 的核心架构采用 "分层设计" ,核心分为 Domain 层(业务核心层) 和 View 层(视图展示层) ,两层通过框架提供的专属 API 进行交互,互不渗透

2.1 Domain 层:业务逻辑的容器

Domain 层是 Remesh 的核心,负责封装所有领域相关的状态、查询、命令和异步操作 ,是业务逻辑的集中管理中心,不与任何视图层代码耦合

1. 状态定义(State)

用于定义领域内的核心数据状态 ,是整个 Domain 层的数据基础,支持设置默认值,且状态修改仅能通过 "命令" 触发,不允许直接修改

示例场景:分页信息、用户列表、筛选条件等

2. 查询定义(Query)

用于从 "状态" 中获取数据,或基于已有状态进行派生计算 ,仅支持 "只读操作" ,不允许修改任何状态

示例场景:获取用户列表、判断列表是否为空、计算下一页分页参数等

3. 命令定义(Command)

用于定义修改状态的 "操作指令" ,是唯一能修改 State 的入口,支持同步修改状态,也可触发异步操作

示例场景:设置分页信息、重置用户列表、触发加载更多数据等。

4. 异步模块(AsyncModule)

框架封装的异步操作处理工具 ,自动管理异步请求的完整生命周期(默认 → 加载中 → 成功 → 失败 → 已取消),无需手动维护 loading、error 等状态

核心配置:

  • load:封装异步请求逻辑(如接口调用)
  • onSuccess:请求成功后的回调,通常用于触发命令更新状态
  • 自动暴露异步状态查询和相关事件,供 View 层订阅
5. 副作用处理(Effect)

用于定义 Domain 层的副作用逻辑,在 Domain 初始化时自动执行,通常用于触发初始化异步请求(如页面加载时自动获取数据)

2.2 View 层:UI 展示的载体

View 层专注于 UI 渲染和用户交互 ,不包含任何业务逻辑,通过 Remesh 提供的专属 HooksDomain 层进行交互,实现 "数据驱动视图"

1. 核心 Hooks(交互入口)
  • useRemeshSend:获取 "命令发送器 ",用于触发 Domain 层定义的命令(如点击按钮触发加载更多)
  • useRemeshDomain:获取 Domain 实例,用于关联对应的业务领域
  • useRemeshQuery:订阅 Domain 层的查询结果,查询结果变化时自动更新 UI
  • useRemeshEvent:订阅 Domain 层的事件(如异步请求成功 / 失败事件),实现无耦合的逻辑回调
2. 异步状态监听

通过 useRemeshQuery 订阅 AsyncDataQuery,可直接获取异步请求的 5 种互斥状态,无需手动管理:

  • default:初始默认状态(未触发任何异步请求)
  • loading:异步请求加载中(可渲染加载动画)
  • success:异步请求成功(可渲染数据列表)
  • failed:异步请求失败(可渲染错误提示)
  • cancelled:异步请求已取消(可重置视图状态)
3. 事件订阅解耦

通过 useRemeshEvent 订阅 Domain 层暴露的事件,可在 View 层处理 "与 UI 相关的回调逻辑"(如成功提示、错误弹窗),避免业务逻辑与 UI 逻辑耦合

三、实践分析

3.1 优势

1. 职责边界清晰,代码结构规整
  • 命令(修改状态)与查询(读取数据)分离,视图层可直观看到所有可用操作
  • Domain 层封装所有业务逻辑,View 层专注 UI 渲染,提升代码可维护性
  • 团队协作时可统一代码规范,降低沟通成本和交接成本
2. 自动化状态管理,减少冗余代码
  • AsyncModule 自动封装异步请求的 loading、success、failed 等状态,无需手动定义和维护
  • 状态修改仅通过命令触发,避免非法修改,保证数据一致性
  • 派生数据通过 Query 定义,缓存计算结果,提升性能
3. 高可扩展性,适配大型应用迭代
  • 领域可拆分、可组合,支持复杂应用的模块化拆分(如用户领域、订单领域)
  • 业务逻辑变更仅需修改 Domain 层,不影响 View 层,降低迭代风险
  • 支持事件订阅机制,实现跨领域、跨视图的无耦合通信
4. 高可测试性,便于单元测试落地
  • Domain 层无视图依赖,可单独进行单元测试
  • 命令和查询都是纯函数(或接近纯函数),测试用例易编写、易执行

3.2 劣势

1. 异步事件处理繁琐,代码量增加
  • 实际项目中,异步请求通常需要配套 UI 提示(如成功弹窗、错误提示)
  • 每个异步请求都需要通过 useRemeshEvent 订阅事件,再编写回调逻辑,增加大量模板代码
  • 事件订阅机制存在 "逻辑跳跃性",不如同步代码直观,排查问题时效率较低
2. 复杂交互逻辑处理困难,可读性下降
  • 实际项目中,单个用户操作(如提交表单)可能包含多个串行 / 并行请求、参数处理、条件判断、UI 交互
  • 采用 Remesh 框架需要将这些逻辑拆分到 "命令、异步模块、事件" 中,代码分散,可读性下降
  • 新手难以理解逻辑流转路径,后期维护成本较高
3. 学习成本高,上手门槛高
  • 要求开发者掌握 DDD、CQRS 等核心概念,对前端开发者的架构能力要求较高
  • 框架有专属的 API 规范和代码组织方式,需要一定时间的学习和适应
  • 小型项目中容易出现 "过度设计",增加开发成本和项目复杂度
4. 生态相对薄弱,问题排查难度大
  • 相比 Vuex、Redux 等主流状态管理框架,Remesh 的社区资源和解决方案较少
  • 遇到框架相关问题时,难以快速找到有效解决方案
  • 与部分第三方 UI 框架、工具库的集成兼容性有待提升

3.3 应用场景

1. 适用场景
  • 大型前端应用(如企业后台、电商平台、SAAS 系统):需要清晰的状态管理和业务逻辑组织
  • 复杂业务逻辑场景:需要将业务逻辑从视图层抽离,实现精细化的状态控制
  • 多人协作项目:需要统一的代码规范和组织方式,降低团队协作成本
  • 长期迭代项目:需要高可维护性、高可扩展性,支撑项目的长期演进
  • 对测试要求较高的项目:需要实现单元测试落地,保证代码质量
2. 不适用场景

小型简单应用(如个人博客、静态页面、简单 CRUD 工具):过度设计,增加开发成本

快速迭代项目(如创业项目、短期原型开发):学习成本和开发成本较高,影响项目进度

同步交互频繁的场景(如小游戏、实时编辑工具):事件机制会增加逻辑复杂度,影响性能和体验

团队技术栈薄弱的项目:团队对 DDD、CQRS 概念理解不足,难以发挥框架优势,反而增加项目风险

四、代码示例说明

4.1 前置准备

补充代码示例中缺失的类型定义和模拟接口,保证代码的完整性和可参考性

typescript 复制代码
// 定义分页类型
export interface Pagination {
  currentPage: number;
  pageSize: number;
}

// 定义用户类型
export interface User {
  id: number;
  name: string;
  age: number;
  email: string;
}

// 定义用户列表类型
export type UserList = User[];

// 模拟用户列表接口请求
export const getUserList = async (pagination: Pagination): Promise<UserList> => {
  // 模拟接口延迟
  await new Promise(resolve => setTimeout(resolve, 1000));
  // 模拟返回数据
  return Array.from({ length: pagination.pageSize }, (_, index) => ({
    id: (pagination.currentPage - 1) * pagination.pageSize + index + 1,
    name: `用户${(pagination.currentPage - 1) * pagination.pageSize + index + 1}`,
    age: 20 + Math.floor(Math.random() * 10),
    email: `user${(pagination.currentPage - 1) * pagination.pageSize + index + 1}@example.com`
  }));
};

4.2 Domain 层代码示例

typescript 复制代码
import { Remesh, AsyncModule } from 'remesh';
import { Pagination, UserList, getUserList } from './types';

// 1. 创建 Domain 实例
const UserDomain = Remesh.domain({
  name: 'UserDomain'
});

// 2. 定义默认分页配置
const defaultPagination: Pagination = {
  currentPage: 1,
  pageSize: 10,
};

// 3. 定义领域状态
// 分页状态
const PaginationState = UserDomain.state({
  name: 'PaginationState',
  default: defaultPagination,
});

// 用户列表状态
const UserListState = UserDomain.state({
  name: 'UserListState',
  default: [] as UserList,
});

// 4. 定义查询逻辑(只读,派生数据)
// 获取用户列表
const UserListQuery = UserDomain.query({
  name: 'UserListQuery',
  impl: ({ get }) => {
    return get(UserListState());
  },
});

// 判断用户列表是否为空
const IsEmptyUserListQuery = UserDomain.query({
  name: 'IsEmptyUserListQuery',
  impl: ({ get }) => {
    const userList = get(UserListQuery());
    return userList.length === 0;
  },
});

// 计算下一页分页信息
const NextPaginationQuery = UserDomain.query({
  name: 'NextPaginationQuery',
  impl: ({ get }) => {
    const pagination = get(PaginationState());
    const userList = get(UserListQuery());
    
    // 列表为空时,保持当前分页
    if (userList.length === 0) {
      return pagination;
    }
    
    // 列表有数据时,页码+1
    return {
      ...pagination,
      currentPage: pagination.currentPage + 1,
    };
  },
});

// 5. 定义命令逻辑(唯一修改状态的入口)
// 设置分页信息
const SetPaginationCommand = UserDomain.command({
  name: 'SetPaginationCommand',
  impl: ({}, pagination: Pagination) => {
    return PaginationState().new(pagination);
  },
});

// 设置用户列表信息
const SetUserListCommand = UserDomain.command({
  name: 'SetUserListCommand',
  impl: ({}, userList: UserList) => {
    return UserListState().new(userList);
  },
});

// 6. 定义异步模块(处理接口请求,自动管理异步状态)
const UserAsyncModule = AsyncModule(UserDomain, {
  name: 'UserAsyncModule',
  // 封装异步请求逻辑
  load: async ({}, pagination: Pagination) => {
    return await getUserList(pagination);
  },
  // 请求成功后的回调:更新分页和用户列表
  onSuccess: ({ get }, result: UserList) => {
    const nextPagination = get(NextPaginationQuery());
    const currentUserList = get(UserListState());
    
    // 返回需要执行的命令列表(支持批量执行)
    return [
      SetPaginationCommand(nextPagination),
      SetUserListCommand([...currentUserList, ...result])
    ];
  },
});

// 7. 定义副作用(Domain 初始化时自动执行,加载初始数据)
UserDomain.effect({
  name: 'UserInitEffect',
  impl: ({}) => {
    return [UserAsyncModule.command.LoadCommand(defaultPagination)];
  },
});

// 8. 定义业务相关命令(组合已有命令/异步模块)
// 加载更多用户数据
const LoadMoreCommand = UserDomain.command({
  name: 'LoadMoreCommand',
  impl: ({ get }) => {
    const nextPagination = get(NextPaginationQuery());
    return UserAsyncModule.command.LoadCommand(nextPagination);
  },
});

// 重置用户列表(恢复默认分页,清空列表,重新加载数据)
const ResetCommand = UserDomain.command({
  name: 'ResetCommand',
  impl: ({}) => {
    return [
      SetPaginationCommand(defaultPagination),
      SetUserListCommand([]),
      LoadMoreCommand()
    ];
  },
});

// 9. 导出 Domain 对外暴露的能力(供 View 层使用)
export const UserDomainExports = {
  query: {
    UserListQuery,
    IsEmptyUserListQuery,
    AsyncDataQuery: UserAsyncModule.query.AsyncDataQuery,
  },
  command: {
    LoadMoreCommand,
    ResetCommand,
  },
  event: {
    LoadingUsersEvent: UserAsyncModule.event.LoadingEvent,
    SuccessToLoadUsersEvent: UserAsyncModule.event.SuccessEvent,
    FailedToLoadUsersEvent: UserAsyncModule.event.FailedEvent,
  },
  domain: UserDomain,
};

4.3 View 层代码示例(React 版本)

typescript 复制代码
import { useRemeshSend, useRemeshDomain, useRemeshQuery, useRemeshEvent } from 'remesh';
import { UserDomainExports } from './UserDomain';

const UserListPage = () => {
  // 1. 获取 Domain 实例和命令发送器
  const userDomain = useRemeshDomain(UserDomainExports.domain);
  const send = useRemeshSend();

  // 2. 订阅 Domain 层的查询结果
  const userList = useRemeshQuery(userDomain.query.UserListQuery());
  const isEmptyList = useRemeshQuery(userDomain.query.IsEmptyUserListQuery());
  const asyncData = useRemeshQuery(userDomain.query.AsyncDataQuery());

  // 3. 订阅异步事件(处理 UI 提示逻辑)
  useRemeshEvent(userDomain.event.SuccessToLoadUsersEvent, () => {
    // 模拟成功提示
    console.log('用户数据加载成功!');
  });

  useRemeshEvent(userDomain.event.FailedToLoadUsersEvent, (error) => {
    // 模拟错误提示
    console.error('用户数据加载失败:', error);
  });

  // 4. 判断异步状态
  const isLoading = UserAsyncModule.AsyncData.isLoading(asyncData);

  // 5. 定义用户交互逻辑(触发 Domain 层命令)
  const handleReset = () => {
    send(userDomain.command.ResetCommand());
  };

  const handleLoadMore = () => {
    if (isLoading) return;
    send(userDomain.command.LoadMoreCommand());
  };

  // 6. 渲染 UI
  return (
    <div style={{ padding: '20px' }}>
      <h2>用户列表</h2>
      <button onClick={handleReset} style={{ marginBottom: '20px' }}>
        重置列表
      </button>
      
      {isEmptyList && !isLoading && <p>暂无用户数据</p>}
      
      <ul style={{ listStyle: 'none', padding: 0 }}>
        {userList.map((user) => (
          <li key={user.id} style={{ padding: '10px', borderBottom: '1px solid #eee' }}>
            <p>姓名:{user.name}</p>
            <p>年龄:{user.age}</p>
            <p>邮箱:{user.email}</p>
          </li>
        ))}
      </ul>
      
      {isLoading && <p>加载中...</p>}
      {!isLoading && !isEmptyList && (
        <button onClick={handleLoadMore} style={{ marginTop: '20px' }}>
          加载更多
        </button>
      )}
    </div>
  );
};

export default UserListPage;

4.4 代码核心说明

1. Domain 层关键点
  • 状态(State )是只读的,仅能通过命令(Command)修改,保证数据一致性
  • 查询(Query)基于状态派生,无需重复存储冗余数据,提升性能
  • 异步模块(AsyncModule)封装了异步请求的全生命周期,简化异步状态管理
  • 副作用(Effect )用于初始化数据,实现 "页面加载即请求数据" 的需求
  • 命令支持组合调用,可实现复杂的业务逻辑(如 ResetCommand 组合了三个操作)
2. View 层关键点
  • 仅通过 HooksDomain 层交互,不包含任何业务逻辑
  • 订阅查询结果后,数据变化时自动更新 UI ,实现 "数据驱动视图"
  • 异步状态直接通过 AsyncData.isLoading 等方法判断,无需手动维护
  • 事件订阅用于处理 UI 相关的回调逻辑,实现与业务逻辑的解耦
3. 核心流转逻辑
复制代码
用户操作 → View 层发送命令 → Domain 层执行命令 / 异步模块 → 状态更新 → Query 结果更新 → View 层 UI 自动刷新

五、总结

Remesh 框架为大型前端应用提供了一套完整的 "DDD + CQRS " 解决方案,通过 "Domain 层与 View 层的彻底分离 ",解决了传统状态管理框架的耦合问题,提升了代码的可维护性、可扩展性和可测试性。

然而,框架也存在明显的局限性,尤其是在处理复杂交互逻辑和快速迭代项目时,会增加学习成本和开发成本,甚至出现 "过度设计" 的问题。

5.1 选型建议

1. 评估项目复杂度

大型复杂应用(如企业后台、电商平台)可优先考虑,小型简单应用建议使用 Vuex、Pinia、Redux 等轻量框架

2. 评估团队能力

确保团队对 DDD、CQRS 等概念有足够的理解,避免后期维护困难

3. 评估项目周期

长期迭代项目适合使用 Remesh,短期快速迭代项目不建议使用

4. 渐进式接入

可先在核心业务模块中试点使用,逐步推广,降低项目风险

5.2 关键点回顾

  1. Remesh 核心是 "CQRS 模式 + DDD 分层",实现业务逻辑与视图的解耦
  2. Domain 层由五大核心组成:State、Query、Command、AsyncModule、Effect
  3. View 层通过专属 HooksDomain 层交互,专注于 UI 渲染和用户交互
  4. 适合大型复杂、长期迭代、多人协作的项目,不适合小型简单、快速迭代的项目
  5. 异步模块自动管理 loading 等状态,是框架的核心优势之一,但也带来了事件处理的繁琐性
相关推荐
Charlie_lll2 小时前
学习Three.js–雪花
前端·three.js
onebyte8bits2 小时前
前端国际化(i18n)体系设计与工程化落地
前端·国际化·i18n·工程化
晚霞的不甘2 小时前
CANN 编译器深度解析:UB、L1 与 Global Memory 的协同调度机制
java·后端·spring·架构·音视频
C澒2 小时前
前端分层架构实战:DDD 与 Clean Architecture 在大型业务系统中的落地路径与项目实践
前端·架构·系统架构·前端框架
前端不太难2 小时前
HarmonyOS 游戏里,Ability 是如何被重建的
游戏·状态模式·harmonyos
BestSongC2 小时前
行人摔倒检测系统 - 前端文档(1)
前端·人工智能·目标检测
Re.不晚3 小时前
MySQL进阶之战——索引、事务与锁、高可用架构的三重奏
数据库·mysql·架构
松☆3 小时前
深入理解CANN:面向AI加速的异构计算架构
人工智能·架构
0思必得03 小时前
[Web自动化] Selenium处理滚动条
前端·爬虫·python·selenium·自动化