What´s the best approach when developing an Android app and trying to follow clean architecture guidelines (but not extremely strict - cause that may be overkill for smaller projects).
In my case, I am unsure which approach is the best (if there is a best one) regarding the data layer and if the data layer should operate on its own model classes or if it may operate directly on the domain layer models.
Also, if the data layer should operate on its own model classes, should data sources like DB
or API
have their own models (like for an API
using Retrofit
and Gson
a model class with Gson
annotations) and then map to data layer models OR should the data layer model itself be the model returned by DB
and API
(this means the data layer model must be annotated for Gson
being able to parse it in case of Retrofit
and Gson
).
This is the case in this project: https://github.com/android10/Android-CleanArchitecture/blob/master/data/src/main/java/com/fernandocejas/android10/sample/data/entity/UserEntity.java
The following images should clarify the 3 approaches I mean:
In Image 1 the DB
and API
return specific model classes. In the case of the API
it may look like (using Retrofit
and Gson
):
class ArticleResponse(@SerializedName("source") val source: SourceResponse,
@SerializedName("author") val author: String?,
@SerializedName("title") val title: String,
@SerializedName("description") val description: String?,
@SerializedName("url") val url: String,
@SerializedName("urlToImage") val urlToImage: String?,
@SerializedName("publishedAt") val publishedAt: String?)
These are then mapped by the Local/Remote data sources to Article
models (which are used by the domain layer). Therefore the repository operates on domain layer models and is breaking boundaries right? That is approach 1.
In Image 2 the DB
and API
still return specific model classes. In the case of the API
it may look like (using Retrofit
and Gson
):
class ArticleResponse(@SerializedName("source") val source: SourceResponse,
@SerializedName("author") val author: String?,
@SerializedName("title") val title: String,
@SerializedName("description") val description: String?,
@SerializedName("url") val url: String,
@SerializedName("urlToImage") val urlToImage: String?,
@SerializedName("publishedAt") val publishedAt: String?)
However, these models are then mapped to a data-layer model (ArticleEntity
) which the data-layer operates on. When responding to the domain-layer the repository
maps these ArticleEntity
´s to the domain-layer model Article
. This doesn´t break boundaries (right) but it needs some extra mapping in the data-layer. This is approach 2.
In Image 3 the DB
and API
already return the data-layer model ArticleEntity
. Therefore this model class must contain all annotations needed to parse the API request (with Gson
):
class ArticleEntity(@SerializedName("source") val source: SourceResponse,
@SerializedName("author") val author: String?,
@SerializedName("title") val title: String,
@SerializedName("description") val description: String?,
@SerializedName("url") val url: String,
@SerializedName("urlToImage") val urlToImage: String?,
@SerializedName("publishedAt") val publishedAt: String?)
If the DB also needs some kind of annotations then these would have to be added to this class too (right?). An advantage of this approach I can think of is that there are fewer model classes (because DB and API directly map to data-layer models). However, doesn´t this blow up the data-layer model class with annotations/properties from all different data sources (DB, API)? Isn´t the whole point of abstracting the data sources from the repository violated then because the data-layer model is dependent on the specific data source implementation (e.g using Gson to parse API request with exact API response names). So this was approach 3.
My question is: Which of the 3 approaches is the most flexible and future proof one?