androidkotlinclean-architecturedata-layer

Android app clean architecture: Should data layer have its own model classes?


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. Image1

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.

enter image description here

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.

enter image description here

My question is: Which of the 3 approaches is the most flexible and future proof one?


Solution

  • If I were you, I would go with the Image1 process. I think it is the job of your datasource to take whatever object is coming from your database or API or any endpoint you have and transform it into something easier to user in your repository.

    Between Image1 and Image2, I think it depend of what data you want to store and what is the object you want to store. You may or may not need an ArticleEntity depending of your business rules. And if you don't need it, you don't have to create one.

    But for some use case you may need to create that object to add some informations to your Article. For exemple, if you want to place in that a validity date or any other information that you will only use in your repository, you can go for Image2.