DEV Community

Cover image for Understanding Clean Architecture with Small Ruby Libraries
Moeki Kawakami
Moeki Kawakami

Posted on • Edited on

Understanding Clean Architecture with Small Ruby Libraries

After about 5 laps around Clean architecture since I came across hanami/hanami: The web, with simplicity., I'm finally getting it down in my gut, so I'll summarize.

It is difficult to understand while using a frameworks like Ruby on Rails

When using a framework, the outer moat of the Usecase in the diagram is filled in: Controller, View, and Model in the case of MVC, and Repository in the case of other patterns. Personally, I feel that if all three layers from the inner layer are integrated, they are the center of the application.

Image description

So I tried to implement each responsibility of the application using different libraries.

Here it is. cc-kawakami/clean-architecture-minimal-app: A minimal Clean Architecture app

These are the libraries we used.

"Let there be Entity."

We will attack from the heart of the architecture. First, Entity.

class User
  attr_reader :id, :name, :email

  def initialize(id:, name:, email:)
    @id = id
    @name = name
    @email = email
  end
end
Enter fullscreen mode Exit fullscreen mode

This is a plain ruby object. It defines what kind of information you want to handle in your business. In this case, we have a User Entity to manage users.

Next, the business logic of the app

This app will allow you to search users by ID.

class FindUser
  include Hanami::Interactor

  expose :user

  def initialize(repository:, serializer:)
    @repository = repository
    @serializer = serializer
  end

  def call(id)
    begin
      # Find users
    rescue => e
      error!(e.class.name)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

We now have a Use Case that looks like this. We can now define what purpose the app serves in the business: Repository, Serializer is an Adapter that fetches Entity and outputs it. This is used by Use Case.

Then, we can create the Interface Adapters.

Repository.

class UserRepository < ROM::Repository[:users].
  commands :create

  def find(id)
    users.by_pk(id).map_to(User).one
  end
end
Enter fullscreen mode Exit fullscreen mode

Controller.

get "/users/:id" do
  find = FindUser.new.call(params["id"])

  if find.success?
    if find.user
      status 200
      body = find.user
    else
      status 404
      body = { error: "Not found!" }
    end
  else
    status 500
    body = { error: find.error }
  end

  json body
end
Enter fullscreen mode Exit fullscreen mode

Serializer.

class UserSerializer < Blueprinter::Base
  identifier :id

  fields :name, :email
end
Enter fullscreen mode Exit fullscreen mode

We're all set.

Now we can write the details of the Use case that came up earlier.

class FindUser
  include Hanami::Interactor

  expose :user

  def initialize(
    repository: UserRepository.new(App.new.rom),
    serializer: UserSerializer
  )
    @repository = repository
    @serializer = serializer
  end

  def call(id)
    begin
      user = @repository.find(id)
      @user = @serializer.render_as_hash(user)
    rescue => e
      error!(e.class.name)
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Execute

$ curl http://127.0.0.1:9292/users/1
{"id":1, "email": "smith@exmaple.com", "name": "Smith"}
Enter fullscreen mode Exit fullscreen mode

OK, so you get the sense that the HTTP request/response is just one of the input/output details of business logic.
Likewise, the DB is only one of the implementations of creating the User. DB and HTTP are details.

Difficult to mix responsibilities, No mixing of responsibilities

This design naturally leads to a design in which the details of DB, HTTP, HTML, and JSON are distributed in each direction of the circle, starting from the Use case. The above is all.

That's all. Thank you very much.

Top comments (2)

Collapse
 
nvoynov profile image
Nikolay Voynov

Hi, I've just created Punch Code Generator, which exactly matches The Clean Architecture, just PORO, entity, service, plugin, DSL for describing domains, etc. Templates can be changed according your needs, Have a look github.com/nvoynov/punch

Collapse
 
deyvin profile image
mano deyvin

Man,
That is amazing, thanks for share.