Reputation: 3367
In my clean architecture Android app setup, I have own Gradle module for each layer (data, domain, presentation). I also have own models/entities for each layers, which are converted from one layer to another using mappers. This leads to situation where I have a lot of kotlin data classes, representing basically same thing, but in different layer. This does not sound right to me.
Simple example:
Data layer - Android library module
@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
)
Domain layer - Pure Kotlin module
data class Building(
val id: String,
val name: String,
val location: CoordinatePoint,
val tenants: List<Tenant>? = null
Presentation layer Android app module
data class BuildingModel(
val id: String,
val name: String,
val location: LatLng,
val tenants: List<TenantModel> = listOf()
)
BuildingEntity
is fetched from external network api.
This nicely separates each modules from each other, but in my app I have a lot of different entities with nested structures. So I end up writing a lot of kotlin data classes and mappers.
How I can simplify this? Can I remove Building
class and use BuildingEntity
on data and domain layer? Just convert BuildingEntity
to BuildingModel
on presentation layer?
Im trying find practical answers, how people are solving this kind of problem, not ending up to writing tons of data classes and mappers?
Upvotes: 12
Views: 12063
Reputation: 2091
Actually, it's right the way you are doing it. To make an app totally Clean you should have different representations of you entities in each layer, and convert them with mappers. This allow you to only change the mappers when you need to change some entity.
For example you may receive some data from the server that you don't want to be shown in UI, so you don't have it in your presentation entity. Another example would be if you want to change an argument name in the data entity. If you access it directly from presentation, you will need to change all the accesses. Instead, if you have mappers, the only thing you have to do is to change the mapper.
Obviously, Clean Architecture is a complex one, that get more sense in big and long time projects, where changes may arise more often. So, it's okay if you want to get rid of doing lot of code if you are doing a little app. In this case i will advice you to use the same entity for Domain and Presentation, and keep mappers for data ones, because as data depends on API, that changes doesn't depend on you, and you would get some benefits from mappers.
Upvotes: 12
Reputation: 1084
I know this is an old question, but I would like to contribute a bit.
So, yes this is clean architecture by the book. If you want to "break" the architecture, then I would suggest you to drop the Presentation Models, and use the Domain models instead.
However, there are cases that a data model(entity) contains information that is not needed in the presentation layer. There you need a different model in the domain, and presentation layer. Do not pass information that is not needed!
Upvotes: 4
Reputation: 2626
In my domain module I have my models as interfaces (Kotlin allow us to have vals inside interfaces), the implemenations in data module and no models in presentation at all.
Take a look at this small sample:
domain:
interface IUserModel {
val id: String
val name: String
}
interface UserRepository {
fun getUserDetails(id: String): IUserModel
}
data:
@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
}
}
presentation:
class UserDetailsViewModel(val userId: String, val userRepository: UserRepository) : ViewModel() {
val userData: LiveData<IUserModel> = MutableLiveData()
fun getUserData() {
(userData as MutableLiveData).postValue(userRepository.getUserDetails(userId))
}
}
No mappers, no tons of data classes.
I have a couple of projects with this sctructure and sometimes a mapper is needed (convert network models to database entities) but the verbosity is widely reduced using interfaces as models in domain.
Upvotes: 19