a3ba6c294495718aeb4ce7c11ce7fa838d82e197
DDD领域驱动设计的微服务前端案例
持续开发中,及时拉取代码,文档还不够完善,先凑合看!!目前只是demo,代码还会进行大规模封装细化和改造!
搭建的一套 基于DDD领域驱动设计的微服务前端 项目脚手架,适用于大型企业,开发大型复杂且拓展灵活的前端应用的架构方案。
代码编写建议使用 OOP面向对象 + TypeScript + JSX 进行开发,让项目更适合开发和维护,获得更好更极致的编码体验。
本案例中 [主应用] 采取的就是OOP风格,当然如果你不熟悉上面的写法,那么可以在 【其他模块】中使用Vue2,Vue3都行,但仍然需要遵守DDD的规范进行开发!
开发日记
- 基于DDD理念改造
- 拆分领域模块为联邦模块,实现微前端
- 基于 Vue3+OOP+JSX 改造项目代码
- 开发Nacos的config-service实现代理访问Nacos服务
- 基于 Nacos 开发 nacos-federation 插件,实现各模块的服务注册&发现,统一管理远程地址
- 修改 nacos-federation 插件实现其成为vite的插件,进一步简化代码!
- 修改 nacos-federation 插件,实现 接口请求基地址从 配置中心获取,并注入到环境变量中
- 配置 Traefik,实现微前端网关,限流与熔断,重试,IP 白名单,压缩
- 封装自动生成 Traefik 代理配置的 nodejs 插件代码
- 修改base,增加资源来源地址,修复资源错误加载的问题
- 将vite.config.ts中的base 改到插件 nacos-federation中实现,简化代码
- 增加控制台日志,方便访问各种服务
- pnpm 统一管理集中项目依赖,运行&构建等
- 领域模块实现 docker 自动化构建部署上线
- Deno集成GraphQL
- ....
- 爬快手api,实现一个完整项目,测试脚手架不足
技术栈:
- DDD领域驱动设计
- @originjs/vite-plugin-federation
- Deno
- TypeScript
- GraphQL
- docker
- drone
- nacos
- nginx
- Vue3
- Jsx
- Vite
- axios
- 等等等....
文件夹结构
根目录:
.
├── back-end # 【后端】基于Deno写的后端 测试接口,仅供测试
├── doc # 一些markdown笔记
├── docker # 微服务系统下的一些工具
│ ├── nacos # 动态服务发现配置管理平台
│ └── traefik # 微服务网关
├── front-end # 前端 微服务 项目
│ ├── app-product # 独立的【商品】领域模块
│ ├── app-shared # 独立的 共享资源 模块
│ ├── nacos-federation # 开发的Nacos服务注册与发现的插件,简化优雅代码
│ ├── .... # 实际项目还会有更多其他的领域模块
│ └── main-app # 主应用(入口应用)
├── package.json # pnpm的顶级配置文件,可以控制模块运行相同命令
├── pnpm-workspace.yaml # 管理多个包的依赖关系
└── README.md # 帮助文档
** DDD前端分层目录:**
src/
├── domains/ # 【核心】领域层 - 所有业务领域模块
│ └── product/ # 【app-product模块】订单领域模块
│ ├── domain/ # 领域模型(充血模型)
│ │ ├── entities/ # 实体(如:Product, ProductLineItem)
│ │ ├── value-objects/ # 值对象(如:Money, Address)
│ │ ├── types/ # 领域内通用类型
│ │ ├── enums/ # 领域枚举
│ │ └── events/ # 领域事件(如果需要)
│ ├── application/ # 应用服务层 - 协调领域对象完成用例
│ │ └── services/ # 应用服务(如:ProductApplicationService)
│ ├── infrastructure/ # 基础设施层 - 领域层的具体实现
│ │ └── api/ # 数据获取实现(如:HttpProductRepository)
│ ├── ports/ # 端口(接口/抽象类)- 定义契约
│ │ └── repositories/ # 仓储接口(如:ProductRepository)
│ └── use-cases/ # (可选)或将用例放在这里
├── app/ # 【交付层】应用组装和配置:路由、状态管理、主布局、UI库设置、错误处理、依赖注入容器
│ ├── components/ # 通用UI组件(与业务无关,如Button,Modal)
│ ├── layouts/ # 布局组件
│ ├── di/ # 依赖注入配置
│ ├── router/ # Vue Router 配置
│ ├── stores/ # Pinia Store(用于UI状态管理,非核心业务状态)
│ └── main.ts # 应用入口,依赖注入的组装之地
│ └── App.vue # 根组件
├── features/ # 【交付层】基于领域的功能模块:关注具体的业务功能实现,它是舞台上的“演员”和“节目”。
│ └── product/ # 订单功能模块(app-product)
│ ├── components/ # 订单领域专用的UI组件
│ ├── views/ # 订单相关的页面级Vue组件
│ ├── router/ # 订单相关的页面级Vue组件
│ ├── composables/ # 订单相关的Vue组合式函数
│ └── index.ts # 订单功能模块的出口
├── shared/ # 【app-shared模块】共享资源
│ ├── infra(infrastructure) # 基础设施层
│ │ ├── http/
│ ├── lib/ # 第三方库的封装/工具函数
│ ├── utils/ # 纯工具函数
│ └── types/ # 全局通用的TypeScript类型定义
│ └── constants/ # 全局常量(如错误码、正则)
│ └── exceptions/ # 通用异常(如NotFoundError)
└── public/ # 静态资源目录
└── index.html # 主HTML文件
代码风格
// 首页 页面
import { Component, Ref, Setup, Vue } from 'vue-facing-decorator'
import Header from '../components/Header'
import { RouteLocationNormalizedLoaded, Router, useRoute, useRouter } from "vue-router";
interface ListItemType {
id: number
title: string
}
@Component()
export default class Home extends Vue {
counter: number = 1;
@Ref
readonly refH1!: HTMLDivElement
@Setup((props, ctx) => useRouter())
router!: Router
@Setup((props, ctx) => useRoute())
route!: RouteLocationNormalizedLoaded
newList: ListItemType[] = [
{ id: 1, title: '新闻1' },
{ id: 2, title: '新闻2' },
{ id: 3, title: '新闻3' },
]
addCount() {
// console.log("ref dom: ", this.refH1)
// this.router.push({ name: 'about' })
// this.route.query
this.counter++
}
testHtml() {
return <p>测试html jsx 函数</p>
}
render() {
return (
<div class="home">
<h1 ref="refH1">首页</h1>
<p onClick={ this.addCount }>counter 响应式变量(点我):{ this.counter }</p>
<Header
list={ this.newList }
text={ "测试" }>
{ {
default: () => <div class="default">父到子 default 插槽</div>,
haveName: () => <div class="haveName">父到子 具名 插槽</div>,
item: (val: ListItemType) => <li key={ val.id }>{ val.title }</li>
} }
</Header>
{ this.testHtml }
</div>
)
}
}
// 头部组件
import { Component, Prop, TSX, Vue } from 'vue-facing-decorator'
interface Props {
text?: string
list?: ListItemType[]
}
interface ListItemType {
id: number
title: string
}
@Component()
export default class Header extends TSX<Props>()(Vue) implements Props {
@Prop({ default: '默认数值' })
text!: string;
@Prop()
list!: ListItemType[];
created() {
console.log("created: ", this)
}
render() {
return (
<div class="header">
头部组件
<p>{ this.text }</p>
{ this.$slots.default?.() }
{ this.$slots.haveName?.() }
<ul>
{
this.list.map(
item =>
this.$slots.item?.(item)
)
}
</ul>
</div>
)
}
}
【失效】运行 & 打包
# 1 下载代码
git clone https://gitee.com/bmycode/dddmicro-frontend.git
# 2 运行后端
cd back-end # 进入 后端 文件夹
deno install # 安装依赖
deno run dev # 运行后端
# 3 运行前端
cd front-end # 进入 前端 文件夹
pnpm install # 会同时为 所有模块 安装依赖
pnpm add -g concurrently # 全局安装 并发命令执行 插件,局部安装也行,随你。
pnpm run all # 构建 app-product 和 app-shared 并启动预览,并开发阶段启动 main-app 模块
# 注意:命令跑完后 ctrl + c 停止,重跑一次!!因为子模块内命令并发执行预览跑在构建之前,解决办法很简单,后面再搞。
pnpm run all
# 打开 main-app 主模块访问地址即可:[http://localhost:1300/](http://localhost:1300/)
# 4 部署上线
pnpm run build:main # 构建并预览主应用,预览打包后的主应用,没问题,ctrl + c 停止。
# 子模块在 第三步 已经打包好了,不需要重新打包了。
# 上传部署 各模块内的 dist/ 文件夹到服务器上,注意修改 vite.config.ts 内 plugins.federation.remotes.xxx 的地址!
# 后期会实现集中管理 地址配置,并根据环境变量动态加载!!
问 & 答
问: 为什么vue项目要抛弃SFC写法而尝试jsx/tsx方式来编码?
问:不会 vue3 jsx 语法,怎么办?
Description
Languages
TypeScript
75.7%
JavaScript
16.9%
CSS
6.4%
HTML
0.8%
Less
0.2%