Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
[KRUG] Architecture. The reclaimed years.
Search
Piotr Solnica
March 20, 2018
Programming
2
510
[KRUG] Architecture. The reclaimed years.
A short intro into clean architecture for Ruby apps based on dry-system gem.
Piotr Solnica
March 20, 2018
Tweet
Share
More Decks by Piotr Solnica
See All by Piotr Solnica
rom-rb 4.0 - Moscow, RailsClub 2017
solnic
3
1.3k
rom 4.0 is coming
solnic
3
890
Blending Functional and OO programming in Ruby
solnic
22
2.5k
Deep Dive Into ROM
solnic
7
1.2k
Clean Code Cowboy
solnic
4
1k
Convenience vs Simplicity
solnic
4
1.8k
Micro Libraries FTW
solnic
2
610
DataMapper 2 - an object mapping toolkit
solnic
6
1.1k
Beyond the ORM - RuLu Conf 2012
solnic
1
1.5k
Other Decks in Programming
See All in Programming
ハイパーメディア駆動アプリケーションとIslandアーキテクチャ: htmxによるWebアプリケーション開発と動的UIの局所的適用
nowaki28
0
330
俺流レスポンシブコーディング 2025
tak_dcxi
13
7.7k
社内オペレーション改善のためのTypeScript / TSKaigi Hokuriku 2025
dachi023
1
470
ソフトウェア設計の課題・原則・実践技法
masuda220
PRO
24
21k
CSC305 Lecture 15
javiergs
PRO
0
240
Full-Cycle Reactivity in Angular: SignalStore mit Signal Forms und Resources
manfredsteyer
PRO
0
180
テストやOSS開発に役立つSetup PHP Action
matsuo_atsushi
0
140
Rediscover the Console - SymfonyCon Amsterdam 2025
chalasr
2
140
令和最新版Android Studioで化石デバイス向けアプリを作る
arkw
0
110
目的で駆動する、AI時代のアーキテクチャ設計 / purpose-driven-architecture
minodriven
11
3.9k
TypeScriptで設計する 堅牢さとUXを両立した非同期ワークフローの実現
moeka__c
6
2.9k
ViewファーストなRailsアプリ開発のたのしさ
sugiwe
0
390
Featured
See All Featured
Learning to Love Humans: Emotional Interface Design
aarron
274
41k
The Pragmatic Product Professional
lauravandoore
37
7.1k
Building a Scalable Design System with Sketch
lauravandoore
463
34k
4 Signs Your Business is Dying
shpigford
186
22k
[RailsConf 2023] Rails as a piece of cake
palkan
58
6.1k
Product Roadmaps are Hard
iamctodd
PRO
55
12k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
253
22k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
3.8k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.5k
Faster Mobile Websites
deanohume
310
31k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
8.3k
Music & Morning Musume
bryan
46
7k
Transcript
ARCHITECTURE THE RECLAIMED YEARS 1
PIOTR SOLNICA > rom-rb creator > dry-rb co-founder > github.com/solnic
> @_solnic_ > solnic.eu 2
ARCHITECTURE THE LOST YEARS 3
"The web is a delivery mechanism, the web is a
detail" — Uncle Bob 4
"The top level architecture of my rails application, did not
scream its intent at you, it screamed the framework at you, it screamed Rails at you" — Uncle Bob 5
"It’s good for DHH, not so good for you" —
Uncle Bob 6
"Database is a detail" — Uncle Bob 7
RAILS IS YOUR ARCHITECTURE EMBRACE IT OR LEAVE IT 8
FAST TESTS 9
> Boundaries > Data structures > Dependencies 10
BOUNDARIES 11
12
13
DATA STRUCTURES 14
> HTTP Request > Application response > View-specific data >
Domain-specific data 15
DEPENDENCIES 16
17
18
class CreateUser end 19
class CreateUser attr_reader :user_repo, :validator, :mailer def initialize(user_repo:, validator:, mailer:)
@user_repo = user_repo @validator = validator @mailer = mailer end end 20
> Classes > Modules > Singleton methods 21
PROBLEM WITH CLASSES 22
CLASSES IN RUBY ARE GLOBAL, STATEFUL, MUTABLE VARIABLES 23
24
> Minimize state in classes > Don't rely on class
state at runtime > Don't rely on monkey-patching 25
PROBLEM WITH MODULES 26
MODULES IN RUBY IS A FORM OF MULTIPLE INHERITANCE 27
IT'S HARD TO ACHIEVE A COHERENT SYSTEM WHEN MODULES ARE
USED EXTENSIVELY 28
FAVOR COMPOSITION OVER INHERITANCE 29
SINGLETON METHODS 30
> Using singleton methods couple your code to class/ module
constants > Singleton methods easily lead to awkward, procedural code 31
> Minimize usage of singleton methods, they are only good
as "builder" methods, or top-level configuration APIs > Don't use them at runtime, objects are 10 x better and more flexible 32
DRY-SYSTEM 33
> An architecture for Ruby applications > Based heavily on
lightweight dependency injection > Allows you to compose an application from isolated components 34
35
RUBY APPLICATION COMES FIRST 36
app |-lib |-system |-spec 37
app |-lib |-system |- app.rb <= your app |- import.rb
<= DI extension |-spec 38
app |-lib |- users/create_user.rb |- repos/user_repo.rb |-system |-spec 39
# app/system/app.rb require 'dry/system/container' class App < Dry::System::Container configure do
|config| config.auto_register = %w(lib) end load_paths! 'lib', 'system' end 40
SIMPLE OBJECT COMPOSITION require 'import' module Users class CreateUser include
Import['repos.user_repo'] def call(params) user_repo.create(params) end end end 41
YOUR APP IS THE ENTRY POINT TO YOUR SYSTEM ∞
pry -r ./system/app [1] pry(main)> App['users.create_user'] => #<Users::CreateUser:0x00007ff3a1b2e520..> [2] pry(main)> App['repos.user_repo'] => #<Repos::UserRepo:0x00007ff3a11b0890..> 42
43
TESTING IN ISOLATION require 'users/create_user' RSpec.describe Users::CreateUser do subject(:create_user) do
Users::CreateUser.new end describe '#call' do it 'returns created user' do user = create_user.call(id: 1, name: 'Jane') expect(user).to eql(id: 1, name: 'Jane') end end end 44
USER INTERFACE AS AN EXTENSION 45
> Web UI based on HTML/CSS/JS > JSON API >
CLI interface > ... 46
LET'S ADD A WEB INTERFACE ON TOP USING RODA 47
# system/web.rb require_relative 'app' require 'roda' class Web < Roda
opts[:api] = App plugin :json route do |r| r.post 'users' do api['users.create_user'].call(r[:user]) end end def api self.class.opts[:api] end end 48
∞ curl -X POST http://localhost:9292/users -d "user[id]=1&user[name]=Jane" {"id":"1","name":"Jane"} 49
LET'S ADD A CLI ON TOP USING HANAMI-CLI 50
#!/usr/bin/env ruby require "bundler/setup" require "hanami/cli" require "json" require_relative '../system/boot'
module Commands extend Hanami::CLI::Registry class CreateUser < Command desc "Creates a user" argument :user, desc: "User data" def call(user: nil, **) params = JSON.parse(user) output = App['users.create_user'].call(params) puts "Created #{output.inspect}" end end register "create_user", CreateUser end Hanami::CLI.new(Commands).call 51
∞ bin/app create_user '{"id":1,"name":"Jane"}' Created {"id"=>1, "name"=>"Jane"} 52
DID YOU NOTICE THE BOUNDARIES HERE? 53
WEB INPUT AS PRE-PROCESSED RACK PARAMS r[:user] # { "id"
=> 1, "name" => "Jane" } CLI INPUT AS A PLAIN JSON STRING '{"id":1,"name":"Jane"}' 54
THIS IS NOT A CONCERN OF YOUR APPLICATION 55
YOUR APPLICATION IS AN API WITH OBJECTS ACCEPTING SPECIFIC DATA
STRUCTURES AS INPUT 56
# user creation end-point App['users.create_user'] # expected data structure schema
{ id: Integer, name: String } 57
58
CLEAN ARCHITECTURE > Ruby app comes first > Respecting boundaries
> Object composition > User interface as an extension of your Ruby app 59
THANK YOU 60
MORE THINGS TO CHECK OUT > Boundaries talk by Gary
Bernhardt > dry-system on GitHub > sample app from slides on GitHub 61