在 Android 干净架构中映射到域的正确位置

Tha*_*man 8 android kotlin clean-architecture

我和我的同事正在争论哪里才是将实体对象或远程 dto 对象映射到简单域对象的正确位置。

我们的结构看起来像这样。

源(包括 dao)> 存储库(包括源)> 用例(包括存储库)

我的同事认为映射到域应该在源内部完成,以便域对象可以传递到下一层,如下所示

class SomeSourceImpl(private val dao: Dao) : SomeSource {
    override fun get(): Observable<DomainModel> {
        return dao.getResponse().map { it.mapToDomain() }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的同事认为,根据鲍勃叔叔的说法,这是由于依赖规则造成的。

该规则规定源代码依赖关系只能指向内部。内圈中的任何事物都无法了解外圈中的任何事物。特别是,在内圈中的代码中不得提及外圈中声明的内容的名称。这包括函数、类。变量,或任何其他命名的软件实体。

我非常不同意直接映射到源内部域的方法,因为这样存储库就会变得贫乏,我们因此采用贫乏存储库无用的反模式,它们所做的就是盲目传播来自源的所有内容。(现在你可能会说源也很贫乏,我们可以简单地删除它们并将 dao 对象直接包含到存储库中,但这在我们的例子中是不可能的)。

相反,我建议源返回原始数据库实体(如果我们进行休息调用,则返回远程实体),因为源返回原始数据以供以后处理是有意义的。存储库的工作是从源获取结果,然后将其映射到域,最后将此域对象传播到类似这样的用例。

class SomeRepoImpl(private val someSource: SomeSource) : SomeRepo {
    override fun get(haId: String): Observable<DomainModel> {
        return otherAssetSource.get().map { it.mapToDomain() }
    }
Run Code Online (Sandbox Code Playgroud)

我还在 github 上发现了一些示例,它们映射到存储库内的域而不是源

这里

这里

这里

也是 iOS 版的

关于可以将实体映射到域对象的位置,干净架构原则中的严格规则是什么?

Key*_*öze 9

引用规则

源代码依赖只能指向内部

我想这取决于架构。让我用一个例子来解释这一点:

建筑学:

DOMAIN <- DATA <- PRESENTATION
Run Code Online (Sandbox Code Playgroud)

在哪里:

DATA -> LOCAL  
|  
v  
REMOTE
Run Code Online (Sandbox Code Playgroud)

注意: DOMAIN 代表最内圈,PRESENTATION 代表最外圈。

现在 DOMAIN 是一个纯 Kotlin 模块,没有任何 Android 依赖项。让我们定义一个存储库:

DOMAIN <- DATA <- PRESENTATION
Run Code Online (Sandbox Code Playgroud)

我们在数据层(这是一个 Android 库)中实现它:

class ProfileRepositoryImpl(
    private val networkManager: NetworkManager,
    private val remoteDataSource: ProfileRemoteDataSource,
    private val localDataSource: ProfileLocalDataSource
): ProfileRepository {
     
    override fun getProfile(): Profile? {
        return if(networkManager.isNetworkAvailable) {
            localDataSource.insert(remoteDataSource.get())
        } else {
            localDataSource.get()
        }
    }

    override fun updateProfile(profile: Profile): Profile {
        val updatedProfile = remoteDataSource.update(profile)
        return localDataSource.insert(updatedProfile)
    }
}
Run Code Online (Sandbox Code Playgroud)
DATA -> LOCAL  
|  
v  
REMOTE
Run Code Online (Sandbox Code Playgroud)
interface ProfileRepository {
    
    fun getProfile(): Profile?

    fun updateProfile(profile: Profile): Profile
}
Run Code Online (Sandbox Code Playgroud)
class ProfileRepositoryImpl(
    private val networkManager: NetworkManager,
    private val remoteDataSource: ProfileRemoteDataSource,
    private val localDataSource: ProfileLocalDataSource
): ProfileRepository {
     
    override fun getProfile(): Profile? {
        return if(networkManager.isNetworkAvailable) {
            localDataSource.insert(remoteDataSource.get())
        } else {
            localDataSource.get()
        }
    }

    override fun updateProfile(profile: Profile): Profile {
        val updatedProfile = remoteDataSource.update(profile)
        return localDataSource.insert(updatedProfile)
    }
}
Run Code Online (Sandbox Code Playgroud)

LOCAL模块是一个独立于任何依赖项的 Android 库,并公开DAOEntity对象:

interface ProfileDao {
    fun insert(profile: ProfileEntity)
    fun get(): ProfileEntity?
}
Run Code Online (Sandbox Code Playgroud)

同样,对于REMOTE模块:

interface ProfileApi {
    fun get(): ProfileDto
    fun update(profile: ProfileDto): ProfileDto
}
Run Code Online (Sandbox Code Playgroud)

Source因此,让类返回DTOEntity对象对我来说没有意义。回购类看起来像这样:

class ProfileRepositoryImpl(
    private val networkManager: NetworkManager,
    private val remoteDataSource: ProfileRemoteDataSource,
    private val remoteDataMapper: Mapper<ProfileDto, Profile>,
    private val localDataSource: ProfileLocalDataSource,
    private val localDataMapper: Mapper<ProfileEntity, Profile>
) : ProfileRepository {

    override fun getProfile(): Profile? {
        if (networkManager.isNetworkAvailable) {
            val dto = remoteDataSource.get()
            val profile = remoteDataMapper.toModel(dto)
            val entity = localDataMapper.fromModel(profile)

            localDataSource.insert(entity)
        }

        return localDataSource.get()?.let(localDataMapper::toModel)
    }

    override fun updateProfile(profile: Profile): Profile {
        val request = remoteDataMapper.fromModel(profile)
        val dto = remoteDataSource.update(request)
        
        val updatedProfile = remoteDataMapper.toModel(dto)
        
        val entity = localDataMapper.fromModel(updatedProfile)
        localDataSource.insert(entity)

        return localDataMapper.toModel(
            requireNotNull(localDataSource.get())
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

在您的示例中,您仅GET考虑了操作。在这里,对于操作我们还UPDATE需要映射对象。DOMAIN因此,当我们添加更多功能时,如果对象的映射是在 Repo 类中完成的,那么 Repo 类会变得非常混乱。

我相信这将取决于系统的整体架构。

  • @mars8 就我而言,目的是处理来自各种来源的数据。此外,在我的大多数用例中,某些数据源在多个存储库中使用,因此我不是在多个存储库中映射,而是在数据源中进行映射。 (3认同)