网络层架构演进:从单模块到多模块
说实话,我见过太多项目一开始图省事,把网络请求代码直接写在Activity里。嗯,当时觉得挺爽的,后来项目一膨胀,那叫一个痛苦。今天我们就聊聊网络层架构的演进之路,从单模块到多模块,看看怎么把网络层做成一个独立的Module,并且把接口和实现彻底分离。
一、单模块时代的痛点
先说说单模块架构。早期项目,所有代码都在一个app模块里。网络层、业务层、UI层混在一起。我接手过一个项目,光网络请求就写了三种不同的方式:有的用OkHttp回调,有的用RxJava,还有的直接用AsyncTask。你想想看,维护起来有多崩溃。
单模块典型问题:
- 网络层代码散落在各个业务模块中,难以统一管理
- 接口定义和实现耦合,换一个网络库就要改所有调用方
- 单元测试困难,因为网络层和业务逻辑绑在一起
- 多人协作时容易冲突,大家都在改同一个模块
我曾经在一个项目里,因为要升级Retrofit版本,结果发现十几个地方都直接引用了Retrofit的注解。改了一个星期,还漏了几个。从那以后,我就下定决心要把网络层独立出来。
二、网络层独立Module
把网络层抽成独立的Module,是架构演进的第一步。说白了,就是给网络请求盖一个独立的房子,谁想用都得通过这个房子的门进来。
我习惯这样组织网络层Module:
// network-module 的结构
network/
├── api/ // 接口定义
├── model/ // 请求/响应模型
├── interceptor/ // 拦截器
├── converter/ // 数据转换
└── di/ // 依赖注入配置
这样做的好处很明显:
- 职责单一:网络层只关心网络通信,不关心业务逻辑
- 可替换性:想换网络库?只改这一个Module就行
- 复用性:多个App可以共用同一个网络Module
- 测试方便:可以单独测试网络层,不用启动整个App
我的经验:独立Module的命名要清晰。我一般用"network"或者"data-remote",不要用"lib"这种模糊的名字。团队新成员一看就知道这是干什么的。
三、接口与实现分离
这是网络层架构设计的核心思想。接口和实现分离,说白了就是定义好"做什么",但不告诉别人"怎么做"。
举个例子:
// 接口定义 - 放在api模块
interface UserApi {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: String): UserResponse
}
// 接口实现 - 放在impl模块
class UserApiImpl @Inject constructor(
private val retrofit: Retrofit
) : UserApi {
private val delegate = retrofit.create(UserApi::class.java)
override suspend fun getUser(id: String): UserResponse {
return delegate.getUser(id)
}
}
为什么要这么做?我遇到过这样一个场景:项目初期用Retrofit,后来因为某些原因要换成Ktor。如果接口和实现没有分离,那所有调用UserApi的地方都要改。但如果我们只依赖接口,换实现就只需要改一个地方。
注意:接口定义要放在公共模块,实现放在独立模块。业务层只依赖接口,不依赖实现。这样依赖关系就是单向的,不会形成循环依赖。
四、多模块架构下的网络层设计
当项目发展到多模块时,网络层的设计就要更讲究了。我一般这样划分:
// 多模块架构
app/ // 主模块
├── feature-home/ // 首页业务模块
├── feature-profile/ // 个人中心模块
├── network-api/ // 网络接口定义(公共)
├── network-impl/ // 网络接口实现(独立)
└── core/ // 核心公共模块
这里有个关键点:业务模块只依赖network-api,不依赖network-impl。那实现怎么注入呢?通过依赖注入框架,比如Dagger Hilt或者Koin。
// 在network-impl模块中提供实现
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideUserApi(retrofit: Retrofit): UserApi {
return UserApiImpl(retrofit)
}
}
业务模块使用时,只需要声明依赖接口:
class HomeViewModel @Inject constructor(
private val userApi: UserApi // 依赖接口,不是实现
) : ViewModel() {
// ...
}
这样,如果哪天要换网络实现,只需要改network-impl模块,其他模块完全不用动。我在项目中实践过,换网络库只花了半天时间,而且没有影响到任何业务代码。
五、架构演进路线图
下面这张图展示了网络层架构的演进过程:
六、避坑指南
最后分享几个我踩过的坑:
我曾经犯过的错误:
- 过度设计:一开始就搞了七八个Module,结果小项目反而开发效率变低。建议根据项目规模逐步演进。
- 接口定义太细:每个API都定义一个接口,导致接口爆炸。我后来习惯按业务领域分组,比如UserApi、OrderApi这样。
- 忽略版本管理:多Module之间依赖版本要统一管理,否则会出现版本冲突。建议用Gradle Version Catalog。
- 测试覆盖不足:接口分离后,一定要写单元测试验证接口定义是否正确。我吃过亏,接口改了但调用方没更新。
嗯,网络层架构演进这件事,说白了就是不断解耦的过程。从单模块到多模块,从耦合到分离,每一步都让代码更健壮、更易维护。你想想看,如果一开始就设计好,后面能省多少事?
我的建议:不要追求一步到位。先做独立Module,再逐步分离接口和实现。每走一步都要确保测试通过,团队也适应了新的架构风格。
好了,关于网络层架构演进就聊到这里。记住,好的架构不是设计出来的,是演进出来的。关键是保持方向正确,一步步往前走。
公众号:蓝海资料掘金营,微信deep3321