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

Великие архитекторы делают архитектуру простой.

Следовательно, возникает вопрос, как сделать нашу архитектуру простой?

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

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

  • функция
  • микросервис
  • архитектура

Упростить функцию

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

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

Если мы присмотримся повнимательнее, то поймем, что эта функция не очень хороша, есть ряд вещей, которые могут пойти не так.

Наиболее очевидным из них является то, что вызов API может завершиться ошибкой, а выборка вызовет ошибку.

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

Третьим может быть неприятная маленькая ошибка, которая может быть связана с неправильной сборкой конечной точки, что может быть причиной сбоя нашего вызова выборки.

Итак, что мы можем с этим поделать? Добавьте тесты, регистрацию и обработку ошибок. И вдруг наша маленькая функция запутывается кодом, который не имеет ничего общего с бизнес-логикой того, как из xyz и update собрать абв.

Я знаю, что это выглядит как много кода, но потерпите меня. Мы можем игнорировать parseJsonResponse, logAndPassOn и throwWithStatusCode, потому что это всего лишь общие вспомогательные функции, которые я бы повторно использовал повсюду, чтобы сделать нашу цепочку промисов более читаемой.

Мы извлекли transformXYZtoABC и getURL, чтобы мы могли тестировать эти части простой логики независимо и без моков.

Обратите внимание, что функция createABC только управляет потоком. Мы можем добавить ведение журнала и обработку ошибок, не создавая слишком много сложности, потому что вся логика абстрагируется в функциях.

Упрощение микросервиса

Существует множество определений микросервиса. Точное определение не имеет отношения к этому посту. Давайте просто подумаем о программном обеспечении, которое можно развернуть самостоятельно (например, контейнер или бессерверная функция). Этот развертываемый объект состоит из набора функций и данных.

Как и в приведенном выше примере, мы разделяем нашу бизнес-логику (вычисления) и наши побочные эффекты (действия). Если мы будем следовать подходу TDD, мы обычно начинаем реализовывать наши данные (сущности и типы) и вычисления. Мы составляем наши функции, чтобы абстрагировать нашу логику до такой степени, что мы не можем абстрагироваться дальше, не нуждаясь в побочных эффектах.

Затем мы переходим к написанию наших действий и включаем наши вычисления (составные функции) в действия для выполнения нашей бизнес-логики.

В наших действиях также принято использовать абстракции. Например, использование контроллеров для определения конечных точек HTTP API и перемещение обработки в службу.

Лично я считаю, что разделение конфигурации и управления потоком имеет смысл, как и отделение моей конечной точки и определения API от обработки входящих данных.

Тем не менее, я считаю наличие нескольких уровней абстракции над побочным эффектом недостатком кода. Части моего приложения, которые могут сломаться, должны быть прямо перед моим лицом, а не скрыты абстракцией. Конечно, это работает только в том случае, если у меня есть ограниченное количество побочных эффектов для любого данного микросервиса.

Упрощение архитектуры

Давайте подумаем об архитектуре как о способе взаимодействия различных программных систем.

Когда мы хотим упростить нашу архитектуру, мне нравится применять два принципа.

  1. Минимизируйте побочные эффекты
  2. Предпочитайте неизменяемые события.

Минимизируйте побочные эффекты

Когда вы хотите построить распределенную систему, одна из самых сложных вещей — решить, как разбить функциональность на разные компоненты (микросервисы).

Есть несколько факторов, которые вступают в игру. С точки зрения простоты мы должны попытаться свести к минимуму взаимодействие между службами. Это может показаться неинтуитивным, потому что в распределенной системе это то, что делают сервисы. Однако каждый синхронный вызов API, необходимый для выполнения запроса клиента, представляет собой тесно связанную зависимость между службами. То самое, от чего вы хотели избавиться, перейдя на микросервисы. Кроме того, каждый вызов API потенциально может быть нарушен, что создает необходимость проектирования для исправления или распределенных транзакций, которые сложно разработать, внедрить и поддерживать.

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

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

Если мы не хотим связывать наши сервисы с помощью синхронных вызовов HTTP, мы можем использовать асинхронные события.

Предпочитать неизменяемые события

Если ваше приложение является облачным, крупномасштабным или распределенным и не включает компонент обмена сообщениями, это, вероятно, ошибка. — Тим Брей

Я абсолютно согласен. Обмен сообщениями и, в частности, асинхронный обмен сообщениями о событиях — отличный способ отделить микросервисы. Отличие от вызовов API в том, что отправитель не знает о получателе. Это принцип «выстрелил и забыл», который зависит только от системы обмена сообщениями. Это полиморфизм, но для архитектуры. Вам не нужно заранее знать или решать, кто ваши подписчики.

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

У обмена сообщениями о событиях есть и другие преимущества, такие как простая интеграция с инструментами аналитики или даже с массивами данных, но это было бы слишком много, чтобы вдаваться в этот пост в блоге.

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

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

Ваше здоровье!