Представь: ты пишешь микросервис на Ktor, всё летает на корутинах, код минималистичный и всё прекрасно... До того момента, когда ты касаешься базы данных. Все мечты разбиваются об Spring Data и Koin, либо об тонны бойлерплейта на Exposed или Ktorm DSL. Знакомо?
У меня есть решение: KRepo — это лёгкий, полностью suspend-ориентированный слой репозиториев, который даёт вам магию Spring Data (интерфейсы, findBy…, CRUD из коробки), но без DI-контейнеров, без рефлексии в рантайме и тонн мусора.
interface UserRepository : Repository<User, Long> {
suspend fun findByEmail(email: String): User?
suspend fun findByAgeGreaterThan(age: Int): List<User>
}
— и всё. Никаких имплементаций или инъекций. Под капотом — динамический прокси, кэширование метаданных и независимость от фреймворков: плагины под Exposed, JDBC или MongoDB реализуются отдельно.
Как и какие пробелы закрывает моё детище
-
Blocking I/O и отсутствие suspend-поддержки из коробки:
Exposed/JDBC — синхронные по умолчанию, нужно вручную оборачивать в
withContext(Dispatchers.IO),что приводит к контекст-свитчингу и снижению производительности в Ktor. Spring Data JPA — чисто blocking.У KRepo 100% приостанавливаемая реализация CRUD и кастомные методы через динамические прокси. Все операции —
suspend fun, интегрируются напрямую в корутины Ktor без обёрток.Это идеально подходит для async-роутов Ktor: нет "пробок", выше пропускная способность под нагрузкой. Закрывает потребность в reactive Spring (R2DBC) и/или бойлерплейта.
-
Тяжёлый DI и автоконфигурация:
Spring Data требует Spring Boot/Koin для инъекции репозиториев — overhead для легковесного Ktor. В Exposed — ручная регистрация в DI, без "магии".
В KRepo RepositoryContext — простой registry. Нет Spring/Koin. Репозитории генерируются на лету через фабрики (
RepositoryFactory).Легче тестировать (мокаешь DataAccessor) и деплойить (меньше зависимостей).
-
Boilerplate-код для репозиториев:
В Exposed/Ktorm — пишешь SQL/DSL вручную для каждого метода. Нет динамического парсинга имён методов (типа
findByName).Proxy-based генерация без ручной имплементации. Определяешь интерфейс, прокси сам реализует через
DataAccessor.Экономит часы на CRUD: пишешь интерфейс — и готово.
-
Жёсткая привязка к одному ORM/БД:
Абстрактный интерфейс для любого бэкенда (Exposed, MongoDB, JDBC, in-memory). Модули реализации для Exposed, чистого SQL, MongoDB можно создавать самому.
-
Runtime-overhead из-за рефлексии:
Метаданные (методы, entity-структура) собираются один раз на compile-time/reflection-at-startup и кэшируются.
-
Отсутствие "Spring-like" удобства в Ktor:
Spring Data-подобный API, но Ktor-native. Интерфейсы с suspend-методами, автоматическая генерация. Интеграция в Ktor: просто регистрируешь в контексте и используешь в роутах.
-
Сложности с кастомными запросами и пагинацией:
Поддержка кастомных suspend-методов через прокси.
findByAgeGreaterThan→ автоматический запрос. Легче масштабировать на сложные запросы без потери асинхронности.
Состояние проекта и ссылки
На данный момент полностью реализован core-модуль со всей необходимой абстракцией (методы, парсеры, прокси, аннотации и т.д). Осталась только реализация паттерна для конкретных БД и драйверов: SQL-диалекты, Mongo и in-memory.
Структура сейчас:
./core/src/main/kotlin
├── exception
│ ├── QueryParseException.kt
│ └── RepositoryException.kt
├── query
│ ├── LogicalOperator.kt
│ ├── MethodNameParser.kt
│ ├── Operator.kt
│ ├── ParsedMethod.kt
│ ├── QueryAction.kt
│ ├── QueryBuilder.kt
│ └── QueryCondition.kt
├── repository
│ ├── access
│ │ └── DataAccessor.kt
│ ├── annotations
│ │ ├── Column.kt
│ │ ├── Entity.kt
│ │ ├── Id.kt
│ │ └── Transient.kt
│ ├── CrudRepositoryDelegate.kt
│ ├── CrudRepository.kt
│ ├── DefaultRepositoryFactory.kt
│ ├── KtorRepository.kt
│ ├── metadata
│ │ ├── ColumnProperty.kt
│ │ ├── EntityColumn.kt
│ │ ├── EntityMetadata.kt
│ │ └── PropertyExtensions.kt
│ ├── RepositoryConfig.kt
│ ├── RepositoryContext.kt
│ ├── RepositoryFactory.kt
│ ├── RepositoryInvocationHandler.kt
│ ├── RepositoryMetadata.kt
│ └── RepositoryMethod.kt
└── utils
└── ReflectionUtils.kt
Ссылки и контакты:
На этом всё, ждите новостей!