Files
DDDMicroFrontend/README.md
2025-09-19 03:21:05 +08:00

9.4 KiB
Raw Blame History

DDD领域驱动设计的微服务前端案例

持续开发中及时拉取代码文档还不够完善先凑合看目前只是demo代码还会进行大规模封装细化和改造

搭建的一套 基于DDD领域驱动设计的微服务前端 项目脚手架,适用于大型企业,开发大型复杂且拓展灵活的前端应用的架构方案。

代码编写建议使用 OOP面向对象 + TypeScript + JSX 进行开发,让项目更适合开发和维护,获得更好更极致的编码体验。

本案例中 [主应用] 采取的就是OOP风格当然如果你不熟悉上面的写法那么可以在 【其他模块】中使用Vue2Vue3都行但仍然需要遵守DDD的规范进行开发

技术栈:

文件夹结构

根目录:

.
├── back-end          # 【后端】基于Deno写的后端 测试接口,仅供测试
├── doc               # 一些markdown笔记
├── front-end         # 前端 微服务 项目
│   ├── app-product   # 独立的【商品】领域模块
│   ├── app-shared    # 独立的 共享资源 模块
│   ├── ....          # 实际项目还会有更多其他的领域模块
│   └── main-app      # 主应用(入口应用)
└── README.md         # 帮助文档

main-app 目录:

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/                 # 共享资源
│   ├── infra(infrastructure) # 基础设施层
│   │   ├── http/  
│   ├── lib/                # 第三方库的封装/工具函数
│   ├── utils/              # 纯工具函数
│   └── types/              # 全局通用的TypeScript类型定义
│   └── constants/          # 全局常量(如错误码、正则)
│   └── exceptions/         # 通用异常如NotFoundError
└── public/                 # 静态资源目录
└── index.html              # 主HTML文件

代码风格

// 首页 页面
import { Autobind, Component, Link, Mut, VueComponent } from 'vue3-oop'
import Header from '@/components/Header'

interface ListItemType {
    id: number
    title: string
}

@Component()
export default class Home extends VueComponent {

    @Mut()
    count: number = 1

    @Mut()
    text: string = "Home props 传给 Header 的字符串"

    @Mut()
    newList: ListItemType[] = [
        { id: 1, title: '新闻1' },
        { id: 2, title: '新闻2' },
        { id: 3, title: '新闻3' },
    ]

    @Link() H1?: HTMLDivElement

    @Autobind()
    async addCount() {
        this.count++
        this.text = "22222"
        console.log(this.H1)
    }

    render() {
        return (
            <div class="home" ref="H1">
                <h1>首页</h1>
                <p onClick={ this.addCount }>count响应式变量(点我):{ this.count }</p>
                <Header
                    v-slots={ {
                        default: () => <div class="default">父到子 default 插槽</div>,
                        haveName: () => <div class="haveName">父到子 具名 插槽</div>,
                        item: (val) => <li key={ val.id }>{ val.title }</li>
                    } }
                    list={ this.newList }
                    text={ this.text }>
                </Header>
            </div>
        )
    }
}
// 头部组件
import { Component, ComponentProps, Hook, useProps, VueComponent } from 'vue3-oop'
import { JSX } from "vue/jsx-runtime";
import { VNodeChild } from "vue";

interface ListItemType {
    id: number
    title: string
}

interface HeaderProps {
    text?: string;
    list: ListItemType[]
    slots: {
        default: () => JSX.Element;
        haveName: () => JSX.Element;
        item: (val: ListItemType) => VNodeChild;
    }
}

@Component()
export default class Header extends VueComponent<HeaderProps> {

    // required 的 true or false 需要和 类型中 text的? 对应
    static defaultProps: ComponentProps<HeaderProps> = {
        text: { default: '默认数值', required: true },
        list: { default: () => [] }
    };

    @Hook('Mounted')
    mounted() {
        console.log("this: ", this);
        console.log('Header 组件的 mounted 生命周期...')
    }

    render() {
        const { text, list } = useProps<HeaderProps>();
        return (
            <div class="header">
                头部组件
                <p>{ text }</p>
                { this.context.slots.default?.() }
                { this.context.slots.haveName?.() }
                <ul>
                    {
                        list.map(
                            item =>
                                this.context.slots.item?.(item)
                        )
                    }
                </ul>
            </div>
        )
    }
}

开发任务

  • 基于DDD理念改造
  • 基于 vite-plugin-federation 拆分领域模块为联邦模块,实现微前端
  • 基于Vue3+OOP+JSX改造项目代码
  • 领域模块实现 docker 自动化构建部署上线
  • Deno集成GraphQL
  • 爬快手api实现一个完整项目测试脚手架不足

运行 & 打包

# 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