在设计个人博客后端时,我面临一个经典抉择:用简单粗暴的单体应用,还是拥抱时髦的微服务?最终我选择了两者之间的 模块化单体架构(Modular Monolith),事实证明这是正确的决定。
微服务固然优秀,但对于个人项目来说成本太高:
而传统单体应用又有明显缺陷:所有代码堆在一起,模块边界模糊,时间长了就变成无法维护的"大泥球"。
模块化单体是两者的平衡点:
| 特性 | 传统单体 | 微服务 | 模块化单体 |
|---|---|---|---|
| 开发效率 | ✅ 高 | ❌ 低 | ✅ 高 |
| 模块隔离 | ❌ 差 | ✅ 强 | ✅ 强 |
| 运维复杂度 | ✅ 低 | ❌ 高 | ✅ 低 |
| 微服务演进 | ❌ 难 | — | ✅ 易 |
我的博客后端分为 6 个 Maven 模块:
personal-blog-backend/
├── blog-common/ # 公共基础模块(工具类、异常、基础框架)
├── blog-modules/
│ ├── blog-module-system/ # 用户/角色/认证
│ │ ├── blog-system-api/ # 契约层:DTO、VO、Interface
│ │ └── blog-system-service/ # 实现层:Controller、Service、Entity
│ ├── blog-module-article/ # 文章/分类/标签
│ ├── blog-module-comment/ # 评论/审核/举报
│ └── blog-module-file/ # 文件上传(S3 兼容)
└── blog-application/ # 启动入口 + 全局配置 + Flyway 迁移
每个业务模块(除 file)都分为两层:
*-api 层(契约层),只包含:
// blog-system-api 模块中的跨模块接口
public interface RemoteUserService {
UserDTO getUserById(Long userId);
List<UserDTO> getUsersByIds(List<Long> userIds);
}
*-service 层(实现层),包含:
关键规则:Entity 是私有资产,绝不能跨模块直接引用。对外通信必须使用 DTO/VO。
blog-application ──→ 所有 *-service
blog-*-service ──→ blog-*-api(自己的 API)
──→ blog-common
──→ 其他模块的 *-api(跨模块调用)
blog-*-api ──→ blog-common
禁止的依赖:
*-service 不能依赖另一个 *-service(会造成模块耦合,无法拆分)这是最重要的架构红线。文章模块需要用户信息时,不能这样做:
-- ❌ 禁止:文章模块直接 JOIN 用户表
SELECT a.*, u.username
FROM art_article a
JOIN sys_user u ON a.author_id = u.id
必须通过接口调用:
// ✅ 正确做法
List<ArticleEntity> articles = articleMapper.selectList(wrapper);
List<Long> authorIds = articles.stream()
.map(ArticleEntity::getAuthorId)
.toList();
// 通过接口(将来可以替换为 Feign Client)
List<UserDTO> users = remoteUserService.getUsersByIds(authorIds);
光靠约定俗成容易被打破,我引入了 ArchUnit 进行自动化架构测试:
@ArchTest
static final ArchRule services_should_not_depend_on_other_services =
noClasses()
.that().resideInAPackage("..service..")
.should().dependOnClassesThat()
.resideInAPackage("..other.service..");
每次 CI 构建都会自动校验架构规则,确保没有人意外引入了违规依赖。
当某个模块需要独立扩展时,演进成本极低:
*-app 启动模块// 当前:本地 Bean 调用
@Service
public class RemoteUserServiceImpl implements RemoteUserService {
private final UserService userService;
// 直接调用
}
// 微服务化后:只需替换实现类
@FeignClient(name = "user-service")
public interface RemoteUserService {
@GetMapping("/api/v1/users/{id}")
UserDTO getUserById(@PathVariable Long id);
}
业务代码无需任何修改,因为接口定义没有变。这正是模块化单体的最大价值。
模块化单体架构让我在个人开发效率和代码可维护性之间找到了最优解:
如果你也在做中小型项目,不妨考虑这个"被低估"的架构模式。
在做个人博客后端时,我发现每个业务模块都在重复同样的代码:创建实体、校验参数、调用 Mapper、处理异常……于是我设计了 BaseServiceImpl,一个基于 MyBatis-Plus 和 MapStruct 的通用 CRUD 基类。
作为一个人项目,我的博客系统包含 4 个独立仓库:后端(Spring Boot)、前端(Next.js 博客展示)、管理后台(Next.js Admin)、部署配置(Docker Compose)。最初我每次更新代码都要手动构建镜像、推送 Docker Hub、SSH 到 VPS 拉取最新版——这套流程繁琐且容易出错。
博客系统的管理后台需要处理文章的增删改查、评论审核、文件上传、系统监控等功能。这类界面有一个共同特点:大量表单 + 大量数据展示 + 频繁的服务端交互。 本文记录我在管理后台中选用的技术栈,以及每个选型背后的理由。 --- 技术栈总览 | 层级 | 技术 | 版本 | 用途 | |:---|:---|:---|:---| | 框架 | Next.
从数据库到用户界面,这篇文章记录我在个人博客系统中每一层技术选型背后的思考。整个系统包含 4 个独立仓库,涵盖 Java 后端、两个 Next.js 前端、和 Docker 部署配置。 --- 一、整体架构图 ` 用户浏览器 │ ▼ Caddy(反向代理 + 自动 HTTPS) │ ├── chonkybird.com → Next.js 博客前端(SSR) ├── admin.
这篇文章记录我在开发个人博客系统全程中使用 AI 工具的真实体验——包括它在哪些环节显著提升了效率,在哪些地方需要谨慎,以及如何正确地使用 AI。 整个系统包含 Spring Boot 后端、两个 Next.js 前端、Docker Compose 部署配置,共 4 个独立仓库,全程有 AI 深度介入。