Skip to content

Instantly share code, notes, and snippets.

View AndrewDryga's full-sized avatar

Andrew Dryga AndrewDryga

View GitHub Profile
defmodule BookingApp.Booker do
alias BookingApp.{Billing, Users, Hotels, Cars, Flights, Repo}
def create_booking(attrs) do
Sage.new()
|> Billing.authorize_card(attrs)
|> Hotels.maybe_book_hotel(attrs)
|> Cars.maybe_book_car(attrs)
|> Flights.maybe_book_flight(attrs)
|> Users.sign_up()
@AndrewDryga
AndrewDryga / sign_up_geeting_worse.ex
Last active May 20, 2018 12:47
Sage: Business Logic Getting Worse
defmodule MyApp.Domain.User.SignUp do
def create_and_subscribe_user(attrs) do
Repo.transaction(fn ->
with {:ok, user} <- create_user(attrs),
{:ok, plans} <- fetch_subscription_plans(attrs),
{:ok, charge} <- charge_card(user, subscription),
{:ok, subscription} <- create_subscription(user, plan, attrs),
{:ok, _delivery} <- schedule_delivery(user, subscription, attrs),
{:ok, _receipt} <- send_email_receipt(user, subscription, attrs),
{:ok, user} <- update_user(user, %{subscription: subscription}) do
@AndrewDryga
AndrewDryga / sign_up.ex
Created May 20, 2018 12:19
Sage: Business logic example
defmodule MyApp.Domain.User.SignUp do
def create_and_subscribe_user(attrs) do
Repo.transaction(fn ->
with {:ok, user} <- create_user(attrs),
{:ok, plans} <- fetch_subscription_plans(attrs),
{:ok, charge} <- charge_card(user, subscription),
{:ok, subscription} <- create_subscription(user, plan, attrs),
{:ok, _delivery} <- schedule_delivery(user, subscription, attrs),
{:ok, _receipt} <- send_email_receipt(user, subscription, attrs),
{:ok, user} <- update_user(user, %{subscription: subscription}) do
@AndrewDryga
AndrewDryga / booking_sage.ex
Last active June 14, 2018 17:52
Saga: Booking
defmodule BookingApp.Sage do
import Sage
@spec book_trip(attrs :: map()) :: {:ok, last_effect :: any(), all_effects :: map()} | {:error, reason :: any()}
def book_trip(attrs) do
new()
|> run(:exchange_rate, &Billing.fetch_currency_exchange_rates/2, &Billing.currency_exchange_rates_circuit_breaker/4)
|> run(:authorization, &Billing.authorize_card/2, &Billing.cancel_card_authorization/4)
|> run_async(:book_hotel, &HotelsBooking.book/2, &HotelsBooking.cancel_booking/4)
|> run_async(:book_car, &CarsBooking.book/2, &CarsBooking.cancel_booking/4)
@AndrewDryga
AndrewDryga / stage_with_circuit_breaker.ex
Last active June 3, 2018 17:41
Sage: Stage with Circuit Breaker
def currency_exchange_rates_circuit_breaker(_effect_to_compensate, _effects_so_far, %{"base_currency" => base_currency}) do
with {:ok, exchange_rates} <- BookingApp.Cache.fetch(:eur_exchange_rates, base_currency) do
{:continue, exchange_rates}
else
_ -> :ok
end
end
@AndrewDryga
AndrewDryga / billing.ex
Created May 13, 2018 16:37
Sage: Booking example
defmodule BookingApp.Billing do
def authorize_card(%{exchange_rate: exchange_rate}, %{"card_token" => card_token, "total" => total}) do
{:ok, authorization} = authorize(card_token, total * exchange_rate)
end
def cancel_card_authorization(_effect_to_compensate, effects_to_compensate, _name_and_reason, %{"card_token" => card_token}) do
with {:ok, authorization} <- Map.fetch(effects_to_compensate, :authorization) || fetch_authorization_for_card(card_token) do
:ok = cancel_authorization(authorization)
end
end
@AndrewDryga
AndrewDryga / callbacks.ex
Last active June 4, 2018 12:36
Sage: Callbacks
@callback transaction(attrs :: map()) ::
{:ok, last_effect :: any(), all_effects :: map()} | {:error, reason :: any()}
@callback compensation(effect_to_compensate :: any(), effects_so_far :: map(), attrs :: any()) ::
:ok
| :abort
| {:retry,
[
{:retry_limit, pos_integer()},
{:base_backoff, pos_integer() | nil},
@AndrewDryga
AndrewDryga / compensation_with_retry.ex
Last active May 28, 2018 15:51
Sage: Stage with Retry in Compensation
def delete_subscription(_effect_to_compensate, %{user: user}, _name_and_reason, _attrs) do
:ok = SageExample.Billing.APIClient.delete_all_subscriptions_for_user(user)
# We want to apply forward recovery from :subscription stage for 5 times
{:retry, retry_limit: 5, base_backoff: 10, max_backoff: 30_000, enable_jitter: true}
end
@AndrewDryga
AndrewDryga / xref.ex
Created May 7, 2018 14:38
Find unused code
defmodule Mix.Tasks.Xref2 do
use Mix.Task
import Mix.Compilers.Elixir,
only: [read_manifest: 2, source: 0, source: 1, source: 2, module: 1]
@shortdoc "Performs cross reference checks"
@recursive true
@manifest "compile.elixir"
@AndrewDryga
AndrewDryga / rules.txt
Created April 9, 2018 13:45
Grok parser for Elixir logs
## You can use this set or rules to parse Elixir logs in DataDog Logger
# 16:01:37.511 request_id=2khj5fsrc3lpk86dh8000g5h [debug] Processing with TwilioProxy.RequestController.create
router %{date("HH:mm:ss.SSS"):date} (%{data::keyvalue("=", " ")} )?\[+%{word:level}\] Processing (with|by) +%{data:controller.callback}
# 16:04:07.995 request_id=u8m11nptsmsjc282k3l1133u6evmljer [info] Sent 200 in 16ms
# 16:04:07.995 request_id=u8m11nptsmsjc282k3l1133u6evmljer [info] Sent 200 in 16µs
# 00:05:30.048 request_id=sXax4ynq8i+zU90Emg84 [info] Sent 400 in 1ms