Функциональное моделирование предметной области с базовыми типами Arrow
Моделируйте сложные предметные области с помощью Either, NonEmptyList и накопления проверенных ошибок
«Функциональное моделирование предметной области с базовыми типами Arrow» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.
Что такое функциональное моделирование предметной области
Функциональное моделирование предметной области использует алгебраические типы (Either, Option, запечатанные классы), чтобы закодировать бизнес-правила в типах. Некорректные состояния становятся непредставимыми во время компиляции, а ошибки явно указываются в сигнатурах функций — без скрытых исключений.
Объекты-значения с inline-классами и value-классами
Используйте value-классы Kotlin, чтобы оборачивать примитивные типы и предотвращать одержимость примитивами. Система типов не позволит передать UserId туда, где ожидается PostId:
@JvmInline value class UserId(val value: Long)
@JvmInline value class PostId(val value: Long)
fun findUser(id: UserId): Either<UserError, User> = TODO()
// findUser(PostId(1L)) // Compile error!Запечатанные классы для ошибок предметной области
Моделируйте ошибки с помощью иерархий запечатанных классов. Каждый подкласс содержит только данные, относящиеся к соответствующему случаю ошибки:
sealed class UserError {
data class NotFound(val id: UserId) : UserError()
data class EmailTaken(val email: String) : UserError()
data class ValidationFailed(val field: String, val reason: String) : UserError()
}Validated: накопление нескольких ошибок
Если нужно сообщить обо всех ошибках проверки сразу, а не только о первой, используйте Validated из Arrow (или zipOrAccumulate в Raise DSL). Ошибки накапливаются в NonEmptyList:
import arrow.core.Validated
import arrow.core.valid
import arrow.core.invalid
fun validateName(name: String): Validated<String, String> =
if (name.isNotBlank()) name.valid() else "Name is blank".invalid()Составление проверок
Используйте zip(), чтобы объединить несколько результатов Validated. Если хотя бы один из них имеет значение Invalid, накапливаются все ошибки:
val result: Validated<NonEmptyList<String>, User> =
validateName(name).zip(validateEmail(email)) { n, e -> User(n, e) }
.mapLeft { it }Представление недопустимых состояний невозможным
Вместо User с nullable-полем email и флагом isVerified используйте запечатанные подклассы, чтобы система типов не позволяла обращаться к адресу электронной почты непроверенного пользователя:
sealed class User {
data class Unverified(val id: UserId, val pendingEmail: String) : User()
data class Verified(val id: UserId, val email: String) : User()
}Умные конструкторы
Используйте фабричные функции companion object, возвращающие Either или Option, вместо открытых конструкторов. Это гарантирует, что экземпляры всегда находятся в корректном состоянии:
class Email private constructor(val value: String) {
companion object {
fun of(raw: String): Either<String, Email> =
if (raw.contains("@") && raw.length <= 255) Email(raw).right()
else "Invalid email format".left()
}
}Сочетание Either и Option в конвейере
Реальная операция предметной области обычно объединяет поиск в репозиториях (Option) с проверками (Either). Используйте блок Arrow either { } с .bind(), чтобы связно объединить эти операции:
fun createPost(authorId: UserId, title: String, body: String): Either<PostError, Post> = either {
val author = userRepo.findById(authorId).toEither { PostError.AuthorNotFound }.bind()
val validTitle = validateTitle(title).bind()
postRepo.save(Post(author, validTitle, body))
}Неизменяемость и Copy
Сущности предметной области должны быть неизменяемыми. Используйте классы данных и copy(), чтобы получать новые состояния. Lens из Arrow (из arrow-optics) позволяет удобно обновлять вложенные значения без изменения исходных данных:
val updated = user.copy(email = "new@example.com")
// Arrow Optics:
val emailLens = User.email
val updatedWithLens = emailLens.set(user, "new@example.com")Типобезопасная конфигурация с запечатанными классами
Моделируйте варианты конфигурации приложения с помощью запечатанных классов, чтобы компилятор требовал обработать каждый случай:
sealed class DbConfig {
data class Postgres(val url: String, val user: String, val pass: String) : DbConfig()
data class InMemory(val dbName: String = "test") : DbConfig()
}Преимущества с первого взгляда
Функциональное моделирование предметной области с базовыми типами Arrow обеспечивает:
- Отсутствие исключений из-за нулевого указателя в коде предметной области
- Видимые в сигнатурах функций ошибки
- Проверки на полноту с помощью
whenдля иерархий запечатанных классов - Тестируемые чистые функции
- Самодокументируемый код
Быстрая проверка
Какое главное преимущество даёт иерархия sealed class для ошибок предметной области по сравнению с выбрасыванием исключений?
Итоги: функциональное моделирование предметной области с базовыми типами Arrow
Главное:
- Value-классы предотвращают одержимость примитивами
- Иерархии запечатанных ошибок обеспечивают полное и типобезопасное описание случаев сбоя
- Умные конструкторы гарантируют корректность экземпляров
either { }.bind()аккуратно объединяет операции, которые могут завершиться ошибкой- Неизменяемые данные +
copy()/ Arrow Optics обеспечивают безопасные переходы между состояниями
Часто задаваемые вопросы
Урок «Функциональное моделирование предметной области с базовыми типами Arrow» бесплатный?
Да — полный текст урока «Функциональное моделирование предметной области с базовыми типами Arrow» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.
Чему я научусь в уроке «Функциональное моделирование предметной области с базовыми типами Arrow»?
Моделируйте сложные предметные области с помощью Either, NonEmptyList и накопления проверенных ошибок Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Kotlin Academy?
Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Функциональное моделирование предметной области с базовыми типами Arrow»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Kotlin Academy?
Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Either : типизированная обработка ошибок без исключений
- DSL Arrow Raise: объединение типизированных ошибок
- Option и Nullable: когда использовать каждый из них
- Функциональное моделирование предметной области с базовыми типами Arrow