Ближе к концу 2017 года некоторые разработчики iZettle собрались вместе и начали обсуждать возможность корпоративной разработки с Kotlin. Моя команда запускала новый проект, и все мы думали, что это будет хорошая возможность попробовать свои силы в Kotlin.

Наши системы эффективно используют JDBI и Dropwizard, и мы не испытывали особого желания слишком сильно отклоняться от этого шаблона. Вы придерживаетесь того, что знаете, особенно когда начинаете изучать новый язык. Мы пошли осторожно.

Приложение Dropwizard в iZettle в основном состоит из трех частей: класса приложения, класса конфигурации и всего остального. Класс приложения быстро раздувается строкой за строкой создания экземпляров, переплетается с новыми экземплярами менеджеров и ресурсов. Каждый экземпляр тщательно объявляется за другим, создавая точный каскад экземпляров, один из которых вставляется в другой. Среди этих экземпляров были наши объекты JDBI, наши DAO (объект доступа к данным). А их было много.

Возможно, вы уже узнаете проблему и даже имеете готовое решение. Конечно, мы могли бы представить Guice, облегченный фреймворк Google для внедрения зависимостей, вместе с его мешаниной адаптеров для Dropwizard, и попытаться заставить все это работать с Kotlin. Нам это не понравилось. Вместо этого мы решили не усложнять задачу и перейти на Kotlin.

Kotlin вводит несколько новых концепций и связанных с ними ключевых слов, на осознание которых нужно время. Когда мы обнаружили inline и reified, мы были близки к решению.

Ключевое слово inline указывает компилятору Kotlin буквально скопировать ваш код в то место, откуда он вызывается. Это позволяет вам оптимизировать собственный код до того, как это сделает компилятор, немного подстроив его под вашу волю. Вместе с inline мы обнаружили reified, который позволяет нам получить доступ к фактическому типу параметра универсального типа. Чтобы что-то было reified, оно также должно быть inline.

Мы создали новый класс для проксирования всех взаимодействий с JDBI. Мы назвали это DataOperations. При запуске класс сканирует любые DAO (определяемые тем, что они находятся в конкретном пакете и наследуют интерфейс Dao). Для этого мы использовали библиотеку Reflections, но поскольку она еще явно не поддерживает Kotlin, мы сохраняем объекты Java Class, которые она создает.

companion object {
    val daoTypes: List<Class<out Dao>> =
        Reflections("com.izettle.db", MyApp::class.java.classLoader)
            .getSubTypesOf(Dao::class.java).toList()
}

Теперь, когда у нас есть список обнаруженных классов DAO, мы можем использовать JDBI для их создания всякий раз, когда создается экземпляр DataOperations, передавая объект Jdbi. Это делается только один раз из основного класса приложения Dropwizard.

typealias DaoMap = Map<KClass<out Dao>, Dao>
val daos: DaoMap = 
    daoTypes.map { it.kotlin to jdbi.onDemand(it) }.toMap()

Затем, используя inline и reified, мы создали способ для других классов доступа к выбранному ими DAO. Мы назвали это use().

inline fun <reified T : Dao> use() = daos[T::class] as T

Это позволило нам использовать единственный экземпляр DataOperations, объявленный один раз в классе приложения и совместно используемый со всеми другими ресурсами, менеджерами и т. Д., Которым необходим доступ к базе данных. Благодаря Kotlin и reified эти другие классы могут получать доступ к DAO либо путем непосредственного указания желаемого типа, либо путем разрешения Kotlin делать вывод о типе.

// specify the desired Dao type
val orderDao = dataOperations.use<OrderDao>()
// infer the Dao type
fun publish(orderDao: OrderDao) = doSomething(orderDao)
publish(
    dataOperations.use()
)

Мы продолжали использовать этот шаблон для добавления поддержки транзакций в DataOperations. Для тестирования мы добавили класс MockingDataOperations, который создает имитацию каждого DAO и содержит утилиты для сброса и программирования имоков.

Переход на использование DataOperations сократил шаблонный код и упростил логику доступа к базе данных во всем приложении, сведя знания о JDBI в один класс.

Это только первая остановка в пути, который мы начали, когда решили принять Котлин. Мы уже с нетерпением ждем следующей остановки - следующего ага-момента.