Expecting the Unexpected

Tobias Kräntzer

Senior Software Developer

Qixxit

bus + train + flight

Journey

authorize payment
authorize payment
book train
book train
book flight
book flight
capture payment
capture payment
send email
send email
prepare request to
book train ticket
[Not supported by viewer]
book train ticket
book train ticket
handle response &
persist result
[Not supported by viewer]
Qixxit
Qixxit
Train Operator
Train Operator
prepare request to
book train ticket
[Not supported by viewer]
book train ticket
book train ticket
handle response &
persist result
[Not supported by viewer]
Qixxit
Qixxit
Train Operator
Train Operator
prepare request to
book train ticket
[Not supported by viewer]
book train ticket
book train ticket
handle response &
persist result
[Not supported by viewer]
Qixxit
Qixxit
Train Operator
Train Operator

Do we have the ticket?

Do we owe money?

prepare request to
book train ticket
[Not supported by viewer]
book train ticket
book train ticket
handle response &
persist result
[Not supported by viewer]
Qixxit
Qixxit
Train Operator
Train Operator
create & store
transaction id
[Not supported by viewer]
request + transaction id
request + transaction id
Error Handling
Error Handling

Example in Elixir


                defmodule Checkout do
                  def run(order, payment) do
                    with {:ok, order} <- Payment.authorize(order, payment),
                         {:ok, order} <- Product.book(order),
                         {:ok, order} <- Payment.capture(order) do
                      :ok
                    end
                  end
                end
              

                defmodule Product do
                  def book(order) do
                    tx_id = UUID.uuid4()
                    
                    request = %{
                      transaction_id: tx_id,
                      ticket_details: order.some_details
                    }
                    
                    with {:ok, order} <- save_transaction_id(order, tx_id),
                         {:ok, response} <- send_request(request),
                         {:ok, ticket} <- handle_response(response),
                         {:ok, order} <- save_ticket(order, ticket) do
                      Logger.info("We have a ticket!")
                      {:ok, order}
                    end
                  end
                end
              

                test "booking a ticket" do
                  order = %{ some_details: "booking data" }

                  StoreMock
                  |> expect(:save_transaction_id, fn order, tx_id ->
                    {:ok, order}
                  end)

                  APIMock
                  |> expect(:send_request, fn request ->
                    response = ...
                    {:ok, response}
                  end)

                  StoreMock
                  |> expect(:save_ticket, fn order, ticket ->
                    {:ok, order}
                  end)

                  assert {:ok, order} = Product.book(order)
                  assert order.ticket != nil
                end
              

                test "booking a ticket" do
                  order = %{ some_details: "booking data" }

                  StoreMock
                  |> expect(:save_transaction_id, fn order, tx_id ->
                    {:ok, order}
                  end)

                  APIMock
                  |> expect(:send_request, fn request ->
                    response = ...
                    {:ok, response}
                  end)

                  StoreMock
                  |> expect(:save_ticket, fn order, ticket ->
                    {:ok, order}
                  end)

                  assert {:ok, order} = Product.book(order)
                  assert order.ticket != nil
                end
              

              defmodule Product do
                def book(order) do
                  tx_id = UUID.uuid4()
                  
                  request = %{
                    transaction_id: tx_id,
                    ticket_details: order.some_details
                  }
                  
                  with {:ok, order} <- save_transaction_id(order, tx_id),
                       {:ok, response} <- send_request(request),
                       {:ok, ticket} <- handle_response(response),
                       {:ok, order} <- save_ticket(order, ticket) do
                    Logger.info("We have a ticket!")
                    {:ok, order}
                  end
                end
              end
            

              defmodule Product do
                def book(order) do
                  tx_id = UUID.uuid4()
                  
                  request = %{
                    transaction_id: tx_id,
                    ticket_details: order.some_details
                  }
                  
                  with {:ok, order} <- save_transaction_id(order, tx_id),
                       {:ok, response} <- send_request(request),
                       {:ok, ticket} <- handle_response(response),
                       {:ok, order} <- save_ticket(order, ticket) do
                    Logger.info("We have a ticket!")
                    {:ok, order}
                  end
                end
              end
            

            defp prepare(order) do
              tx_id = UUID.uuid4()
        
              request = %{
                transaction_id: tx_id,
                ticket_details: order.some_details
              }
        
              with {:ok, order} <- save_transaction_id(order, tx_id) do
                {:ok, order, request}
              end
            end

            defp commit(order, request) do
              with {:ok, response} <- send_request(request),
                   {:ok, ticket} <- handle_response(response),
                   {:ok, order} <- save_ticket(order, ticket) do
                {:ok, order}
              end
            end
          

            def book(order) do
              with {:ok, order, request} <- prepare(order),
                   {:ok, order} <- commit(order, request) do
                Logger.info("We have a ticket!")
                {:ok, order}
              end
            end

            defp prepare(order)
            defp commit(order, request)
          

            defp prepare(order) do
              tx_id = UUID.uuid4()
        
              request = %{
                transaction_id: tx_id,
                ticket_details: order.some_details
              }
        
              with {:ok, order} <- save_transaction_id(order, tx_id) do
                {:ok, order, request}
              end
            end

            defp commit(order, request) do
              with {:ok, response} <- send_request(request),
                   {:ok, ticket} <- handle_response(response),
                   {:ok, order} <- save_ticket(order, ticket) do
                {:ok, order}
              end
            end
          

            defp prepare(order) do
              tx_id = UUID.uuid4()
        
              request = %{
                transaction_id: tx_id,
                ticket_details: order.some_details
              }
        
              with {:ok, order} <- save_request(order, request) do
                {:ok, order, request}
              end
            end

            defp commit(order, request) do
              with {:ok, response} <- send_request(request),
                   {:ok, ticket} <- handle_response(response),
                   {:ok, order} <- update_order(
                                     %Order{order | ticket: ticket}) do
                {:ok, order}
              end
            end
          

            defmodule Product do
              def prepare(order) do
                tx_id = UUID.uuid4()

                request = %{
                  transaction_id: tx_id,
                  ticket_details: order.some_details
                }

                {:ok, request}
              end

              def commit(order, request) do
                with {:ok, response} <- send_request(request),
                     {:ok, ticket} <- handle_response(response) do
                  {:ok, %Order{order | ticket: ticket}}
                end
              end
            end

            defmodule Store do
              def save_request(order, request)
              def update_order(order)
            end
          

            defmodule Product do
              def prepare(order)
              def commit(order, request)
            end

            defmodule Store do
              def save_request(order, request)
              def update_order(order)
            end

            def book(order) do
              with {:ok, request} <- Product.prepare(order),
                   {:ok, request} <- Store.save_request(order, request),
                   {:ok, order} <- Product.commit(order, request),
                   {:ok, order} <- Store.update_order(order) do
                Logger.info("We have a ticket!")
                {:ok, order}
              end
            end
          

                test "booking a ticket" do
                  order = %{ some_details: "booking data" }

                  StoreMock
                  |> expect(:save_transaction_id, fn order, tx_id ->
                    {:ok, order}
                  end)

                  APIMock
                  |> expect(:send_request, fn request ->
                    response = ...
                    {:ok, response}
                  end)

                  StoreMock
                  |> expect(:save_ticket, fn order, ticket ->
                    {:ok, order}
                  end)

                  assert {:ok, order} = Product.book(order)
                  assert order.ticket != nil
                end
              

            test "booking a ticket" do
              order = %{ some_details: "booking data" }

              assert {:ok, request} = Product.prepare(order)
              assert request.transaction_id != nil

              APIMock
              |> expect(:send_request, fn request ->
                response = ...
                {:ok, response}
              end)
              
              assert {:ok, order} = Product.commit(order, request)
              assert order.ticket != nil
            end
          

              defmodule Product do
                def prepare(order)
                def commit(order, request)
              end
  
              defmodule Store do
                def save_request(order, request)
                def update_order(order)
              end
  
              def book(order) do
                with {:ok, request} <- Product.prepare(order),
                     {:ok, request} <- Store.save_request(order, request),
                     {:ok, order} <- Product.commit(order, request),
                     {:ok, order} <- Store.update_order(order) do
                  Logger.info("We have a ticket!")
                  {:ok, order}
                end
              end
            

            defmodule AnotherProduct do
              def prepare(order)
              def commit(order, transaction)
            end

            defmodule Store do
              def prepare(order, transaction)
              def commit(order)
            end

            def book(order) do
              with {:ok, transaction} <- AnotherProduct.prepare(order),
                   {:ok, transaction} <- Store.prepare(order,
                                                        transaction),
                   {:ok, order} <- AnotherProduct.commit(order, transaction),
                   {:ok, order} <- Store.commit(order) do
                Logger.info("We have a ticket!")
                {:ok, order}
              end
            end
          

separation of concerns

book train
book train
commit
(store result)
[Not supported by viewer]
prepare
(store request)
prepare<br>(store request)<br>
prepare
(create request)
[Not supported by viewer]
commit
(call 3rd-party)
commit<br>(call 3rd-party)<br>
Train Operator
Train Operator

Thank You

Tobias Kräntzer
tobias@qixxit.de
https://qixxit.com


https://medium.com/qixxit-development
https://tobias-kraentzer.de/expecting-the-unexpected