很多后端项目刚开始都很简单,一个接口就是一个函数,参数解析、业务判断、数据库查询、响应拼装全写在一起,功能也确实能跑。但项目只要开始增长,代码很快就会变得越来越难维护。一个很典型的信号就是:改一个接口要同时看 handler、SQL、业务分支和各种工具函数,而且几乎找不到清晰边界。

项目分层的真正意义,不是为了套模板,而是为了让请求入口、业务逻辑和数据访问各自承担清楚的职责,这样代码才能在规模扩大时仍然保持可读、可测、可替换。

为什么分层问题会这么快暴露

因为后端服务天然要面对多个维度的变化。

请求参数会变,返回结构会变,业务规则会变,数据来源会变,下游依赖也会变。如果这些变化都挤在同一层,后面任何一次修改都可能牵一发而动全身。

而分层设计的价值,就是把变化隔离在更明确的边界里。

handler、service、repo 各自更适合做什么

handler 更适合处理 HTTP 层语义。

例如解析参数、校验基础格式、调用业务层、封装响应。

service 更适合承载业务规则。

例如状态判断、流程编排、聚合多个依赖、做核心业务决策。

repo 更适合封装数据访问。

例如 SQL 查询、缓存读写、持久化操作、对外部存储的访问。

这样拆的目的很直接:让每层只关注自己最擅长的一类变化。

为什么不要把 SQL 写满 handler

因为一旦这么做,请求控制逻辑和数据访问逻辑就绑死了。

以后你想换数据源、补缓存、复用查询逻辑、做单元测试,都会变得很痛苦。更糟的是,handler 很容易变成“巨型函数”,里面同时混着参数处理、权限判断、业务规则和数据库细节。

这种代码不一定立刻报错,但维护成本会非常高。

service 层为什么经常最容易被忽略

因为很多初学项目在规模不大时,会误以为 handler 直接调 repo 就够了。

但只要业务规则开始增长,例如一个接口要查多个数据源、校验状态、组合不同返回结构、调用下游服务,service 层就会变得非常必要。因为它提供了一个明确位置,让业务逻辑不必混在 HTTP 层,也不必污染数据层。

可以把它理解成“业务意图所在的地方”。

repo 层的意义不只是封装 SQL

很多人会觉得 repo 层是不是只是“把 SQL 挪个位置”。其实不是。

repo 层最大的价值,是把数据访问细节和上层解耦。这样上层只需要关心“取什么数据”,而不必关心“用 MySQL、Redis 还是别的存储怎么取”。

这对测试和重构都非常重要。因为一旦边界清楚,替换实现的成本就会显著下降。

分层和工程化能力之间的关系

分层设计并不是孤立的结构问题,它和日志、错误处理、测试、配置注入都有强关系。

如果分层清楚,你可以在 handler 层统一处理响应和错误,在 service 层集中记录业务上下文,在 repo 层清晰包裹数据访问错误。测试时也可以只测某一层,而不必每次都把整个服务跑起来。

所以分层设计并不是“架构洁癖”,它本质上是在为工程化铺路。

一个常见误区

一个常见误区是把分层做成机械转发:handler 调 service,service 立刻调 repo,完全没有新增语义。这会让人误以为分层只是多写样板代码。

真正合理的分层,不是为了层数而层数,而是每层都应该承载清晰职责。只要职责没有被拉开,再多层目录也不会让项目更清楚。

一个值得自己做的小练习

拿你现有的一个简单接口,故意重构成三层。

  • handler 只收参数和回响应
  • service 只处理业务流程
  • repo 只处理数据访问

然后再试着给它补一个测试。你会很容易发现:一旦层次清楚,测试位置、错误定位和未来扩展点都会更自然。

小结

项目分层不是为了让目录看起来专业,而是为了让后端服务在持续演进过程中仍然能保持边界稳定。handlerservicerepo 拆开的真正价值,在于把 HTTP 语义、业务规则和数据访问从一锅粥里分离出来。只要这一层意识建立起来,代码就会比单纯堆接口函数更容易长期维护。