网络层架构演进:从单模块到多模块

说实话,我见过太多项目一开始图省事,把网络请求代码直接写在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模块,其他模块完全不用动。我在项目中实践过,换网络库只花了半天时间,而且没有影响到任何业务代码。

五、架构演进路线图

下面这张图展示了网络层架构的演进过程:

网络层架构演进路线 阶段一:单模块 所有代码在app模块 网络层与业务层耦合 接口与实现不分离 阶段二:独立Module 网络层抽成独立模块 职责单一,可替换 但仍未完全解耦 阶段三:接口分离 接口定义在公共模块 实现放在独立模块 依赖注入解耦 关键变化对比 • 代码组织:从混乱到清晰,每个模块职责明确 • 依赖关系:从循环依赖到单向依赖,架构更健康 • 可测试性:从难以测试到可以单独测试网络层 • 维护成本:从改一处动全身到局部修改不影响全局 • 团队协作:从频繁冲突到各模块独立开发

六、避坑指南

最后分享几个我踩过的坑:

我曾经犯过的错误:

  • 过度设计:一开始就搞了七八个Module,结果小项目反而开发效率变低。建议根据项目规模逐步演进。
  • 接口定义太细:每个API都定义一个接口,导致接口爆炸。我后来习惯按业务领域分组,比如UserApi、OrderApi这样。
  • 忽略版本管理:多Module之间依赖版本要统一管理,否则会出现版本冲突。建议用Gradle Version Catalog。
  • 测试覆盖不足:接口分离后,一定要写单元测试验证接口定义是否正确。我吃过亏,接口改了但调用方没更新。

嗯,网络层架构演进这件事,说白了就是不断解耦的过程。从单模块到多模块,从耦合到分离,每一步都让代码更健壮、更易维护。你想想看,如果一开始就设计好,后面能省多少事?

我的建议:不要追求一步到位。先做独立Module,再逐步分离接口和实现。每走一步都要确保测试通过,团队也适应了新的架构风格。

好了,关于网络层架构演进就聊到这里。记住,好的架构不是设计出来的,是演进出来的。关键是保持方向正确,一步步往前走。


公众号:蓝海资料掘金营,微信deep3321