1. 什么是Service的语义?
在软件架构中,Service不是一个新名词,但它的语义经常被误解。Service本质上代表了一个清晰的业务逻辑边界,就像国境线一样明确地区分不同业务领域的职责。
想象这样一个场景:当你的Token处理逻辑散落在路由守卫、登录模块和axios拦截器中时,就像把厨房用具分散在卧室、客厅和阳台 - 每次做饭都要四处收集工具。而Service就是你的"厨房",把所有烹饪相关的工具和食材有序地收纳在一起。
2. 为什么需要Service层?
最近我在重构一个项目时遇到典型问题:
- Token刷新逻辑同时存在于5个文件中
- 登录流程修改需要改动3个不同目录下的代码
- 新成员很难找到权限校验的实现位置
这些正是缺乏Service层导致的"逻辑碎片化"症状。良好的Service设计应该像图书馆的分类系统,让每段代码都有明确的归属位置。
3. Service的典型特征
一个设计良好的Service通常具有以下特征:
- 明确的契约:清晰的输入输出定义
interface AuthService {
login(credential: Credential): Promise<AuthResult>;
logout(): void;
refreshToken(): Promise<Token>;
}
-
技术无关性:不关心调用方是组件、路由还是其他Service
-
业务完整性:封装完整的业务流
class OrderService {
async placeOrder(order: Order) {
// 验证 -> 计算 -> 创建 -> 支付 -> 通知
// 这一系列操作对外是原子的
}
}
4. 粒度控制的艺术
Service的粒度选择就像摄影时的焦距调整:
- 广角镜头:AuthService(包含认证、授权、Token管理等)
- 长焦镜头:拆分为LoginService、TokenService、PermissionService
判断标准:
- 如果一组逻辑总是同时变化 → 保持在一起
- 如果经常需要独立修改 → 考虑拆分
- 如果被多个Service依赖 → 应该独立
5. 前端项目中的落地实践
5.1 项目结构示例
src/
├── services/ # Service层
│ ├── auth/ # 认证领域
│ │ ├── token.service.ts
│ │ └── sso.service.ts
│ └── order/ # 订单领域
├── api/ # 纯API通信
└── features/ # 功能模块
└── checkout/
├── components/
└── hooks/ # 领域hooks
5.2 渐进式演进案例
阶段1:简单项目
// 直接在组件中调用API
function LoginForm() {
const login = async () => {
const res = await axios.post('/login', form);
localStorage.setItem('token', res.token);
}
}
阶段2:提取Service
// services/auth.service.ts
class AuthService {
async login(credential: Credential) {
const res = await api.post('/login', credential);
this.saveToken(res.token);
return res.user;
}
}
阶段3:领域拆分
// services/auth/token.service.ts
class TokenService {
private storage: TokenStorage;
saveToken(token: string) {
this.storage.set(token);
}
}
6. 高质量Service的五个特征
- 单一职责:每个Service只做一件事
- 明确边界:知道什么该做,什么不该做
- 可测试性:不依赖UI框架或特殊环境
- 稳定接口:内部实现可变但接口稳定
- 领域语言:使用业务术语而非技术术语
7. 总结
Service不是银弹,但它是解决"逻辑碎片化"的有效手段。就像整理房间一样,好的Service设计让每个业务概念都有明确的"归属地"。下次当你发现自己在代码库中"捉迷藏"时,不妨思考:是不是该引入或调整Service层了?
架构不是从第一行代码就开始完美的,而是在不断演进中逐渐找到最合适的形态。Service层的设计也是如此 - 它应该随着你对业务理解的深入而不断进化。