DEV Community

lucasprag
lucasprag

Posted on • Updated on

Use-case oriented rails applications

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 web is a delivery system

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 user interacts with the system

the delivery mechanism builds a request model and passes it to the boundary/interface

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

the interactor executes the business rules and interacts with entities

and interacts with entities

and interacts with entities

the interactor builds a result model and give it back to the boundary/interface

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

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 Process

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?

MVC as an web architecture is messy

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.

the delivery mechanism is the web framework, and the boundary/interface, interactor and entities are out application

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

We add that use case to the Accountant module like so:

require 'caze'

module Accountant
  include Caze

  has_use_case :pay_invoice, PayInvoice
end
Enter fullscreen mode Exit fullscreen mode

Now we can say one thing that this application does which is pay invoices. Take a look on how the files are organized:

We have an use cases folder with an accountant.rb there and an accountant folder with the pay_invoice.rb there

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.

references

Top comments (4)

Collapse
 
pablomarti profile image
Pablo Martí

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).

Collapse
 
lucasprag profile image
lucasprag

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 =)

Collapse
 
jonathanbruno profile image
Jonathan Bruno

What is the difference between use-case object and service object?

Collapse
 
lucasprag profile image
lucasprag

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.