架构总览:Monorepo 结构与容器化部署

架构总览:Monorepo 结构与容器化部署

本文是《墨言博客助手》系列的第 2 篇。项目源码已开源:https://github.com/2692341798/InkWords

引言:为什么需要架构设计?

想象一下你要建造一栋房子。你不会直接把砖块、水泥、木材堆在一起就开始施工,而是先画好设计图纸 ,规划好房间布局 ,确定好水电线路。软件开发也是如此,良好的架构设计就是我们的"设计图纸"。

今天,我将带你深入了解墨言博客助手的整体架构设计。无论你是刚接触全栈开发的小白,还是有经验的开发者,这篇文章都会让你清晰地理解:

  1. Monorepo 是什么?为什么选择它?
  2. Docker 如何让开发环境"一次配置,处处运行"?
  3. 前端、后端、数据库如何优雅地协同工作?

一、Monorepo:一体化的代码仓库

什么是 Monorepo?

Monorepo(单一仓库)是一种将所有相关项目的代码放在同一个版本控制仓库中的策略。与之相对的是 Multi-repo(多仓库),即每个项目都有自己的独立仓库。

生活化比喻

  • Multi-repo:像是一个小区,每栋楼(项目)有自己的物业(仓库)、自己的门禁(配置)
  • Monorepo:像是一栋综合大楼,所有楼层(前端、后端、文档)共享同一个大堂(仓库)、同一套安保(CI/CD)

墨言项目的目录结构

让我们看看项目的根目录结构:

复制代码
InkWords/
├── frontend/          # 前端代码(React + Vite)
├── backend/           # 后端代码(Go + Gin)
├── docker-compose.yml # 容器编排配置文件
├── .trae/             # 项目文档和规范
│   ├── rules/         # 工程规范
│   └── documents/     # 架构文档
└── README.md          # 项目说明

为什么选择 Monorepo?

  1. 代码共享方便:前后端可以共享 TypeScript 类型定义、工具函数等
  2. 依赖管理统一:所有依赖版本集中管理,避免版本冲突
  3. 开发体验一致:一键启动所有服务,无需在多个仓库间切换
  4. CI/CD 简化:一次构建、测试、部署整个应用

二、Docker-First:环境一致性的保障

Docker 解决了什么问题?

在传统开发中,最让人头疼的问题之一就是 "在我电脑上能运行,为什么在你那里不行?"。这通常是因为:

  • 操作系统不同(Windows/macOS/Linux)
  • 运行时版本不同(Node.js 18 vs 20,Go 1.20 vs 1.21)
  • 依赖库版本不同
  • 环境变量配置不同

Docker 通过容器化技术,将应用及其所有依赖打包成一个标准化的单元,确保在任何地方运行结果一致。

Docker Compose:多服务的编排工具

当我们的应用包含多个服务(前端、后端、数据库)时,手动启动和管理每个容器会很麻烦。Docker Compose 就是解决这个问题的工具。

让我们逐行解析项目的 docker-compose.yml 文件:

yaml 复制代码
# docker-compose.yml
version: '3.8'  # 指定 Compose 文件格式版本

services:  # 定义所有服务
  db:  # 数据库服务
    image: postgres:14-alpine  # 使用 PostgreSQL 14 的 Alpine 版本(轻量)
    container_name: inkwords-db  # 容器名称
    environment:  # 环境变量
      POSTGRES_USER: inkwords  # 数据库用户名
      POSTGRES_PASSWORD: inkwords_password  # 数据库密码
      POSTGRES_DB: inkwords_db  # 数据库名称
    ports:
      - "5432:5432"  # 宿主机端口:容器端口
    volumes:
      - pgdata:/var/lib/postgresql/data  # 数据持久化
    restart: unless-stopped  # 自动重启策略
    healthcheck:  # 健康检查
      test: ["CMD-SHELL", "pg_isready -U inkwords -d inkwords_db"]
      interval: 10s
      timeout: 5s
      retries: 5

  backend:  # 后端服务
    build:
      context: ./backend  # 构建上下文目录
      dockerfile: Dockerfile  # Dockerfile 路径
    container_name: inkwords-backend
    env_file:
      - ./backend/.env  # 从文件加载环境变量
    environment:
      # 覆盖 .env 中的配置,使用容器网络连接数据库
      - DATABASE_URL=postgres://inkwords:inkwords_password@db:5432/inkwords_db?sslmode=disable
      - FRONTEND_URL=http://localhost
    ports:
      - "8081:8080"  # 后端服务映射到 8081 端口
    volumes:
      - uploads:/app/uploads  # 上传文件持久化
    depends_on:  # 依赖关系
      db:
        condition: service_healthy  # 等待数据库健康检查通过
    restart: unless-stopped

  frontend:  # 前端服务
    build:
      context: ./frontend
      dockerfile: Dockerfile
    container_name: inkwords-frontend
    ports:
      - "80:80"    # HTTP 端口
      - "5173:80"  # 开发常用端口(兼容 OAuth 回调)
    depends_on:
      - backend  # 依赖后端服务
    restart: unless-stopped

volumes:  # 定义持久化卷
  pgdata:  # 数据库数据卷
  uploads: # 上传文件卷

关键概念解释

1. 容器间通信

注意后端连接数据库的 URL:

yaml 复制代码
DATABASE_URL=postgres://inkwords:inkwords_password@db:5432/inkwords_db?sslmode=disable

这里的 db 不是 localhost,而是服务名。Docker Compose 会自动创建一个内部网络,容器间可以通过服务名相互访问。

2. 数据持久化
yaml 复制代码
volumes:
  - pgdata:/var/lib/postgresql/data

这行配置创建了一个名为 pgdata 的 Docker Volume,将容器内的数据库数据持久化到宿主机。即使容器被删除,数据也不会丢失。

3. 健康检查
yaml 复制代码
healthcheck:
  test: ["CMD-SHELL", "pg_isready -U inkwords -d inkwords_db"]

后端服务通过 condition: service_healthy 确保数据库完全启动后才启动,避免了连接失败的问题。

三、多阶段构建:优化镜像体积

为什么需要多阶段构建?

直接构建的 Docker 镜像往往包含编译工具、源代码等不必要的文件,导致镜像体积庞大。多阶段构建可以显著减小最终镜像的大小。

后端 Dockerfile 解析

dockerfile 复制代码
# backend/Dockerfile

# 第一阶段:构建阶段
FROM golang:1.25-alpine AS builder  # 使用 Go 官方镜像作为构建环境

WORKDIR /app  # 设置工作目录

# 安装必要的系统依赖
RUN apk add --no-cache git tzdata

# 下载依赖(利用 Docker 层缓存)
COPY go.mod go.sum ./  # 先复制依赖文件
RUN go mod download    # 下载依赖(这一层会被缓存)

# 复制源码并构建
COPY . .  # 复制所有源代码
RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/server  # 编译

# 第二阶段:运行阶段
FROM alpine:3.19  # 使用极简的 Alpine 作为运行环境

WORKDIR /app

# 设置时区为上海,并安装必要的运行时依赖
RUN apk add --no-cache tzdata ca-certificates git && \
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone

# 从构建阶段复制编译产物
COPY --from=builder /app/server .

# 暴露端口
EXPOSE 8080

# 启动服务
CMD ["./server"]

关键优化点

  1. 分离构建和运行环境:构建阶段使用完整的 Go 环境,运行阶段使用极简的 Alpine
  2. 利用层缓存 :先复制 go.modgo.sum,这样依赖下载层可以被缓存,加快构建速度
  3. 静态编译CGO_ENABLED=0 确保生成静态二进制文件,不依赖系统库

前端 Dockerfile 解析

dockerfile 复制代码
# frontend/Dockerfile

# 第一阶段:构建阶段
FROM node:20-alpine AS builder

WORKDIR /app

# 复制 package 文件(利用缓存)
COPY package*.json ./

# 安装依赖
RUN npm ci  # 使用 ci 命令确保依赖版本精确

# 复制源码并构建
COPY . .
RUN npm run build  # 执行构建命令

# 第二阶段:Nginx 运行阶段
FROM nginx:alpine

# 设置时区为上海
RUN apk add --no-cache tzdata && \
    cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
    echo "Asia/Shanghai" > /etc/timezone

# 移除默认的 Nginx 静态文件
RUN rm -rf /usr/share/nginx/html/*

# 复制自定义 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 从构建阶段复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html

# 暴露端口
EXPOSE 80

# 启动 Nginx
CMD ["nginx", "-g", "daemon off;"]

四、一键启动:完整的开发体验

启动步骤

  1. 克隆项目
bash 复制代码
git clone https://github.com/2692341798/InkWords.git
cd InkWords
  1. 配置环境变量
bash 复制代码
# 复制后端环境变量模板
cp backend/.env.example backend/.env
# 编辑 .env 文件,配置必要的参数
  1. 一键启动所有服务
bash 复制代码
docker compose up -d --build
  1. 查看服务状态
bash 复制代码
docker compose ps
  1. 访问应用
  1. 查看日志
bash 复制代码
# 查看所有服务日志
docker compose logs -f

# 查看特定服务日志
docker compose logs -f backend
  1. 停止服务
bash 复制代码
docker compose down

架构流程图

外部服务
Docker 内部网络
静态文件请求
API 请求 /api/*
用户访问 http://localhost
Nginx 前端容器
请求类型判断
返回 React 构建产物
代理到后端容器
Go 后端容器
数据库操作
PostgreSQL 容器
调用 LLM API
DeepSeek API

五、工程规范:保证代码质量

后端规范要点

  1. 目录结构标准化

    backend/
    ├── cmd/server/ # 程序入口
    ├── internal/ # 内部包(外部无法导入)
    │ ├── api/ # HTTP 处理器
    │ ├── service/ # 业务逻辑
    │ ├── parser/ # 代码解析器
    │ └── llm/ # LLM 相关逻辑
    ├── pkg/ # 可公开的包
    └── go.mod # 依赖管理

  2. 依赖注入模式

go 复制代码
// 示例:使用依赖注入的业务服务
type BlogService struct {
    db     *gorm.DB
    llm    LLMClient
    parser CodeParser
}

func NewBlogService(db *gorm.DB, llm LLMClient, parser CodeParser) *BlogService {
    return &BlogService{
        db:     db,
        llm:    llm,
        parser: parser,
    }
}

前端规范要点

  1. 组件化开发
jsx 复制代码
// 示例:函数式组件
const BlogEditor = ({ content, onSave }) => {
    const [text, setText] = useState(content);
    
    return (
        <div className="editor-container">
            <textarea 
                value={text}
                onChange={(e) => setText(e.target.value)}
                className="w-full h-64 p-4 border rounded"
            />
            <button 
                onClick={() => onSave(text)}
                className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
            >
                保存
            </button>
        </div>
    );
};
  1. 状态管理分离
javascript 复制代码
// 示例:Zustand store
import { create } from 'zustand';

const useBlogStore = create((set) => ({
    blogs: [],
    loading: false,
    
    fetchBlogs: async () => {
        set({ loading: true });
        const response = await api.get('/api/blogs');
        set({ blogs: response.data, loading: false });
    },
    
    addBlog: async (blog) => {
        const response = await api.post('/api/blogs', blog);
        set((state) => ({ blogs: [...state.blogs, response.data] }));
    }
}));

总结

通过今天的讲解,你应该已经理解了:

  1. Monorepo 的优势:统一管理、简化协作、提升开发体验
  2. Docker 的价值:环境一致性、依赖隔离、简化部署
  3. 多阶段构建的重要性:减小镜像体积、提高安全性
  4. 工程规范的必要性:保证代码质量、便于团队协作

墨言博客助手的架构设计遵循了 "简单、清晰、可维护" 的原则。我们使用成熟的技术栈,结合合理的架构模式,打造了一个既适合学习又适合生产的全栈项目。

实践建议

  1. 本地开发 :直接使用 docker compose up 启动所有服务
  2. 代码修改 :修改代码后,使用 docker compose up -d --build 重新构建
  3. 调试技巧
    • 使用 docker compose logs -f 实时查看日志
    • 使用 docker exec -it inkwords-backend sh 进入容器内部
  4. 生产部署 :同样的 docker-compose.yml 稍作修改即可用于生产环境

记住:好的架构不是一开始就完美无缺的,而是在不断迭代中逐渐完善的。理解这些设计原则,比记住具体配置更重要。


下期预告:后端基石:Go 项目初始化与数据库模型设计

在下一篇文章中,我们将深入后端代码,学习:

  • 如何初始化一个标准的 Go 项目结构
  • 如何使用 GORM 设计数据库模型
  • 如何实现数据库迁移和种子数据
  • 如何编写可测试的业务逻辑代码

准备好你的 Go 开发环境,我们下期见!

相关推荐
搜佛说2 小时前
比SQLite更快,比InfluxDB更轻:sfsDb的降维打击
jvm·数据库·物联网·架构·sqlite·边缘计算·iot
提子拌饭1332 小时前
昼夜节律下的肝脏代谢清除率演算仪:基于鸿蒙Flutter的双路流场与酶解粒子对照架构
flutter·华为·架构·harmonyos·鸿蒙
SuperEugene3 小时前
前端通用基础组件设计:按钮/输入框/弹窗,统一设计标准|组件化设计基础篇
前端·javascript·vue.js·架构
贺小涛3 小时前
DeepSeek vs ChatGPT:技术架构深度解析与核心优势对比
chatgpt·架构
Ghost Face...3 小时前
Linux USB 全栈解析:OTG + Type-C + PD 内核架构(架构师级)
linux·c语言·架构
be to FPGAer3 小时前
架构与微架构设计
架构
fantasy_arch3 小时前
SVT-AV1 整体架构
架构·av1
一个有温度的技术博主4 小时前
Redis集群实战:如何实现节点的弹性伸缩与数据迁移?
redis·分布式·缓存·架构
永霖光电_UVLED4 小时前
氧化镓高体积热容的特性,集成高介电常数界面的结侧冷却架构
人工智能·生成对抗网络·架构·汽车·制造