'm working on an Android application using the MVVM architecture with clean code principles. The app has three layers:
UI -> Domain -> Data (Repository -> Data Source)
From the data layer, I want to return a domain object to maintain separation of concerns. However, I'm unsure where to handle the mapping between the data transfer object (DTO) and the domain object—whether it should be done in the repository or the data source.
Here’s an example of mapping in the repository:
class MyRepository(private val dataSource: MyDataSource) {
suspend fun fetchData(): Either<Failure, MyData> {
return when (val result = dataSource.getMyData()) {
is Either.Left -> result
is Either.Right -> Either.Right(result.b.toDomain())
}
}
interface MyDataSource {
suspend fun getMyData(): Either<Failure, MyDataDto>}
class MyRemoteDataSourceImpl(private val apiService: ApiService) : MyDataSource {
override suspend fun getMyData(): Either<Failure, MyDataDto> {
return try {
val response = apiService.getData()
if (response.isSuccessful) {
response.body()?.let {
Either.Right(it)
} ?: Either.Left(Failure.UnknownError("Response body is null"))
} else {
Either.Left(Failure.ServerError)
}
} catch (e: IOException) {
Either.Left(Failure.NetworkError)
} catch (e: Exception) {
Either.Left(Failure.UnknownError(e.localizedMessage ?: "Unknown error"))
}
}}
With this approach, I feel like the repository is tightly coupled to the data source. If the data source changes and returns a different DTO, the repository would also need to be modified.
Other option would be mapping on data source, however with that approach data source will have a lot of to do, call api and mapping to domain(Data source dont know)
Third option would be mapping to repository then in repository map to domain. With that approach we could make some of separation however we must have two mappers.
Last option is a interface that DTo will extends with parameters and a mapper to domain.
Please let me know which option you think is the best
'm working on an Android application using the MVVM architecture with clean code principles. The app has three layers:
UI -> Domain -> Data (Repository -> Data Source)
From the data layer, I want to return a domain object to maintain separation of concerns. However, I'm unsure where to handle the mapping between the data transfer object (DTO) and the domain object—whether it should be done in the repository or the data source.
Here’s an example of mapping in the repository:
class MyRepository(private val dataSource: MyDataSource) {
suspend fun fetchData(): Either<Failure, MyData> {
return when (val result = dataSource.getMyData()) {
is Either.Left -> result
is Either.Right -> Either.Right(result.b.toDomain())
}
}
interface MyDataSource {
suspend fun getMyData(): Either<Failure, MyDataDto>}
class MyRemoteDataSourceImpl(private val apiService: ApiService) : MyDataSource {
override suspend fun getMyData(): Either<Failure, MyDataDto> {
return try {
val response = apiService.getData()
if (response.isSuccessful) {
response.body()?.let {
Either.Right(it)
} ?: Either.Left(Failure.UnknownError("Response body is null"))
} else {
Either.Left(Failure.ServerError)
}
} catch (e: IOException) {
Either.Left(Failure.NetworkError)
} catch (e: Exception) {
Either.Left(Failure.UnknownError(e.localizedMessage ?: "Unknown error"))
}
}}
With this approach, I feel like the repository is tightly coupled to the data source. If the data source changes and returns a different DTO, the repository would also need to be modified.
Other option would be mapping on data source, however with that approach data source will have a lot of to do, call api and mapping to domain(Data source dont know)
Third option would be mapping to repository then in repository map to domain. With that approach we could make some of separation however we must have two mappers.
Last option is a interface that DTo will extends with parameters and a mapper to domain.
Please let me know which option you think is the best
Share Improve this question edited Feb 6 at 11:08 Pratik Fagadiya 1,47212 silver badges26 bronze badges asked Feb 6 at 10:15 user1866731user1866731 4992 gold badges6 silver badges13 bronze badges2 Answers
Reset to default 2The best approach is to perform the mapping in the repository layer. This aligns with Clean Architecture principles and ensures proper separation of concerns.
Why Mapping Should Happen in the Repository
1. Separation of Concerns:
Data Source: Responsible for fetching raw data (e.g., API calls, database queries) and returning DTOs.
Repository: Orchestrates data flow, handles business logic, and maps DTOs to domain models.
Domain Layer: Contains pure business logic and domain models, independent of data sources.
2. Flexibility:
If you change the data source (e.g., switch from API to local database), only the repository needs to adapt the mapping logic. The domain layer remains unchanged.
3. Testability:
Repositories can be easily tested with mocked data sources and mappers.
Data sources can be tested independently without worrying about domain models.
4. Single Responsibility:
Data sources focus on I/O operations.
Repositories focus on coordinating data flow and mapping.
In my opinion, you should perform the mapping in the repository. The repository must be responsible for providing a fully created domain object, ensuring that the domain layer works with clean and structured data.
The way I understand Clean Architecture:
In Clean Architecture you would have your application-specific logic in another layer (application). You define Gateway interfaces in application and you implement those interfaces in your data layer.
In your case, you are usind repositories in your domain. That is closer to the structure and wording used in ddd.
Still, I would define a repository interface MyRepository
and have different implementations, one for each data source.
MyRemoteApiRepository
, MyInMemoryRepository
, MySqliteRepository
.
Each implementation uses the DTO that best fits its respective data source technology, with the mapping to and from the domain model handled within the repository implementation.
This way the domain stays agnostic to the technology used in the implementation and you change the repository implementation when your data source changes. That can even be done at runtime.