DO notation

Davydov Anton
May 21, 2018 · 2 min read

Часто рассказываю о новой фиче dry-monads — Do notation. Документации пока нет, но штука уже работает в проектах (повод сделать вклад в open source и помочь с документацией).

dry-transactions

Я уже писал о dry-transactions, это такая штука, которая благодаря steps реализует Railway Oriented Programming. Спустя год использования библиотеки нашлись проблемы.

1. Проброc стейта из шага в шаг. Сразу пример: сервис, который принимает и вызывает шаги:

step :find_account
step :validate_account
step :get_orders
step :select_invalid_orders
step :persist

В таком случае из каждого шага приходится прокидывать +1 новый объект или менять существующие объекты:

def find_account(account_id:)
def validate_account(account:)
def get_orders(account:)
def select_invalid_orders(account:, orders:)
def persist(account:, orders:, invalid_orders:)

Когда шагов больше 3 — возникает путаница и аргументы раздуваются или появляются хеши с не понятным количеством аргументов

2. Нет условных переходов между шагами. Например, если я хочу создать запись или обновить — придется все держать в одном шаге:

step :create_or_updatedef create_or_update(id:, payload:)
id ? repo.update(id, payload) : repo.create(payload)
end

3. Не критичные проблемы, такие как: шаги объявляются на уровне класса, а не метода (вкусовщина), методы нельзя сделать приватными. Кроме того, часто вижу вопрос как обернуть пару шагов в одну DB транзакцию (Around steps) выручает, но выглядит страшно)

Что с этим делать

В последнем (1.0.0.pre) релизе dry-monads появилась эмуляция Do нотаций хаскеля.

Эти изменения создают “шаги” выполнения логики в методе, который будет указан. Для этого инклюдим и миксины нужных монад в сервис. После этого, в методе вызываем с аргументом монадой. вернет либо значение “успешной” монады (, , etc), либо метод досрочно завершится и вернется значение как это сделано в dry-transactions.

Полезности

Кроме преимуществ dry-transactions закрываются недостатки подхода с .

Во первых, можно не пробрасывать полный контекст из метода в метод, а пробрасывать только нужные объекты:

def call(account_id)
account = yield find_account(account_id)
yield validate_account(account)
orders = yield get_orders(account)
invalid_orders = yield select_invalid_orders(orders)
persist(account, orders, invalid_orders)
end

Во вторых, никто не запрещает использовать условный переход:

def call(id, payload)
payload = yield validate(payload)
if id
create_object(payload)
else
update_object(id, payload)
end
end

Приватные методы работают без проблем. Обертка в транзакцию выглядит не так монструозно:

def call(id, payload)
# …
repo.transaction do
yield create_object(payload)
create_other(payload)
end
end

Минусы

Как и везде, минусов тоже хватает:

  1. Результатом выполнения кода, который используется с `yield`, должна быть монада, иногда выглядит громоздко;
  2. Если делать без контейнеров — заинжектить определенный шаг не получиться;
  3. Монады вызывают ужас у неподготовленных;
  4. Документации мало, не обкатано как транзакции, но проблем пока не возникало;
  5. Еще один способ сделать сервис объект;

Запомнить

  • Dry-transaction перестает работать, когда между шагами много стейта и (или) нужно вызывать шаги условно;
  • С версии 1.0 dry-monads предоставляют логику работы с шагами, которая может решить проблемы dry-transaction;

Ссылки

Pepegramming

https://t.me/pepegramming

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store