I will bring here an approachable solution for building rails applications using some fundamentals of the so-called clean architecture. But, what is it?
clean architecture
It's hard to tell what it is without telling about the problem.
The problem is, look at your rails application, try to identify what it does, open your app
folder and try to identify again.
We can see some folders like controllers, models, helpers and so on. Even when opening one of those folders we cannot say what is does, but we can only say what that application have, like users or products. The clean architecture came to try to solve this problem.
One thing we can notice on that rails applications is that it's screaming web framework!
.
But one thing we can never forget is that the web is a delivery system and should not dominate our code.
the proposal
A picture is worth a thousand words so let's check some images from Robert C. Martin illustrating his proposal of what path the request should follow:
the user interacts with the system
the delivery mechanism builds a request model and passes it to the boundary/interface
the interface knows which interactor to use and passes the request model to it
and the interactor executes the business rules
and interacts with entities
the interactor builds a result model and give it back to the boundary/interface
which will give it back to the delivery mechanism and the user
As you can see, the framework is just a detail in that architecture, it's a delivery mechanism. The interface to the user can be a website as well as a command line tool, the business rules go on the use cases which can interact with entities.
what about MVC?
MVC (aka Model View Controller) is an architectural pattern which the user interacts with a controller who manipulates a model who updates the view where the user sees the result. I got this image from wikipedia to illustrate better:
The MVC pattern was created by Trygve Reenskaug to use with SmallTalk for graphical user interface (GUI) software design in 1979.
But what happens when we use this approach to build our entire web application?
It gets messy.
I'm not saying the MVC is bad, I'm saying that it's part of the delivery mechanism not of the application architecture.
real world use case
As an user, I want to pay an invoice
Imagine you have a web application and you send invoices to your users, and then they pay their invoices. So that use case could be named 'pay_invoice' and to group related classes I will create a module call Accoutant
.
The goal is to call the use case in the controller like this:
class InvoicesController < ApplicationController
def pay
if Accountant.pay_invoice(params[:invoice_id], credit_card_params)
format.html { redirect_to @invoice, notice: 'Invoice was successfully paid.' }
else
format.html { redirect_to @invoice, error: 'We got a problem paying that invoice' }
end
end
end
In order to have that use case available we need to create a class like so:
require 'caze'
module Accountant
class PayInvoice
include Caze
attr_accessor :invoice_id, :credit_card
export :call, as: :pay_invoice
def initialize(invoice_id, credit_card)
@invoice_id = invoice_id
@credit_card = credit_card
end
def call
# register payment thought payment gateway (maybe background job)
# change invoice status
end
private
def invoice
@invoice ||= Invoice.find(invoice_id)
end
end
end
some points to notice:
- there is only one public instance method (
call
) - this class has one responsibility
- easy to write specs
- I used the
caze
gem to have a simple DSL to define use cases instead of doing this:
def self.pay_invoice(invoice_id, credit_card)
self.new(invoice_id, credit_card).call
end
We add that use case to the Accountant
module like so:
require 'caze'
module Accountant
include Caze
has_use_case :pay_invoice, PayInvoice
end
Now we can say one thing that this application does which is pay invoices. Take a look on how the files are organized:
You don't really need to use the caze
gem, the important part of all this is:
We separated what the application HAS
models like invoice, account and so on that doesn't change very much (stable)
of what the application DOES
behaviors and use cases tend to change a lot while the business grows
when to write use cases?
When you only need to manage resources, you can and should use the rails way which is resource oriented so it makes sense, but when you have a special logic and it's very unique to your business rules, you write use cases like the code above.
conclusion
After all that work we can look on our application and quickly see what it does instead of just seeing resources which is what it has, you can also move between frameworks or create new interfaces to use your unique business rules.
Keep in mind that there is no silver bullet in programming, but this is for sure a very effective approuch to keep your use cases organized and keep your software architecture clean.
Top comments (4)
Hey thanks for writing this. It reminds me about form objects and service objects (for when you are saving and retrieving respectively). It is pretty similar, but I like the name of "cases".
It is interesting to see how many devs are changing on Rails from the common pattern to use objects (following SOLID), Rails projects got success, the time goes by and the projects became hard to maintain so it was needed to go back to foundations. It is good, don't know if Rails 6 will introduce some guidance about these topics (I don't think so).
Really glad that you liked it =)
Because there is no guidance/common way of writing business logic on rails, what I've seen so far is that each company does it in a different way.
Using use cases like I described in this blog post is one of them.
btw, I also recommend to check some design patterns in ruby: github.com/davidgf/design-patterns...
it's quite interesting =)
What is the difference between use-case object and service object?
Sorry for the late response. I'd say the way you organize it. A service can be from getting data from a third-party API to a use case with business logic. When I was thinking about use cases in the blog posts as I tried to think about actual use cases that a product manager would define, not just a class that have one responsibility and one method to be call.
We all are always improving, so am I so I don't like this approach that much anymore, but there is a project call trailblazer.to that I really commend you to check it out. Very interesting.