Files
DDDMicroFrontend/README.md
2025-09-23 12:08:40 +08:00

265 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## DDD领域驱动设计的微服务前端案例
> 持续开发中及时拉取代码文档还不够完善先凑合看目前只是demo代码还会进行大规模封装细化和改造
搭建的一套 **基于DDD领域驱动设计的微服务前端** 项目脚手架,适用于大型企业,开发大型复杂且拓展灵活的前端应用的架构方案。
代码编写建议使用 **OOP面向对象 + TypeScript + JSX** 进行开发,让项目更适合开发和维护,获得更好更极致的编码体验。
本案例中 **[主应用]** 采取的就是OOP风格当然如果你不熟悉上面的写法那么可以在
【其他模块】中使用Vue2Vue3都行但仍然需要遵守DDD的规范进行开发
## 开发日记
- [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 统一管理集中项目依赖,运行&构建等
- [ ] 领域模块实现 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)
- [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组件与业务无关如ButtonModal
│ ├── 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>
)
}
}
```
## ~~【失效】运行 & 打包~~
```bash
# 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方式来编码**
> https://juejin.cn/post/6911175470255964174
**问:不会 vue3 jsx 语法,怎么办?**
> 答https://juejin.cn/post/7141674726434439176