Abstraction in Elixir
👋 Viewer, I am sharing knowledge on how to bring abstraction between controller and downstream service in Elixir. It might help those developers who are new to Elixir and coming from the OOPS background.
In OOPS, the Abstraction principle is easy to understand and implement. We have interfaces/abstract classes and concrete classes to implement those. Example:
We could achieve a similar abstraction in Elixir via @behaviour
Let’s take an example of the Offer Management System, getting offers from downstream systems based on some business logic.
defmodule Offers do
@doc """
Get offers from various downstream systems
"""@callback fetch(OffersRequest) :: {:ok, OffersResponse} | {:error, String.t}
end
Modules adopting the Offers behavior will have to implement all the functions defined with the @callback
attribute (similar to the interface implementation in OOPS)
DownstreamOfferSystem1 & DownstreamOfferSystem2 will be invoking their respective endpoints.
defmodule DownstreamOfferSystem1 do
@doc """
Get offers from various Downstream systems1
"""@behaviour Offers
@impl true
def fetch(%OffersRequest{}) do# invoke downstream system
......
# parse response in OffersResponseend
defmodule DownstreamOfferSystem2 do
@doc """
Get offers from various Downstream systems2
"""@behaviour Offers
@impl true
def fetch(%OffersRequest{}) do # invoke downstream system 2
......
# parse response in OffersResponseend
Let's add the Dynamic dispatcher in `Offers.ex`
defmodule Offers do
@doc """
Get offers from various downstream systems
"""
@callback fetch(OffersRequest) :: {:ok, OffersResponse} | {:error, String.t}def fetch!(implementation, %OffersRequest{} = or) do
case implementation.fetch(or) do
{:ok, %OffersResponse{}} = resp -> resp
{:error, _} = e -> e
end
endend
Configure the default offer system in the ‘config.exs’
config :app, active_offer_client DownstreamOfferSystem2
Parent Function will look like this — (return offers from DownstreamOfferSystem2
)
defmodule Service dorequire Logger
alias Offersdef get_offer() dowith {:ok, client} <- get_active_offer_client,
{:ok, req} <- get_offer_request,
{:ok, %OffersResponse{} } = resp <- Offers.fetch(client,req) dorespelse
{:error, err} -> Logger.error(" caught exception ... fallback handling #{inspect(err)}")
enddefp get_active_offer_client do
{:ok,Application.get_env(:app, :active_offer_client)}
endend
Thanks for reading!