Рубрики
Без рубрики

Ваш собственный пользовательский репозиторий данных Spring

Фреймворки обещают ускорить темпы развития человека при условии, что он следует по основному пути. Буква “р”… С тегами java, весенние данные, весенняя загрузка, настройка.

Фреймворки обещают ускорить темпы развития человека при условии, что он следует по основному пути. Путь может быть более или менее узким. Я большой поклонник экосистемы Spring, потому что ее дизайн расширяем и настраиваем на разных уровнях абстракции: таким образом, путь настолько велик, насколько вам нужно.

Функциональное программирование становится все более популярным. Spring предоставляет пару DSL для языка Kotlin. Например, DSL Beans и DSL Маршрутов обеспечивают более функциональный подход к конфигурации Spring. На стороне типа , Vavr (ранее Javaslang) довольно популярен в Java, в то время как в Kotlin есть Стрелка .

В этом посте я хотел бы описать, как можно использовать систему типов Arrow с данными Spring. В конечном счете, вы можете воспользоваться объяснениями для создания собственного хранилища данных Spring.

Исходная архитектура

Начальная архитектура для моего приложения довольно стандартная:

  1. Контроллер ОТДЫХА с двумя GET сопоставлениями
  2. Интерфейс хранилища JDBC данных Spring Data

Поскольку это стандартно, Spring обрабатывает большую часть сантехники, и нам не нужно писать много кода. С Kotlin это еще более лаконично:

class Person(@Id val id: Long, var name: String, var birthdate: LocalDate?)

interface PersonRepository : CrudRepository

@RestController
class PersonController(private val repository: PersonRepository) {

    @GetMapping
    fun getAll(): Iterable = repository.findAll()

    @GetMapping("/{id}")
    fun getOne(@PathVariable id: Long) = repository.findById(id)
}

@SpringBootApplication
class SpringDataArrowApplication

fun main(args: Array) {
    runApplication(*args)
}

К более функциональному подходу

Этот шаг не имеет ничего общего с данными Spring и не требуется, но он лучше соответствует функциональному подходу. Как упоминалось выше, мы можем извлечь выгоду из использования DSL маршрутов и компонентов. Давайте проведем рефакторинг приведенного выше кода, чтобы максимально удалить аннотации.

class PersonHandler(private val repository: PersonRepository) {                   // 1

  fun getAll(req: ServerRequest) = ServerResponse.ok().body(repository.findAll()) // 2
  fun getOne(req: ServerRequest): ServerResponse = repository
    .findById(req.pathVariable("id").toLong())
    .map { ServerResponse.ok().body(it) }
    .orElse(ServerResponse.notFound().build())                                    // 3
}

fun beans() = beans {                                                             // 4
  bean()
  bean {
    val handler = ref()                                           // 5
    router {
      GET("/", handler::getAll)
      GET("/{id}", handler::getOne)
    }
  }
}

fun main(args: Array) {
  runApplication(*args) {
    addInitializers(beans())                                                      // 6
  }
}
  1. Создайте класс обработчика для организации функций маршрутизации
  2. Все функции маршрутизации должны принимать Сервер запрашивает параметр и возвращает Ответ сервера
  3. Добавьте дополнительную возможность: если объект не найден, верните 404
  4. Используйте DSL маршрутов для сопоставления HTTP-глаголов и пути к функциям маршрутизации
  5. ref() извлекает компонент с настроенным типом из контекста приложения Spring
  6. Явный вызов функции beans() , больше никакой магии!

Вводящая стрелка

Функциональный компаньон стандартной библиотеки Kotlin

Стрела

Стрелка поставляется с четырьмя различными компонентами:

  1. Ядро
  2. FX: Фреймворк функциональных эффектов, дополняющий сопрограммы KotlinX
  3. Оптика: Глубокий доступ и преобразования к неизменяемым данным
  4. Мета: Библиотека метапрограммирования для компилятора Kotlin плагины

Основная библиотека предлагает Либо введите. Стрелка советует использовать Либо<Единица измерения,T> для моделирования необязательного значения. С другой стороны, Spring Data JDBC findById() возвращает java.util. Необязательно .

Преодоление разрыва

Как нам преодолеть разрыв между Необязательным и Либо ?

Вот первая попытка:

repository
    .findById(req.pathVariable("id").toLong())      // 1
    .map { Either.fromNullable(it) }                // 2
    .map { either ->
        either.fold(
            { ServerResponse.notFound().build() },  // 3
            { ServerResponse.ok().body(it) }        // 3
        )
    }.get()                                         // 4
  1. Необязательно<Человек>
  2. Необязательно<Либо<Единица измерения, Человек>>
  3. Необязательно<Ответ сервера>
  4. На этом этапе мы можем безопасно вызвать get() , чтобы получить Ответ сервера

Я считаю, что использование Необязательно<Либо<Единица измерения, человек>> не очень хорошо. Однако Kotlin может помочь нам в этом отношении с помощью функций расширения:

private fun  Optional.toEither() =
    if (isPresent) Either.right(get())
    else Unit.left()

С помощью этой функции мы можем улучшить существующий код:

repository
    .findById(req.pathVariable("id").toLong())    // 1
    .toEither()                                   // 2
    .fold(
        { ServerResponse.notFound().build() },    // 3
        { ServerResponse.ok().body(it) }          // 3
    )
  1. Необязательно<Человек>
  2. Либо<Единица Измерения, Человек>
  3. Ответ сервера

Так выглядит приятнее, но было бы намного лучше, если бы репозиторий возвращал Либо<Единица измерения, Человек> напрямую.

Настройка весенних данных

Давайте проверим, как мы можем настроить данные Spring для достижения этой цели.

По умолчанию хранилище данных Spring предлагает все общие функции, которые вы можете ожидать, .например. :

Я считаю, что один приходит к Spring Data для простоты использования, но этот остается из-за его возможностей расширения.

На базовом уровне можно добавлять функции, которые следуют определенному шаблону именования, например , Порядок Поиска первого имени И последнего Имени По Фамилии() . Spring Data сгенерирует код реализации без необходимости написания одной строки SQL. Когда вы достигнете пределов этого подхода, вы можете аннотировать функцию с помощью SQL, который вы хотите запустить.

В обоих случаях вам необходимо задать тип возвращаемого значения. Хотя количество возможных типов возврата довольно велико, оно все еще ограничено. Фреймворк не может учитывать все возможные типы, и, в частности, список не содержит Либо .

Следующий уровень расширяемости заключается в добавлении любой функции с желаемой подписью с помощью пользовательской реализации. Для этого нам нужно:

  • Интерфейс, который объявляет требуемую функцию
  • Класс, реализующий интерфейс
interface CustomPersonRepository {                                         // 1
    fun arrowFindById(id: Long): Either                      // 2
}

class CustomPersonRepositoryImpl(private val ops: JdbcAggregateOperations) // 3
    : CustomPersonRepository {                                             // 4

    override fun arrowFindById(id: Long) =
        Either.fromNullable(ops.findById(id, Person::class.java))           // 5
}

interface PersonRepository
    : CrudRepository, CustomPersonRepository                 // 6
  1. Новый пользовательский интерфейс
  2. Объявить требуемую функцию
  3. Новый класс реализации…
  4. … который реализует родительский интерфейс
  5. Реализовать функцию
  6. Просто расширьте пользовательский интерфейс

Теперь мы можем позвонить:

repository.arrowFindById(req.pathVariable("id").toLong())
    .fold(
        { ServerResponse.notFound().build() },
        { ServerResponse.ok().body(it) }
    )

Этот подход работает, но имеет один существенный недостаток. Чтобы избежать столкновения в сигнатуре функций, мы должны придумать оригинальное имя для нашей функции, которая возвращает Либо т.е. стрелка findById() .

Изменение базового репозитория по умолчанию

Чтобы преодолеть это ограничение, мы можем использовать еще один пункт расширения: изменить базовый репозиторий по умолчанию.

Приложения Spring Data определяют интерфейсы, но реализация должна откуда-то исходить. Фреймворк предоставляет его по умолчанию, но его можно переключить с помощью нашего собственного.

Вот обзор диаграммы классов:

Подробный процесс довольно сложен: важной частью является Простой репозиторий Jdbc класс. Spring Data найдет класс через компонент Jdbc RepositoryFactoryBean , создаст его новый экземпляр и зарегистрирует экземпляр в контексте.

Давайте создадим базовый репозиторий, который использует Либо :

@NoRepositoryBean
interface ArrowRepository : Repository {         // 1
    fun findById(id: Long): Either                    // 2
    fun findAll(): Iterable                                 // 3
}

class SimpleArrowRepository(                            // 4
    private val ops: JdbcAggregateOperations,
    private val entity: PersistentEntity
) : ArrowRepository {

    override fun findById(id: Long) = Either.fromNullable(
        ops.findById(id, entity.type)                          // 5
    )

    override fun findAll(): Iterable = ops.findAll(entity.type)
}
  1. Наш новый репозиторий интерфейсов…
  2. …с подписью, которую мы выбираем, без какого-либо риска столкновения.
  3. Мне было слишком лень все реализовывать.
  4. Базовая реализация интерфейса репозитория. Конструктор должен принять эти два параметра.
  5. Не изобретайте велосипед заново; используйте существующий экземпляр Jdbc Aggregate Operations .

Нам нужно аннотировать основной класс приложения с помощью @EnableJdbcRepositories и настроить последний для переключения на этот базовый класс.

@SpringBootApplication
@EnableJdbcRepositories(repositoryBaseClass = SimpleArrowRepository::class)
class SpringDataArrowApplication

Чтобы упростить использование клиентского кода, мы можем создать аннотацию, которая переопределяет значение по умолчанию:

@EnableJdbcRepositories(repositoryBaseClass = SimpleArrowRepository::class)
annotation class EnableArrowRepositories

Теперь использование простое:

@SpringBootApplication
@EnableArrowRepositories
class SpringDataArrowApplication

На этом этапе мы можем переместить код репозитория Arrow в его проект и распространить его для использования другими “клиентскими” проектами. Дальнейшее расширение не требуется, хотя Spring Data предлагает гораздо больше, например, , переключение заводского компонента.

Вывод

Spring Data предоставляет готовую к использованию реализацию репозитория “из коробки”. Когда этого недостаточно, его гибкий дизайн позволяет расширять код на разных уровнях абстракции.

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

Спасибо Марку Палучу за его отзыв.

Полный исходный код этого поста можно найти на Github в формате Maven.

Идти дальше:

Первоначально опубликовано на Фанат Java 11 апреля th , 2021

Оригинал: “https://dev.to/nfrankel/your-own-custom-spring-data-repository-140g”