Elixir SOLID Principles - Examples
Tags: elixir, tutorial, beginners, architecture
Published: 2021-12-25 23:11:36

Building an application can be hard depending on how you do it, as our career passes and the knowledge we get, more information and good practices we use for creating and improving the software we maintain.

Some good practices are in SOLID Principles, a [mnemonic](https://en.wikipedia.org/wiki/Mnemonic) [acronym](https://en.wikipedia.org/wiki/Acronym) for five design principles intended to make software designs more understandable, flexible, and [maintainable](https://en.wikipedia.org/wiki/Software_maintenance).

Is good to be clear, SOLID has been created with Object-Oriented Programming in mind, so we are adapting to Elixir, a functional programming language, we are going to see that the benefits do not depend on the programming paradigm.

![Solid](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tiukvqng5ei7yrf4pxse.png)

## Example - Animals module

To explain better how we use the solid principles on elixir, I am gonna create a module and for each principle, we refactor it, the module is called Animals and is responsible to create an animal, adding some customization, getting and sending a picture of it.

The example below is the first version:

```elixir
defmodule Animals do
	def create_mammal(), do: #create an animal of type mammal

	def create_carnivorous(), do: #create an animal of type carnivorous

	def add_hat(animal), do: #add a hat to the animal

	def add_shirt(animal), do: #add a hat to the animal

	def get_picture(animal), do: #get picture from the animal

	def send_picture_email(picture, email), do: #send the picture to email
	
	def send_picture_whatsapp(picture, number), do: #send the picture to whatsapp
end

iex> {:ok, animal} = Animals.create_mammal()
iex> {:ok, animal_customized} = animal |> Animals.add_hat() |> Animals.add_shirt()
iex> :ok = animal_customized |> Animals.get_picture() |> Animals.send_picture_email("email@example.com")
```

if you noticed that this is not maintainable or scalable, you are right, in the first moment that could work, but responsibilities are mixed in a single module and are confusing.

The first principle that we are going to use is Single Responsibility, modules must be separated by their context.

### Single Responsibility Principle

"There should never be more than one reason for a [class](https://en.wikipedia.org/wiki/Class_(computer_programming)) to change." 

```elixir
defmodule Animals do
	def create_mammal(), do: #create an animal of type mammal

	def create_carnivorous(), do: #create an animal of type carnivorous
end

defmodule Animals.Clothes do
	def add_hat(animal), do: #add a hat to the animal

	def add_shirt(animal), do: #add a hat to the animal
end

defmodule Animals.Pictures do
	def get_picture(animal), do: #get picture from the animal

	def send_picture_email(picture, email), do: #send the picture to email
	
	def send_picture_whatsapp(picture, number), do: #send the picture to whatsapp
end

iex> {:ok, animal} = Animals.create_mammal()
iex> {:ok, animal_customized} = animal |> Animals.Clothes.add_hat() |> Animals.Clothes.add_shirt()
iex> :ok = animal_customized |> Animals.Pictures.get_picture() |> Animals.Pictures.send_picture_email("email@example.com")
```

Now is more clear what each module does, but if we get the Animals.Pictures and try to add one more sending method, it starts to be a little repetitive and we break Open Closed principle, because we are modifying an entity.

### Open Closed Principle

"Software entities ... should be open for extension, but closed for modification."

```elixir
# FROM
defmodule Animals.Pictures do
	def get_picture(animal), do: #get picture from the animal

	def send_picture_email(picture, email), do: #send the picture to email
	
	def send_picture_whatsapp(picture, number), do: #send the picture to whatsapp
end

#TO
defmodule Animals.Pictures do
	def get(animal), do: #get picture from the animal

	def send(picture, data, :email), do: #send the picture to email
	
	def send(picture, data. :whats), do: #send the picture to whatsapp
end

iex> {:ok, animal} = Animals.create_mammal()
iex> {:ok, animal_customized} = animal |> Animals.Clothes.add_hat() |> Animals.Clothes.add_shirt()
iex> :ok = animal_customized |> Animals.Pictures.get() |> Animals.Pictures.send("email@example.com", :email)
```

In that way, we just create a new function send/3 without modifying the entity.

Another thing that can be improved is the module that creates the animals, currently, it has a general proposal, which is not extensible because we are changing the interface at each modification, sometimes is better to have a specific module that uses the same interface.

### Interface segregation principle

"Many client-specific interfaces are better than one general-purpose interface."

```elixir
# FROM
defmodule Animals do
	def create_mammal(), do: #create an animal of type mammal

	def create_carnivorous(), do: #create an animal of type carnivorous
end

#TO
defmodule Animals.Mammal do
	def create(), do: #create an animal of type mammal
end

defmodule Animals.Carnivorous do
	def create(), do: #create an animal of type carnivorous
end

iex> {:ok, animal} = Animals.Mammal.create()
iex> {:ok, animal_customized} = animal |> Animals.Clothes.add_hat() |> Animals.Clothes.add_shirt()
iex> :ok = animal_customized |> Animals.Pictures.get() |> Animals.Pictures.send("email@example.com", :email)
```

Now they are using the same interface and it is easy to extend each specific module without changing the interface that creates a new animal.

The Liskov principle states that a superclass object should be replaceable with a subclass object without breaking the functionality of the software and makes heavy use of inheritance and polymorphism, but for a functional programming language we have to deal with it in another way, elixir has behaviours, so all the modules that use the defined behaviour must implement their functions.

### Liskov substitution principle

"Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it."

```elixir
# FROM
defmodule Animals.Clothes do
	def add_hat(animal), do: #add a hat to the animal

	def add_shirt(animal), do: #add a shit to the animal
end

#TO
defmodule Animals.Animal do
  @type t :: %{type: String.t, name: String.t}

  defstruct ~w(type name)a
end

defmodule Animals.Clothes.Clothing do
  @callback add(Animals.Animal.t) :: Animals.Animal.t
end

defmodule Animals.Clothes.Hat do
  @behaviour Animals.Clothes.Clothing

  def add(animal), do: #add a hat to the animal
end

defmodule Animals.Clothes.Shirt do
  @behaviour Animals.Clothes.Clothing

  def add(animal), do: #add a shirt to the animal
end

defmodule Animals.Clothes do
  @spec apply(Animals.Animal.t, String.t)
  def apply(animal, :hat), do: Animals.Clothes.Hat.add(animal)
  def apply(animal, :shirt), do: Animals.Clothes.Shirt.add(animal)
end

iex> {:ok, animal} = Animals.Mammal.create()
iex> {:ok, animal_customized} = animal |> Animals.Clothes.apply(:hat) |> |> Animals.Clothes.apply(:shirt)
iex> :ok = animal_customized |> Animals.Pictures.get() |> Animals.Pictures.send("email@example.com", :email)
```

We have defined the Animals.Clothes.Clothing behaviour and for instance the Animals.Clothes.Hat must use the add/1 callback, so in this way, we guarantee that all the modules that implement it have the same action without breaking the functionality.

Now if we look at the Animals.Clothes module, we are explicitly declaring witch module has to be used depending on function parameters, this is incorrect accordingly with Dependency inversion principle, the correct way to fix this is to abstract the apply/2 function to receive a module in its arguments and use the function of that module because we have the type definition.

### Dependency inversion principle

"Depend upon abstractions, [not] concretions."

```elixir
# FROM
defmodule Animals.Clothes.Clothing do
  @callback add(Animals.Animal.t) :: Animals.Animal.t
end

defmodule Animals.Clothes do
  @spec apply(Animals.Animal.t, String.t)
  def apply(animal, :hat), do: Animals.Clothes.Hat.add(animal)
  def apply(animal, :shirt), do: Animals.Clothes.Shirt.add(animal)
end

#TO
defmodule Animals.Clothes.Clothing do
  @type t :: module()

  @callback add(Animals.Animal.t) :: Animals.Animal.t
end

defmodule Animals.Clothes do
  @spec apply(Animals.Animal.t, Animals.Clothes.Clothing.t)
  def apply(animal, clothing), do: clothing.add(animal)
end

iex> {:ok, animal} = Animals.Mammal.create()
iex> {:ok, animal_customized} = animal |> Animals.Clothes.apply(Animals.Clothes.Hat) |> |> Animals.Clothes.apply(Animals.Clothes.Shirt)
iex> :ok = animal_customized |> Animals.Pictures.get() |> Animals.Pictures.send("email@example.com", :email)
```

With the type definition, we can abstract the inner implementation to depend on the module itself, and we know what the module does because of the behaviour that it uses.

If we look for the difference between the first implementation and the final result, it is more verbose, but much more readable, maintainable and extensible.

```elixir
# FROM
defmodule Animals do
	def create_mammal(), do: #create an animal of type lion

	def create_carnivorous(), do: #create an animal of type dog

	def add_hat(animal), do: #add a hat to the animal

	def add_shirt(animal), do: #add a hat to the animal

	def get_picture(animal), do: #get picture from the animal

	def send_picture_email(picture, email), do: #send the picture to email
	
	def send_picture_whatsapp(picture, number), do: #send the picture to whatsapp
end

#TO
defmodule Animals.Mammal do
	def create(), do: #create an animal of type mammal
end

defmodule Animals.Carnivorous do
	def create(), do: #create an animal of type carnivorous
end

defmodule Animals.Pictures do
	def get(animal), do: #get picture from the animal

	def send(picture, data, :email), do: #send the picture to email
	
	def send(picture, data. :whats), do: #send the picture to whatsapp
end

defmodule Animals.Animal do
  @type t :: %{type: String.t, name: String.t}

  defstruct ~w(type name)a
end

defmodule Animals.Clothes.Clothing do
  @type t :: module()

  @callback add(Animals.Animal.t) :: Animals.Animal.t
end

defmodule Animals.Clothes.Hat do
  @behaviour Animals.Clothes.Clothing

  def add(animal), do: #add a hat to the animal
end

defmodule Animals.Clothes.Shirt do
  @behaviour Animals.Clothes.Clothing

  def add(animal), do: #add a shirt to the animal
end

defmodule Animals.Clothes do
  @spec apply(Animals.Animal.t, Animals.Clothes.Clothing.t)
  def apply(animal, clothing), do: clothing.add(animal)
end

iex> {:ok, animal} = Animals.Mammal.create()
iex> {:ok, animal_customized} = animal |> Animals.Clothes.apply(Animals.Clothes.Hat) |> |> Animals.Clothes.apply(Animals.Clothes.Shirt)
iex> :ok = animal_customized |> Animals.Pictures.get() |> Animals.Pictures.send("email@example.com", :email)
```

 

I appreciate everyone who has read through here, if you guys have anything to add, please leave a comment.

This post was inspired by:

[https://medium.com/@andreichernykh/solid-elixir-777584a9ccba](https://medium.com/@andreichernykh/solid-elixir-777584a9ccba)