I found some interesting references during my search for alternatives to the Rails Way style. At that period, I was convinced that DDD practices can help myself in someway.
Each reference below influenced me a lot, and I strongly recommend you to check it out later.
- Architecture The Lost Years by Robert Martin (Uncle Bob)
- Decoupling from Rails by Jim Weirich
- Hexagonal Architecture For Rails Developers by Victor Savkin
- Building Rich Domain Models in Rails Separating Persistence by Victor Savkin
- DDD For Rails by Victor Savkin
Architecture The Lost Years
Uncle Bob starts this presentation, showing a picture of a Rails top level directory structure. And after that, he asks the question:
"What does this application do?"
This simple question, had a great objective in his presentation. The objective was to show the lack of architectural appeal to application's intentions. That's because we can't find a answer to that question, just looking to the top level application's directory structure. There is no clue about what this application does. And in general to find out that, we need to start checking out the application's controllers.
Architeture is about intent -- [Robert Martin]
But he alert us, this is not exclusive problem of Web Applications. To illustrate that he shows a picture below of a book from 1993, in 1993 desktop applications are in the main stream, which addresses this issue suggesting the use case approach, as a tool to reveal application's intents.
Based on this, Unbcle Bob, suggests the use of a new object to represent the use case. This object will contain the specific business rules, and in his solution he called this Interactor. He also suggests a entity object decoupled from Rails, in other words, PORO(Plain Old Ruby Objects) entities, what at that time was a dream to me. And I confess that I have tried to do that, once or twice, but I was not well, dealing with the object relationships. Anyway, not took too long to understand his intent. He's intention was to decouple the application from his framework (delivery mechanism). And to achieve that, he suggests the use of one more object, which he called Boundaries. The role of this object is to deal with data, in his example it deals with data that comes from, and goes to Framework (Rails). The picture below was extracted from his the presentation, and shows these "new objects/layers" in use:
Example
Let's use a agenda system, that allows users to schedule appointments, and see appointments for a choosen date. Using Uncle Bob's apporach we'll have 2 interactors like:
- schedule_appointment.rb
- list_appointments_from_date.rb
Look, the Interactor's names express what they do. So maybe now it'll be easier to answer the question: "What does this application do?" ;).
Show me the code!
Unfortanlly, Uncle Bob didn't showed any code in order to ilustrate his approach.
But I'll do that, and I still use the names Interactors and Boundaries at this momement, but latelly we'll talk more about these names.
For this example think in a Rails Controller as the Delivery Mechanism and a regular ActiveRecord as a Entity.
Interactor's Example
I used the Command Pattern - GoF to implement the interactors.
Let's take a look on a simple implementation for ScheduleAppointement Interactor.
class ScheduleAppointment
def initialize(starts_at: nil, ends_at: nil, desription: nil )
raise ArugmentError unless starts_at && ends_at
@starts_at = starts_at
@ends_at = ends_at
@description = description
end
def execute
Appointment.create({
start_date: @starts_at,
end_date: @ends_at,
description: @description
})
end
end
This Interactor encapsules the logic to create a new appointment. And now, if we want to add a new bussines rule, to ensure that will not exists two appointments in the same time interval for example. We just need to change the implementation of SheculeAppointment#execute.
Note that it's a good alterantive to our previous choice's: Fat Models or Fat Controllers.
Boundaries Example
Folowing the previous agenda system example. Where we can put or use Boundaries?
Well, keeping in mind that purpose of this aproach is to decouple the system from his framework. The boudaries will be used to catch data from a Rail's Controller, validate, and transform them if necessary, before pass it to a interactor object.
So think in a controller called AppointmentController with a create action.
class AppointmentController < ApplicationController
def create
new_appointment_boundary = NewAppointmentBoundary.new(params[:appointment])
if new_appointment_boundary.valid?
interactor = ScheduleAppointment.new(
:starts_at => new_appointment_boundary.start_date,
:ends_at => new_appointment_boundary.end_date,
:description => new_appointment_boundary.description
)
interactor.execute
redirect_to :index
else
@appointment = new_appointment_boundary
render :new
end
end
end
And a Boundary implementation like that:
class NewAppointmentBoundary
include ActiveAttr::Model
attribute :start_date
attribute :end_date
validates :start_date presence: true
validates :end_date presence: true
end
PS: ActiveAttr is a usefull gem, that help us to easy create validations in PORO.
The ActiveAttr gem enables our boundary to behave like a regular model. So you can use them to plug in into form_helper or even on a simple_form easily. This way we decouple our Views (Delivery Mechanism) from our Models.
Now let's talk about these names: Interactors and Boundaries
About Interactor's
Answer it quickly:
Are the Interactors Use Cases implementations?
If your answer was yes, (and I supposed that was), Why not call them as uses cases?
So in my Hexagonal Rays approach I prefer to user the name use case instead of interactors.
About Boundaries
I thought that the name: Boundaries is to abstract and less informative.
Note that these type of object, has in general two applications:
- Retrieve (validating and transforming) data from user's inputs (Form's in general)
- Wraps and formatting some data to display (View objects like Presenters)
Based on this, I prefer to call my Boundaries as Forms when they are in situation #1, and as Presenters when they are in situation #2.
And yes, I had two new folders in my directory structure. I had one folder for my forms and another for my presenters.
Conclusions
The use of these techniques, (use cases, forms and presenters), introduces more layers in the system. These new layers allowed us to decouple, more, the application from his framework. And helped us with the common situation, to have to choose between the fat models or the fat controllers. Furthermore, now we are closest to the Layered Architecture - GRASP, described by Craig Larman in your book "Applying UML and Patters".
But we still use ActiveRecord to represent our models, and to manage our data.
With that in mind some issues remains unsolved, like for example:
1. Can we use PORO as Domain Objects?
2. Is possible to split data retrieve and persistence of the Model itself?
3. Can I protect or even hide some attributes of my Model?
I'll address these questions in my next post, showing references and my attempt to solve them.
Originally published at www.sagadoprogramador.com.br on March 11, 2015.