Васильев Андрей Михайлович, 2022
Версии презентации
flowchart TB user("Пользователь") view["Слой представления"] business["Слой предметной области"] storage["Слой хранения данных"] fs[("Файловая система")] db[("Базы данных")] dataSources(["Источники данных"]) user --> view view --> business business --> storage storage --> fs storage --> db storage --> dataSources
Для работы с хранилищами необходимо изучить специальные API или языки программирования, например язык SQL
В рамках курса будем использовать собственное хранилище данных
Минусы такого подхода:
Данный подход к хранению данных можно применять для непрофессиональных или небольших приложений
flowchart TB fs("Файловая система") store("Хранилище") repoOne("Репозиторий бабочек") butterfly("Бабочка") repoTwo("Репозиторий коней") horse("Конь") queryOneBlue("Запрос на голубые бабочки") queryOneBeautiful("Запрос на 5 красивых") queryTwo("Запрос на красных коней") handlerMain("HTTP-обработчик стартовой страницы") handlerButterfly("HTTP-обработчик бабочек") handlerMain --> queryOneBeautiful handlerMain --> queryTwo handlerButterfly --> queryOneBlue queryOneBlue --> repoOne queryOneBeautiful --> repoOne queryTwo --> repoTwo repoOne --> store repoOne --> butterfly repoTwo --> store repoTwo --> horse store --> fs
Рассмотрим ключевые особенности каждого элемента архитектуры
В обычных веб-приложениях реализуется внешним приложением (СУБД) или классами специализированной библиотеки
В обычных веб-приложениях использует подключение к СУБД для доступа к данным
При перезапуске веб-приложения данные должны сохраняться в надёжном хранилище. При использовании ручного хранения таким хранилищем выступает файловая система
Сериализация — процесс преобразования структуры данных в последовательность байтов. Формат хранения может быть как отрытым, так и закрытым
Существует множество отрытых форматов хранения данных: XML, JSON, CSV, BSON, YAML, TOML и т.д.
Простой формат обмена данными, удобный для чтения и написания как человеком, так и компьютером
Формат поддерживает следующие типы данных:
Для объединения этих элементов используется:
Описание формата доступно на официальном сайте https://www.json.org/json-ru.html
1[
2 {
3 "title": "Война и мир",
4 "author": "Лев Толстой",
5 "year": 1869
6 },
7 {
8 "title": "Бесы",
9 "author": "Федор Достоевский",
10 "year": 1872
11 },
12 {
13 "title": "Чайка",
14 "author": "Антон Чехов",
15 "year": 1896
16 }
17]
[
и ]
на 1 и 17 строках соответственно{
и }
В рамках экосистемы JVM существует множество библиотек, позволяющих обрабатывать JSON-документы
Библиотека http4k предоставляет единый интерфейс для работы с JSON-документами
В рамках данной лекции рассмотрим использование библиотеки Jackson, которая является одной из лучших в своём классе
В рамках библиотеки http4k разработан ряд методов, которые позволяют удобно взаимодействовать с данными документами. Методы описаны в пакете http4k / org.http4k.format / Json
Для описания JSON-объекта можно воспользоваться следующим кодом:
val objectUsingExtensionFunctions: JsonNode =
listOf(
"thisIsAString" to "stringValue".asJsonValue(),
"thisIsANumber" to 12345.asJsonValue(),
"thisIsAList" to listOf(true.asJsonValue()).asJsonArray()
).asJsonObject()
Для преобразования объекта в строку можно воспользоваться методами asCompactJsonString
и asPrettyJsonString
. Первый позволяет сохранить данные в наиболее компактной форме, а последний — в форме удобной для человека
Метод parse позволяет выполнить обратное преобразование из строки в JSON-объект
Существует несколько стратегий для реализации сериализации:
Хранилище должно реализовывать надёжное сохранение и восстановление состояния
Будем выполнять сохранение состояния при завершении работы приложения
JVM-процесс может завершить свою работу в следующих случаях:
При завершении работы могут быть запущены потоки-обработчики данной ситуации
Для регистрации потока используется метод Platform.addShutdownHook(Thread thread)
Для описания потока в Kotlin можно воспользоваться thread
val hook = thread(start = false) {
println("Работаю во время выключения")
}
Runtime.getRuntime().addShutdownHook(hook)
interface Repository<T> {
fun fetch(id: UUID): T
fun list(): Iterable<T>
fun query(queryParams: QueryParams): Iterable<T>
fun add(entity: T): UUID
fun delete(entity: T)
fun update(entity: T)
}
Репозиторий не должен реализовывать все перечисленные методы, если они не нужны для работы приложения
Каждый конкретный объект в приложении должен быть точно идентифицирован:
Для решения данной задачи порядковый номер в массиве, индекс, не подходит. Вместо этого необходимо каждый объект внутри системы расширить идентификатором
Удобным способом для формирования идентификаторов является использование UUID
Хранилище должно являться единственным источником правдивых данных для всего приложения. Для этого хранилище должно выполнять функции по контролю над данными, которыми оно оперирует. Наиболее эффективный способ достижения этой цели — сделать данные неизменяемыми
data-классы в Kotlin будут неизменяемыми, если:
val
В рамках JVM-экосистемы большинство примитивных типов являются неизменяемыми: числа, строки, классы даты и времени (LocalDate, LocalTime и т.д.)
Обычные хранилища, базы данных, хранят данные вне процесса веб-приложения, поэтому с точки зрения приложения они являются неизменяемыми
data-классы в Kotlin предоставляют возможность создания нового объекта с на основе существующего с помощью функции копирования copy
data class BankNote(val currency: String, val denomination: Int)
val baseNote = BankNote("RUB", 500)
val newNote = baseNote.copy(currency="CHY")
Функция copy
создаёт новый объект с изменёнными свойствами, заменяются только поля, указанные в рамках аргумента функции copy
Новый объект, с новыми свойствами, можно использовать для сохранения нового объекта в хранилище или изменении базового объекта в хранилище
null
-состояний
#
Для описания специальных состояний можно прибегнуть к null
-значению, однако данный подход несёт ряд проблем:
null
-значения ведут себя не как обычные объекты данного классаВместо null
-значений рекомендуется описывать возможные состояния с помощью: