Это третья из серии статей, в которой рассматривается система типов для Inversion of Coupling Control для обеспечения композиции.
Предыдущие статьи охватывали:
В этой статье мы рассмотрим применение теории на практике. Он будет использовать концепции для создания приложения, сочетающего эффекты из различных библиотек эффектов.
Обратите внимание, что используемые эффекты намеренно просты, чтобы сосредоточиться на композиции эффектов. Это главным образом потому, что эта статья не для сравнения библиотек. Эта статья предназначена для их составления. Мы покажем, как с помощью Inversion of Coupling Control их можно легко объединить в одно простое приложение. Кроме того, порядок обсуждения библиотек не более чем алфавитный.
Для простоты, эффект будет получать сообщение из базы данных.
Вам также могут понравиться:
Эффект ZIO и Cats: реактивные потоки для функциональной Scala
коты
Давайте начнем с эффекта кошек .
Scala
xxxxxxxxxx
1
def cats(request: ServerRequest)(implicit repository: MessageRepository): IO[ServerResponse] =
2
for {
3
message <- catsGetMessage(request.getId)
4
response = new ServerResponse(s"${message.getContent} via Cats")
5
} yield response
6
def catsGetMessage(id: Int)(implicit repository: MessageRepository): IO[Message] =
8
IO.apply(repository findById id orElseThrow)
catsGetMessage
Функция оборачивает хранилище эффекта при получении сообщений внутри IO
. Затем его можно использовать для обслуживания запроса на предоставление ответа (согласно cats
функции).
Использование implicit
может быть излишним для зависимости одного репозитория. Тем не менее, он показывает, как внедрение зависимостей может устранить беспорядок зависимости от логики обслуживания. Это особенно полезно, когда число зависимостей растет.
Реактор
Реактор имеет следующую логику обслуживания.
Scala
xxxxxxxxxx
1
def reactor(request: ServerRequest)(implicit repository: MessageRepository): Mono[ServerResponse] =
2
reactorGetMessage(request.getId).map(message => new ServerResponse(s"${message.getContent} via Reactor"))
3
def reactorGetMessage(id: Int)(implicit repository: MessageRepository): Mono[Message] =
5
Mono.fromCallable(() => repository.findById(id).orElseThrow())
Опять же, есть reactorGetMessage
функция, обертывающая эффект извлечения сообщения в Mono
. Затем он используется для обслуживания запроса.
ЗиО
Для ZIO логика немного отличается, так как ZIO предоставляет свое собственное внедрение зависимостей.
Scala
xxxxxxxxxx
1
def zio(request: ServerRequest, repository: MessageRepository): ZIO[Any, Throwable, ServerResponse] = {
2
// Service logic
3
val response = for {
4
message <- zioGetMessage(request.getId)
5
response = new ServerResponse(s"${message.getContent} via ZIO")
6
} yield response
7
// Provide dependencies
9
response.provide(new InjectMessageRepository {
10
override val messageRepository = repository
11
})
12
}
13
def zioGetMessage(id: Int): ZIO[InjectMessageRepository, Throwable, Message] =
15
ZIO.accessM(env => ZIO.effect(env.messageRepository.findById(id).orElseThrow()))
16
trait InjectMessageRepository {
18
val messageRepository: MessageRepository
19
}
zioGetMessage
Снова обертывание получить сообщение эффекта базы данных в пределах ZIO
. Тем не менее, он извлекает введенную черту, чтобы получить хранилище.
Инкапсуляция в модуль
Вышеуказанные функции ( cats
, reactor
, zio
) выполнены в виде процедур первого класса в следующем модуле.
Этот модуль имеет выход , являющиеся Response
входы Cats
, Reactor
, ZIO
и Imperative
.
Поскольку процедуры первого класса лениво оцениваются, они могут также обернуть императивный код, содержащий эффекты. imperative
Функция заключается в следующем.
Scala
xxxxxxxxxx
1
def imperative(request: ServerRequest, repository: MessageRepository): ServerResponse = {
2
val message = repository.findById(request.getId).orElseThrow()
3
new ServerResponse(s"${message.getContent} via Imperative")
4
}
Использование модуля
Следующая конфигурация использует модуль для обслуживания запросов REST. Он настроен как синхронный модуль.
Это демонстрирует, как легко настроить модуль для обслуживания запросов.
Еще интереснее то, что асинхронный модуль имеет тот же интерфейс входов / выходов, что и синхронный модуль. Теперь это вполне может быть повторное использование вышеупомянутого модуля (просто с плохим именем). Однако это не так. Асинхронный модуль выполняет ту же логику, но только асинхронно ( код доступен в демонстрационном проекте ).
Для модулей важен договорный интерфейс входов и выходов. Мы вполне могли бы поменять местами синхронные / асинхронные модули в конфигурации, и приложение все еще будет работать. Это позволяет инкапсулировать сложность.
Более реальным примером является то, что мы могли бы начать с более быстрого написания и упрощения отладки синхронных эффектов. Затем, когда приложение масштабируется, мы можем решить заменить его на асинхронный модуль, чтобы лучше справляться с масштабированием. Количество рефакторинга для замены Синхронного модуля на Асинхронный модуль будет:
- Вставьте новый асинхронный модуль
- Повторно подключите потоки к асинхронному модулю
- Удалить Синхронный модуль
Поскольку Inversion of Coupling Control удаляет функцию связи, код для изменения не требуется, за исключением обеспечения реализации нового модуля.
С модулями, которые могут содержать модули, это обеспечивает способ инкапсулировать сложность приложения для более легкого понимания. Это также упрощает импорт модулей. Оставьте их и подключите их. И особенно полезно, когда библиотеки сторонних модулей доступны для композиции готовых к использованию функций.
Композитные эффекты
Это демонстрирует первоклассные процедуры и первоклассные модули из предыдущих статей этой серии.
Эй, но эта статья обещала написание эффектов!
Ну, я мог бы сказать вам, что посылка является эффектом, и что составление этого после вышеупомянутых эффектов - это композиция. Тем не менее, это требует много моего слова для этого.
Поэтому последний модуль в конфигурации сервера выглядит следующим образом.
Этот модуль составляет эффект от каждой из библиотек. Код для каждого эффекта следующий.
Scala
xxxxxxxxxx
1
def seed: String = "Hi"
2
def cats( param: String): IO[String] = IO.pure(s"$param, via Cats")
4
def reactor( param: String): Mono[String] = Mono.just(s"$param, via Reactor")
6
def zio( param: String): ZIO[Any, Nothing, String] = ZIO.succeed(s"$param, via ZIO")
8
def imperative( param: String): String = s"$param, via Imperative"
10
def response( message: String): ServerResponse = new ServerResponse(message)
Каждый эффект просто берет ввод предыдущего и добавляет свое имя библиотеки. Полученный ответ представляет собой строку, содержащую все имена библиотек эффектов.
Нет адаптеров
Проницательные читатели могут подумать, что под капотом OfficeFloor могут быть какие-то сказочные адаптеры между библиотеками. Хм, мы можем извлечь их и использовать их?
К сожалению, к счастью, между библиотеками нет адаптеров. На самом деле происходит то, что каждая процедура первого класса небезопасно выполняет свой эффект и получает полученный результат. С выходом OfficeFloor затем вызывает следующую процедуру первого класса. При этом нам не нужно адаптировать библиотеки друг к другу. Мы можем запускать каждый эффект изолированно и связывать их через их типизированные входы / выходы.
Это делает интеграцию новых библиотек эффектов очень простой. Просто напишите разовый адаптер, чтобы инкапсулировать эффекты библиотеки в Первоклассной процедуре. Затем библиотека эффектов может интегрироваться со всеми другими библиотеками эффектов. Поскольку процедуры первого класса на самом деле являются специализированным модулем первого класса, это демонстрирует возможности компоновки Inversion of Coupling Control.
Резюме
В этой статье было написано много кода и конфигурации, чтобы продемонстрировать, как сочетаются процедуры первого класса и модули первого класса.
Он продемонстрировал, что система типов Inversion of Coupling Control упрощает композицию (по существу, рисует линии).
Теперь вам не нужно верить моему слову о примерах кода в этой статье. Они извлечены из демонстрационного проекта, который вы можете клонировать и запустить самостоятельно (находится по адресу https://github.com/officefloor/ComposeEffectsDemo ).
Кроме того, если мы пропустили вашу любимую библиотеку эффектов, пожалуйста, извините. Мы будем рады, если вам будет интересно, использовать вместе с вами адаптеры для дальнейшей демонстрации интеграции любимой библиотеки эффектов. Фокус OfficeFloor не должен быть самоуверенным, а скорее предоставить открытую платформу для интеграции программного обеспечения.
Следующая статья в серии испытания моей самоучки математики , чтобы попытаться объяснить базовую модель, почему эта легкость композиции можно.
Дальнейшее чтение
Реактивное программирование с реактором проекта
Ложная надежда на управление эффектами с помощью Tagless-Final в Scala (часть 1)