干净的架构:与不同的层共享相同的模型/实体

Posted

技术标签:

【中文标题】干净的架构:与不同的层共享相同的模型/实体【英文标题】:Clean architecture: share same models/entities with different layers 【发布时间】:2019-04-29 17:07:56 【问题描述】:

在我的干净架构 android 应用程序设置中,我为每一层(数据、域、表示)都有自己的 Gradle 模块。我还为每一层都有自己的模型/实体,它们使用映射器从一层转换为另一层。这导致我有很多 kotlin 数据类,代表基本相同的东西,但在不同的层。这对我来说听起来不对。

简单示例:

数据层 - Android 库模块

@JsonClass(generateAdapter = true)
data class BuildingEntity(
    @Json(name = "u_id")
    val id: String,

    val name: String,

    val latitude: Double,

    val longitude: Double,

    @Json(name = "current_tenants")
    val tenants: List<TenantEntity>? = null
)

域层 - 纯 Kotlin 模块

data class Building(

    val id: String,

    val name: String,

    val location: CoordinatePoint,

    val tenants: List<Tenant>? = null

表示层 Android 应用模块

data class BuildingModel(

    val id: String,

    val name: String,

    val location: LatLng,

    val tenants: List<TenantModel> = listOf()
)

BuildingEntity是从外网api获取的。

这很好地将每个模块彼此分开,但在我的应用程序中,我有很多不同的实体具有嵌套结构。所以我最终写了很多 kotlin 数据类和映射器。

我怎样才能简化这个?我可以删除 Building 类并在数据和域层上使用 BuildingEntity 吗?只需在表示层将BuildingEntity 转换为BuildingModel

我正在努力寻找实用的答案,人们是如何解决这类问题的,而不是最终编写大量的数据类和映射器?

【问题讨论】:

【参考方案1】:

在我的领域模块中,我将模型作为接口(Kotlin 允许我们在接口中使用 val),数据模块中的实现,并且根本没有模型呈现。

看看这个小样本:

域名:

interface IUserModel 
    val id: String
    val name: String


interface UserRepository 
    fun getUserDetails(id: String): IUserModel

数据:

@Entity
data class UserEntity(
    @SerializedName("userId")
    override val id: String,
    override val name: String
) : IUserModel

class UserRepositoryImpl(val userDao: UserDao) : UserRepository 

    override fun getUserDetails(id: String): IUserModel 
        return userDao.getUser(id) //userDao.getUser returns a UserEntity
    

演示文稿:

class UserDetailsViewModel(val userId: String, val userRepository: UserRepository) : ViewModel() 
    val userData: LiveData<IUserModel> = MutableLiveData()
    fun getUserData() 
        (userData as MutableLiveData).postValue(userRepository.getUserDetails(userId))
    

没有映射器,没有大量的数据类。

我有几个具有这种结构的项目,有时需要一个映射器(将网络模型转换为数据库实体),但是使用接口作为域中的模型可以大大减少冗长。

【讨论】:

看起来不错!在我的案例中,我能看到的唯一问题是我想至少在表示层对象上使用一些 android 类型的对象,比如 LatLng。我的域 gradle 模块只有 kotlin,所以我不能在那里使用 android 的东西。 好吧,你总是可以使用扩展函数来实现这一点。在您的演示文稿中: fun Building.getPositionLatLon() = LatLon(location.lat,location.lon) 这只是一个建议,如果它变得复杂,您总是可以创建一个映射器;) 嗯,谢谢!我已经使用扩展函数将我的模型转换为另一个模型,它们非常方便。 是的,他们很棒。我希望您能像我一样找到将域模型用作接口的好方法。干杯 现在当我使用这种模式取得一些进展时,我在通过意图将模型传递给另一个活动时遇到了问题。所以我的模型需要实现 Parcable,但是由于模型(接口)是在数据层(纯 kotlin 模块)中定义的,所以我不能在那里使用 Parcable。也许我只是在数据层模型中实现 Parcable,并将表示层中的接口转换为 Parcable。不过,这听起来有点骇人听闻。【参考方案2】:

我知道这是一个老问题,但我想贡献一点。

所以,是的,这就是本书的干净架构。如果你想“打破”架构,那么我建议你放弃展示模型,而改用领域模型。

但是,在某些情况下,数据模型(实体)包含表示层不需要的信息。在那里,您需要域中的不同模型和表示层。不要传递不需要的信息!

【讨论】:

【参考方案3】:

实际上,您的做法是正确的。要使应用程序完全干净,您应该在每一层中使用不同的实体表示,并使用映射器对其进行转换。这允许您仅在需要更改某些实体时更改映射器。

例如,您可能会从服务器收到一些您不希望在 UI 中显示的数据,因此您的演示实体中没有这些数据。另一个示例是,如果您想更改数据实体中的参数名称。如果您直接从演示文稿中访问它,则需要更改所有访问权限。相反,如果您有映射器,您唯一需要做的就是更改映射器。

显然,清洁架构是一个复杂的架构,在大型和长期项目中更有意义,因为这些项目可能更频繁地发生变化。因此,如果您正在做一个小应用程序,那么如果您想摆脱大量代码,那也没关系。在这种情况下,我建议您对域和表示使用相同的实体,并为数据保留映射器,因为由于数据依赖于 API,因此更改不依赖于您,您将从映射器中获得一些好处。

【讨论】:

在项目所需的内容和做某事的“推荐方式”之间找到平衡本身就是一门艺术。但这是发展的一个重要方面。它正在快速和正确地做这件事之间找到平衡。 您好,感谢您的解释。我完全同意你的看法,但我有一个关于映射器的问题。我的域模块使用数据模块从缓存或远程检索数据,并且这些模块有单独的模型。我只需要一个映射器将实体映射到域模型,那么我应该将该映射器放在哪个模块中? 域模块不应该知道任何其他模块,因此映射器应该在数据模块中(因为它知道两个实体)。就像澄清一样小心依赖关系。如果您使用的是 Android,您可以将项目划分为模块并在 gradle 中指定哪个模块相互依赖。这样您就不会在域模块中放置任何依赖项,因此即使您愿意,也无法将映射器放入其中。

以上是关于干净的架构:与不同的层共享相同的模型/实体的主要内容,如果未能解决你的问题,请参考以下文章

两个不同的深度学习框架如何使用相同的模型?

在洋葱、六边形或干净架构中,域模型是不是可以包含与数据库中的域模型不同的属性?

使用相同的模型实体框架С#访问不同的表[关闭]

如何让asp.net成员提供者和实体框架共享同一个数据库

在两个不同的 Django 项目之间共享模型

SQLITE:选择共享相同外键的所有项目