Cleaner API's with the Facade Pattern

My Essays MOC

Cleaner API's with the Facade Pattern


title: "Saner apps with the Facade Pattern" date: "2019-11-11T17:00:00.00Z" banner: ./banner.jpg author: gage-peterson category: patterns

The Heavy Trunk

One day the trunk of my 2005 Toyota Camry just got stuck. It was a very old car, but had been very reliable in the past so it was a bit surprising. My wife showed me trunk made a grinding sound when you opened and closed it. I found trunk had become unattached this long metal bar that acted as the spring. This metal bar had the purpose of making the trunk float up when you opened it and stay open when you where getting things out. Now it was broken. I tried, but was unable to fix it and as such it became a guillotine for hands. Not only that, it was also super heavy to lift, far more than I would have assumed.

Cars are a great example of a good abstraction. Their interface is simple yet they are complex on the inside. The trunk perhaps is the simplest part of the car yet even this was quite complex when I started looking at the implementation details. It literally hid the weight of the trunk from me for years. It just magically floated up and that's all I knew.

The power of an Interface

The idea of an "interface" (also called an "API") have existed for a very long time in software as well. Mainly because, like cars, software tends to be very complex on the inside yet often provide a very simplified interface on the outside.

Hides complexity

The best interfaces hide complexity entirely so that user will never need to "look in the hood" to know how to drive. Otherwise the abstraction would be very unhelpful, you have to be an expert mechanic to even use the thing. This is not what you want. I think this quote embodies it well:

The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.

~ Edsger Dijkstra

A car's steering wheel is this way. You could say: "please rotate the tire-rod mover counter clockwise 20 degrees" but you don't have to, you can just say: "turn left here!".

Unification

The other cool part of an interface is that is has "combining power". Meaning it can help bring many different parts of a machine all together into one. For instance both the steering wheel and the brakes are completely different parts of the car. Even the stereo and the heat are right next to each other in the interface, yet are completely separate subsystems. The interface of the car combines them together in a seamless interface.

The Facade Pattern

There are many ways to create interfaces. Most programming languages have the idea of "public" functions. In elixir you can change a function to be private to the file by just changing a def to a defp. Yet, these constructs only work on a file level. But what happens if you want to hide a lot of files under one interface? This is where the Facade pattern comes into play.

Mainly the idea is you make one file that acts as an interface to a bunch of other files. Much like the dashboard of a car does this. Let's talk about how to do this.

File structure

We organize things the way you normally would in a context. Every context has a name which in our example below is widget.ex. This acts as the Public API to the sub-modules in the widget/ folder.

lib
├── your_app
│   ├── widget                (<-- context folder)
|   |   ├── private 
│   │   │   ├── create.ex           (<-- private function file)
│   │   │   ├── update_settings.ex  (<-- private function file)
│   │   │   └── upsert.ex           (<-- private function file)
│   └── widget.ex             (<-- facade)
...

Public V.S. Private here means that different contexts should never reference the private modules directly (Widget.Private.Create.call() should never access Org.Private.Upsert.call() directly, it should go through the facade Org.whitelist().

The Facade File (Public API)

A facade is a module that provides a simplified interface to a larger body of code.

Facade files adhere to the following rules:

Example of the facade pattern:

defmodule YourApp.Widget do
  @moduledoc """
  A widget is a unit of sale...
  """
  alias YourApp.Widget.{
    Create,
    Update,
  }

  @doc """
  Create a widget in the database. Any configuration options not specified will
  be provided with defaults.

  # Example

      iex> {:ok, id} = Widget.create()
  """
  defdelegate create(config \\ %{}), to: Create, as: :call

  @doc """
  Update a widget's configuration options. If the update is a no-op it will return an error.

  # Example

      iex> {:ok, id} = Widget.create()
      iex> Widget.update(id, color: "red")
  """
  defdelegate update(id, config), to: Update, as: :call
end

Private Function Files

Private function files adhere to the following rules:

Example of a private function file:

defmodule YourApp.Widget.Create do
  @moduledoc """
  Create a widget.
  """

  # this is the only public function
  def call(config \\ %{}) do
    # do stuff
  end

  defp helper_function() do
    # ...
  end
end

Designing good APIs

"If your application is difficult to use from iex* your APIs are probably wrong"

~ Aaron Renner

*NOTE: iex is ELixir's REPL where you can type in some elixir code, push enter and it will show you the result.)

The facade pattern makes the API of your project much easier to understand, remember, and reuse.

For instance, isn't this easier?

iex(4)> YourApp.Org.whitelist("1")
{:error, "Org is already whitelisted."}

than this?

iex(4)> YourApp.Org.OrgWhitelist.whitelist(%OrgRequest{org_id: 1})
{:error, "Org is already whitelisted."}

Code Generators

One barrier to this pattern is the fact that it can be a lot of work to create these. to fix this problem I made a mix command (mix gen.fn) to generate new andupdate facade and function files.

For example:

λ ~/lib/your_app/ mix gen.fn "Email.feedback_email_contents(user_metadata, org, feedback)"

./lib/your_app/email.ex doesn't exist, creating...
File wrote to: ./lib/your_app/email.ex
File wrote to: ./lib/your_app/email/feedback_email_contents.ex
File wrote to: ./test/your_app/email/feedback_email_contents_test.exs
File modified: ./lib/your_app/email.ex

Here's how I did it (NOTE: not the cleanest code): https://gist.github.com/justgage/0ad132409d0a91db88db5bc6f84666b3


Backlinks: