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.
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.
- Use Case: utils/interactor.rb at v1.3.8 - hanami/utils
 - Controller: sinatra/sinatra: Classy web-development dressed in a DSL (official / canonical repo)
 - Serializer: procore/blueprinter: Simple, Fast, and Declarative Serialization Library for Ruby
 - Object Mapper: rom-rb/rom: Data mapping and persistence toolkit for Ruby
 - DB: sparklemotion/sqlite3-ruby: Ruby bindings for the SQLite3 embedded database
 
"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
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
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
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
Serializer.
class UserSerializer < Blueprinter::Base
  identifier :id
  fields :name, :email
end
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
Execute
$ curl http://127.0.0.1:9292/users/1
{"id":1, "email": "smith@exmaple.com", "name": "Smith"}
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)
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
Man,
That is amazing, thanks for share.