Please notify me if...
-
I speak 2 fast
-
I should repeat anything
-
I should explain something better
-
You have any questions
I will be talking mostly in Ruby on Rails framework context but the same applies to any other
In the end, all of it is just Ruby
What's wrong with Rails the way it is?
The way Rails is being taught
- You need Rails to program in Ruby
- The Rails way is the best way
- Fat models
- Slim controllers
- Models to rule it all
- It's supposed to be object oriented programming - so let's make models validate themselfs
- States, everywhere states
- It can happen even if you don't know about it (callbacks)
You need Rails to program in Ruby
# Gemfile
source 'https://rubygems.org'
gem 'rails', '5.0.1'
%w(
rails
active_model/railtie
active_job/railtie
active_record/railtie
action_controller/railtie
action_mailer/railtie
action_view/railtie
sprockets/railtie
).each { |lib| require lib }
# Yeah now I can program...
@some_object.try :test
The Rails way is the best way
- It's blazing fast at the beginning
- Easy to add new features
- Just test prototypes and it's production ready
The Rails way is NOT the best way
- Business loves not only features
- But also predictability
- Monolith by design
- A lot of magic
- Model level associations and relations
- Non-trivial business logic in models, controlers, views and (horrible!) callbacks
Fat models
Fat models
- "Great" for everything
- Validations
- Presentation
- Relations
- Persistance
- Scopes
- Translations
- Callbacks
- Triggers
SRP as fu*k
Fat controllers
Fat controllers
- "Great" for everything
- Validations
- Presentation
- Relations
- Persistance
- Scopes
- Translations
- Callbacks
- Triggers
- Data filtering
- Authentication
- Permissions and policies
SRP as fu*k
Models don't care about context (unless explicitly given)
- Hard to build multi non-related model form
- Magic behind AR triggers weird things
- "Let's build in a way that will remove everything with a user" automatically
States, everywhere states
if @model.valid?
else
if @model.errors[:xyz].present?
else
end
end
if @model.status == 'available'
else
end
Callbacks
What we can do about it?
- Stop using Rails and use something else (LOL)
What we can do about it?
- Introduce some other layers (policies, services, presenters, validators, uploaders, jobs, etc)
- and... bind most of them in the controller context
What we can REALLY do about it?
- Data, Context, and Interaction (DCI)
- CQRS
- Event sourcing
- DDD practices
- Railway-oriented programming
Where do we start from? Trailblazer
What is Trailblazer?
Trailblazer gives you a high-level architecture for web applications. It extends the basic MVC pattern with new abstractions. Rock-solid conventions that go far beyond database table naming or route paths let you focus on your application code, minimize bugs and improve the maintainability.
What do I know about Trailblazer?
- I've used every major (and sometimes minor) version of Trailblazer
- Even read Apotonicks book about it (heavily outdated novadays :D)
- Developed and maintained multiple apps with Trailblazer
- Had to patch things up from time to time to cover non-std cases
What do I know about Trailblazer?
class OrganizationsController < BaseController
def index
respond_with Organizations::Index.call(params)
end
def new
respond_with Organizations::New.call(params)
end
def create
respond_with Organizations::Create.call(params)
end
end
What Trailblazer is not
- Tool that will converts shitty programmers into Ruby magicians
- The new and only proper way to do Ruby apps
- Tool for junior developers (at least until they understand some concepts)
- Tool that has 100% accurate always up2date documentation
- Silver bullet to automatically refactor your apps
How does it work?
- Operation - A service object implementation with functional flow control.
- Contract - Form objects to validate incoming data
- Policy - To authorize code execution per user
- Representers - For serializing and parsing API documents.
How does it work?
How does it work?
Railway-oriented programming
The flow pipetree is a mix of the Either monad and Railway-oriented programming
Example - operation
class Create < ApplicationOperation
contract Contracts::Create
step Containers::Model::Build(Repository)
step Containers::Contract::Build()
step Containers::Contract::Validate(key: :repository)
step Containers::Contract::Persist()
failure Containers::Error::Raise()
end
Example - AM based contract
class Create < ApplicationContract
property :id
property :slug
property :setup_state
validates :id,
presence: true,
length: { is: 36 }
validates :slug,
presence: true
validates_uniqueness_of :id,
case_sensitive: false
validates_uniqueness_of :slug,
case_sensitive: false
end
What do I need to use it?
- A bit of patience
- A bit of DDD and CQRA understanding
- Understanding that code duplication can be good
- 100% code coverage
Downsides
- Composition over inheritance
- Often more code (but no coupling)
- Some WIP gems under the hood
- No invocation standardization
- Requires a good understanding of Ruby to do non-standard stuff
- Can be replaced (with a custom wrapper) with set of Dry-gems
Downsides
- I still prefer Dry-transactions DSL for endpoint handling
- Low code quality of Tralblazer itself
- No upgrade docs between major versions
- Trailblazer internal documentation understandable (if any) only by Apotonick