首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >从零搭建三层架构:一个博客项目的架构演进实录

从零搭建三层架构:一个博客项目的架构演进实录

原创
作者头像
用户12609250
发布2026-07-31 18:14:51
发布2026-07-31 18:14:51
970
举报

一、当代码开始“长胖”

一年前,我接手了一个博客项目的维护工作。打开项目时,我看到了这样的场景:

一个 Controller 文件里塞了 800 多行代码,包含数据库操作、业务逻辑、参数校验、异常处理——所有事情都在同一个文件里解决。每次加一个新功能,都要在同一个文件里找到合适的位置“塞进去”,代码像滚雪球一样越滚越大。

这就是典型的集中式架构问题:功能优先,架构靠后,所有逻辑堆在一起,状态管理混乱,维护成本随着需求迭代指数级上升。

当时我面临的核心问题是:如何让代码结构清晰到每个新人半天就能上手?

答案是:三层架构


二、什么是三层架构

三层架构(Three-Tier Architecture)是一种经典的软件架构模式,将应用程序按职责划分为三个层次:

代码语言:javascript
复制
┌─────────────────────────────────────────────────────┐
│  表现层(Presentation Layer / Controller)          │
│  职责:接收用户请求,返回响应,不包含业务逻辑        │
└────────────────────┬────────────────────────────────┘
                     ▼
┌─────────────────────────────────────────────────────┐
│  业务逻辑层(Business Logic Layer / Service)       │
│  职责:执行核心业务规则、流程编排、事务管理          │
└────────────────────┬────────────────────────────────┘
                     ▼
┌─────────────────────────────────────────────────────┐
│  数据访问层(Data Access Layer / Repository)       │
│  职责:与数据库交互,封装 CRUD 操作                 │
└─────────────────────────────────────────────────────┘

三层架构的核心价值是职责分离:每一层只关注自己该做的事,层与层之间通过接口通信,互不干扰。


三、重构实战:三层架构落地

3.1 项目背景

这是一个简易博客系统,支持:

  • 发布文章
  • 查看文章列表
  • 根据 ID 查看单篇文章
  • 删除文章

3.2 重构前的代码(反面教材)

重构前,所有逻辑集中在 Controller 中:

代码语言:javascript
复制
// 重构前:BlogController.java - 把所有事情塞在一起
@RestController
@RequestMapping("/api/posts")
public class BlogController {
    
    // 硬编码数据存储——用 static List 代替数据库
    private static List<Post> posts = new ArrayList<>();
    private static Long idCounter = 1L;
    
    @GetMapping
    public List<Post> getAllPosts() {
        return posts;  // 直接返回数据,没有任何业务处理
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<Post> getPostById(@PathVariable Long id) {
        // 查找逻辑直接写在 Controller 里
        for (Post post : posts) {
            if (post.getId().equals(id)) {
                return ResponseEntity.ok(post);
            }
        }
        return ResponseEntity.notFound().build();
    }
    
    @PostMapping
    public Post createPost(@RequestBody Post newPost) {
        // 创建逻辑也写在 Controller 里
        newPost.setId(idCounter++);
        posts.add(newPost);
        return newPost;
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deletePost(@PathVariable Long id) {
        // 删除逻辑也写在 Controller 里
        posts.removeIf(post -> post.getId().equals(id));
        return ResponseEntity.noContent().build();
    }
}

这段代码的问题一目了然:

  • Controller 既管请求响应,又管数据存储
  • 业务逻辑(如创建文章时的校验)无处安放
  • 数据源切换(从内存到数据库)需要改 Controller 代码
  • 单元测试无从下手

3.3 分层重构

第一层:数据访问层(Repository)

定义接口,抽象数据操作:

代码语言:javascript
复制
// 数据访问层接口
public interface PostRepository {
    List<Post> findAll();
    Optional<Post> findById(Long id);
    Post save(Post post);
    void deleteById(Long id);
}

接口实现(内存版本,后续可无缝切换为 JPA):

代码语言:javascript
复制
// 内存实现
@Repository
public class InMemoryPostRepository implements PostRepository {
    private final List<Post> posts = new ArrayList<>();
    private Long idCounter = 1L;
    
    @Override
    public List<Post> findAll() {
        return new ArrayList<>(posts);
    }
    
    @Override
    public Optional<Post> findById(Long id) {
        return posts.stream()
            .filter(post -> post.getId().equals(id))
            .findFirst();
    }
    
    @Override
    public Post save(Post post) {
        if (post.getId() == null) {
            post.setId(idCounter++);
            posts.add(post);
        } else {
            // 更新逻辑
            deleteById(post.getId());
            posts.add(post);
        }
        return post;
    }
    
    @Override
    public void deleteById(Long id) {
        posts.removeIf(post -> post.getId().equals(id));
    }
}
第二层:业务逻辑层(Service)

封装核心业务规则:

代码语言:javascript
复制
@Service
public class PostService {
    
    private final PostRepository postRepository;
    
    // 构造器注入
    public PostService(PostRepository postRepository) {
        this.postRepository = postRepository;
    }
    
    public List<Post> getAllPosts() {
        return postRepository.findAll();
    }
    
    public Optional<Post> getPostById(Long id) {
        return postRepository.findById(id);
    }
    
    public Post createPost(Post post) {
        // 业务规则:标题不能为空
        if (post.getTitle() == null || post.getTitle().trim().isEmpty()) {
            throw new IllegalArgumentException("文章标题不能为空");
        }
        // 业务规则:内容不能为空
        if (post.getContent() == null || post.getContent().trim().isEmpty()) {
            throw new IllegalArgumentException("文章内容不能为空");
        }
        // 业务规则:限制敏感词(示例)
        if (post.getContent().contains("敏感词")) {
            throw new IllegalArgumentException("内容包含违规词汇");
        }
        return postRepository.save(post);
    }
    
    public void deletePost(Long id) {
        // 业务规则:检查文章是否存在
        if (!postRepository.findById(id).isPresent()) {
            throw new IllegalArgumentException("文章不存在");
        }
        postRepository.deleteById(id);
    }
}
第三层:表现层(Controller)

只负责 HTTP 请求响应,不包含任何业务逻辑:

代码语言:javascript
复制
@RestController
@RequestMapping("/api/posts")
public class PostController {
    
    private final PostService postService;
    
    // 构造器注入 Service
    public PostController(PostService postService) {
        this.postService = postService;
    }
    
    @GetMapping
    public ResponseEntity<List<Post>> getAllPosts() {
        return ResponseEntity.ok(postService.getAllPosts());
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<Post> getPostById(@PathVariable Long id) {
        return postService.getPostById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
    
    @PostMapping
    public ResponseEntity<?> createPost(@RequestBody Post newPost) {
        try {
            Post saved = postService.createPost(newPost);
            return ResponseEntity.status(HttpStatus.CREATED).body(saved);
        } catch (IllegalArgumentException e) {
            // 业务层抛出的异常在这里被捕获并返回友好信息
            return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
        }
    }
    
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deletePost(@PathVariable Long id) {
        try {
            postService.deletePost(id);
            return ResponseEntity.noContent().build();
        } catch (IllegalArgumentException e) {
            return ResponseEntity.notFound().build();
        }
    }
}

3.4 目录结构(按功能组织)

重构后的项目结构清晰了很多:

代码语言:javascript
复制
src/main/java/com/blog/
├── controller/
│   └── PostController.java          # 表现层
├── service/
│   ├── PostService.java             # 业务逻辑层(接口)
│   └── impl/
│       └── PostServiceImpl.java     # 业务逻辑层(实现)
├── repository/
│   └── PostRepository.java          # 数据访问层
├── entity/
│   └── Post.java                    # 实体类
└── dto/
    └── PostRequest.java             # 请求/响应 DTO

四、重构后的收益

4.1 对比表

维度

重构前

重构后

Controller 行数

80+ 行

~50 行

单一职责

❌ Controller 管所有

✅ 每层各司其职

业务逻辑复用

❌ 无法复用

✅ Service 可被多处调用

数据源切换

❌ 需改 Controller

✅ 改 Repository 实现即可

单元测试难度

❌ 高(需启动 Web 容器)

✅ 低(可独立测试 Service)

4.2 单元测试变得简单

重构后,测试 Service 层只需要 mock Repository:

代码语言:javascript
复制
@ExtndWith(MockitoExtension.class)
class PostServiceTest {
    
    @Mock
    private PostRepository repository;
    
    @InjectMocks
    private PostService service;
    
    @Test
    void createPost_shouldThrowWhenTitleEmpty() {
        Post invalidPost = new Post();
        invalidPost.setTitle("");
        invalidPost.setContent("valid content");
        
        assertThrows(IllegalArgumentException.class, 
            () -> service.createPost(invalidPost));
    }
    
    @Test
    void createPost_shouldSaveWhenValid() {
        Post validPost = new Post();
        validPost.setTitle("Valid Title");
        validPost.setContent("Valid Content");
        
        when(repository.save(any(Post.class))).thenReturn(validPost);
        
        Post saved = service.createPost(validPost);
        assertNotNull(saved);
        verify(repository, times(1)).save(validPost);
    }
}

五、更进一步的架构分层

如果项目规模继续扩大,可以引入更细的分层:

代码语言:javascript
复制
┌─────────────────────────────────────────────────────┐
│  表现层:Controller / API Gateway                   │
└────────────────────┬────────────────────────────────┘
                     ▼
┌─────────────────────────────────────────────────────┐
│  应用层:Application Service(编排多个领域服务)     │
└────────────────────┬────────────────────────────────┘
                     ▼
┌─────────────────────────────────────────────────────┐
│  领域层:Domain Service(核心业务逻辑)             │
└────────────────────┬────────────────────────────────┘
                     ▼
┌─────────────────────────────────────────────────────┐
│  基础设施层:Repository / MQ / Cache                │
└─────────────────────────────────────────────────────┘

这种分层在 DDD(领域驱动设计)中更为常见,适合复杂业务场景。


六、总结

三层架构的核心不是“分三个文件夹”,而是通过职责分离来降低认知负荷,让每段代码都有明确的归属。当你在写代码时,不妨多问一句:“如果三个月后有人接手这段代码,他能轻松理解并修改吗?”答案就是你架构演进的方向。

对于中小型项目,三层架构已经足够应对 80% 的业务场景。它能带来的收益是实实在在的:降低维护成本、提升可测试性、让新人更快上手

如果你正在维护一个“能跑就行”的项目,不妨从今天开始,试试用三层架构的思路进行一次小重构——哪怕只抽离一个 Service 类,也会让代码变得更好一点。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、当代码开始“长胖”
  • 二、什么是三层架构
  • 三、重构实战:三层架构落地
    • 3.1 项目背景
    • 3.2 重构前的代码(反面教材)
    • 3.3 分层重构
      • 第一层:数据访问层(Repository)
      • 第二层:业务逻辑层(Service)
      • 第三层:表现层(Controller)
    • 3.4 目录结构(按功能组织)
  • 四、重构后的收益
    • 4.1 对比表
    • 4.2 单元测试变得简单
  • 五、更进一步的架构分层
  • 六、总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档