265 lines
10 KiB
Markdown
265 lines
10 KiB
Markdown
## DDD领域驱动设计的微服务前端案例
|
||
|
||
> 持续开发中,及时拉取代码,文档还不够完善,先凑合看!!目前只是demo,代码还会进行大规模封装细化和改造!
|
||
|
||
搭建的一套 **基于DDD领域驱动设计的微服务前端** 项目脚手架,适用于大型企业,开发大型复杂且拓展灵活的前端应用的架构方案。
|
||
|
||
代码编写建议使用 **OOP面向对象 + TypeScript + JSX** 进行开发,让项目更适合开发和维护,获得更好更极致的编码体验。
|
||
|
||
本案例中 **[主应用]** 采取的就是OOP风格,当然如果你不熟悉上面的写法,那么可以在
|
||
【其他模块】中使用Vue2,Vue3都行,但仍然需要遵守DDD的规范进行开发!
|
||
|
||
## 架构图
|
||
|
||

|
||
|
||
## CI/CD 自动化集群部署
|
||
|
||

|
||
|
||
## 开发日记
|
||
|
||
- [x] 基于DDD理念改造
|
||
- [x] 拆分领域模块为联邦模块,实现微前端
|
||
- [x] 基于 Vue3+OOP+JSX 改造项目代码
|
||
- [x] 基于 Nacos 开发 nacos-federation 插件,实现各模块的服务注册&发现,统一管理远程地址
|
||
- [x] 修改 nacos-federation 插件实现其成为vite的插件,进一步简化代码!
|
||
- [x] 修改 nacos-federation 插件,实现 接口请求基地址从 配置中心获取,并注入到环境变量中
|
||
- [x] 配置 Traefik,实现微前端网关,限流与熔断,重试,IP 白名单,压缩
|
||
- [x] 封装自动生成 Traefik 代理配置的 nodejs 插件代码
|
||
- [x] 修改base,增加资源来源地址,修复资源错误加载的问题
|
||
- [x] 将vite.config.ts中的base 改到插件 nacos-federation中实现,简化代码
|
||
- [x] 增加控制台日志,方便访问各种服务
|
||
- [x] pnpm 统一管理集中项目依赖,运行&构建等
|
||
- [x] 领域模块实现 docker 自动化构建部署上线
|
||
- [ ] Deno集成GraphQL
|
||
- [ ] ....
|
||
- [ ] 爬快手api,实现一个完整项目,测试脚手架不足
|
||
|
||
## 技术栈:
|
||
|
||
- [DDD领域驱动设计](https://zh.wikipedia.org/zh-cn/%E9%A0%98%E5%9F%9F%E9%A9%85%E5%8B%95%E8%A8%AD%E8%A8%88)
|
||
- [@originjs/vite-plugin-federation](https://www.npmjs.com/package/@originjs/vite-plugin-federation)
|
||
- [Deno](https://deno.com/)
|
||
- [TypeScript](https://www.typescriptlang.org/)
|
||
- [GraphQL](https://graphql.org/)
|
||
- [docker](https://www.docker.com/)
|
||
- [drone](https://drone.cool/)
|
||
- [nacos](https://nacos.io/?spm=5238cd80.47ee59c.0.0.189fcd36fF0ca9)
|
||
- [traefik](https://traefik.io/)
|
||
- [nginx](https://nginx.org/)
|
||
- [Vue3](https://cn.vuejs.org/)
|
||
- [Jsx](https://legacy.reactjs.org/docs/introducing-jsx.html)
|
||
- [Vite](https://cn.vite.dev/)
|
||
- [axios](https://axios-http.com/)
|
||
- 等等等....
|
||
|
||
## 文件夹结构
|
||
|
||
**根目录:**
|
||
|
||
```txt
|
||
.
|
||
├── 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前端分层目录:**
|
||
|
||
```txt
|
||
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文件
|
||
```
|
||
|
||
## 代码风格
|
||
|
||
```tsx
|
||
// 首页 页面
|
||
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>
|
||
)
|
||
}
|
||
}
|
||
```
|
||
|
||
```tsx
|
||
// 头部组件
|
||
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: 准备 >=8台Linux服务器,并提前安装配置好Docker,确保网络可用,建议安装 [1panel](https://1panel.cn/)
|
||
|
||
- 2: 在1台Linux服务器上,部署公司自己的 Docker Registry 镜像存储库
|
||
- 3: 在1台Linux服务器上,部署公司自己 Gitea 仓库
|
||
|
||
- 4: 准本 N台运行 前端各模块 的Linux服务器,并提前组成N个Docker Swarm 集群,以供前端各模块部署使用
|
||
|
||
- 5: 准本 >=3台运行 Nacos服务中心 的Linux服务器,并提前组成Docker Swarm 集群,提前运行好,确保服务可用
|
||
- [Nacos 部署文档](./docker/nacos-cluster/README.md)
|
||
|
||
- 6: 准本 一台 运行 traefik 网关的Linux服务器,,提前运行好,确保服务可用
|
||
- [Traefik 部署文档](./docker/traefik/README.md)
|
||
|
||
**前端工作:**
|
||
|
||
详细请看 各前端模块 存储库内的 帮助文档!!!
|
||
|
||
- [商品模块 app-product](https://gitee.com/bmycode/app-product)
|
||
- [共享模块 app-shared](https://gitee.com/bmycode/app-shared)
|
||
|