Как делал маппинг данных между слоями архитектуры
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Маппинг данных между слоями в Android-архитектуре
Маппинг данных — это процесс преобразования объектов данных при их передаче между слоями архитектуры (например, из сетевого слоя в слой домена, а затем в UI-слой). Я придерживаюсь принципа разделения ответственности: каждый слой работает со своими собственными моделями данных, что обеспечивает гибкость, тестируемость и соблюдение принципа единой ответственности.
Основные типы моделей данных
В типичной чистой архитектуре (Clean Architecture) или MVVM я использую следующие модели:
- Data Model (DTO/Entity) — модели для работы с сетью и базой данных.
- Domain Model — бизнес-объекты, независимые от фреймворков.
- UI Model (ViewState/ViewEntity) — данные, оптимизированные для отображения.
Пример структур:
// Data Layer
data class UserDto(
@SerializedName("id")
val id: Long,
@SerializedName("full_name")
val fullName: String,
@SerializedName("email_address")
val email: String
)
// Domain Layer
data class User(
val id: Long,
val name: String,
val email: String
)
// UI Layer
data class UserUi(
val id: String,
val displayName: String,
val email: String,
val avatarColor: Int
)
Способы реализации маппинга
Я использую несколько подходов в зависимости от сложности проекта:
1. Ручной маппинг в мапперах
Создаю отдельные классы-мапперы с статическими методами или функциями расширения:
class UserMapper {
fun toDomain(dto: UserDto): User {
return User(
id = dto.id,
name = dto.fullName,
email = dto.email
)
}
fun toUi(domain: User): UserUi {
return UserUi(
id = domain.id.toString(),
displayName = formatName(domain.name),
email = domain.email,
avatarColor = generateColor(domain.id)
)
}
private fun formatName(name: String): String {
return name.trim().split(" ").joinToString(" ") { it.capitalize() }
}
}
2. Использование функций расширения
Более идиоматичный Kotlin-подход:
fun UserDto.toDomain(): User = User(
id = this.id,
name = this.fullName,
email = this.email
)
fun User.toUi(): UserUi = UserUi(
id = this.id.toString(),
displayName = this.name.toTitleCase(),
email = this.email,
avatarColor = ColorGenerator.generate(this.id)
)
3. Библиотеки для маппинга
Для сложных проектов с большим количеством моделей использую MapStruct (через kapt) или Kotlinx.serialization с кастомными сериализаторами:
// Пример с MapStruct
@Mapper
interface UserMapper {
@Mapping(source = "fullName", target = "name")
fun toDomain(dto: UserDto): User
@Mapping(source = "id", target = "displayId")
@Mapping(target = "avatarColor", ignore = true)
fun toUi(domain: User): UserUi
}
Лучшие практики, которые я применяю
- Иммутабельность: все модели данных —
data classс неизменяемыми свойствами (val) - Null-safety: явно обрабатываю nullable-поля на этапе маппинга
- Валидация: проверяю и валидирую данные при преобразовании
- Ленивые вычисления: для сложных преобразований UI-моделей использую
by lazy - Тестирование: обязательно пишу unit-тесты для мапперов
@Test
fun `user dto to domain mapping correct`() {
val dto = UserDto(1, "john doe", "john@test.com")
val domain = userMapper.toDomain(dto)
assertEquals(1L, domain.id)
assertEquals("john doe", domain.name)
assertEquals("john@test.com", domain.email)
}
Где происходит маппинг
- Репозитории — преобразуют DTO/Entity в Domain-модели
- UseCases/Interactors — работают только с Domain-моделями
- ViewModel/Presenter — преобразуют Domain в UI-модели
- Адаптеры, Composable функции — получают готовые UI-модели
Преимущества такого подхода
- Изоляция изменений: при изменении API меняется только DTO и маппер
- Тестируемость: domain-слой не зависит от деталей реализации
- Читаемость: каждый слой имеет понятные, специализированные модели
- Безопасность: валидация данных происходит в контролируемых точках
Правильный маппинг данных — это фундамент поддержуемой архитектуры, который окупается при первых же изменениях требований или API.