Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
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
490
[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
850
Blending Functional and OO programming in Ruby
solnic
22
2.4k
Deep Dive Into ROM
solnic
7
1.1k
Clean Code Cowboy
solnic
4
1k
Convenience vs Simplicity
solnic
4
1.7k
Micro Libraries FTW
solnic
2
570
DataMapper 2 - an object mapping toolkit
solnic
6
1k
Beyond the ORM - RuLu Conf 2012
solnic
1
1.4k
Other Decks in Programming
See All in Programming
AI時代の開発者評価について
ayumuu
0
190
「理解」を重視したAI活用開発
fast_doctor
0
180
Enterprise Web App. Development (1): Build Tool Training Ver. 5
knakagawa
1
120
音声プラットフォームのアーキテクチャ変遷から学ぶ、クラウドネイティブなバッチ処理 (20250422_CNDS2025_Batch_Architecture)
thousanda
0
280
Empowering Developers with HTML-Aware ERB Tooling @ RubyKaigi 2025, Matsuyama, Ehime
marcoroth
2
780
Making TCPSocket.new "Happy"!
coe401_
1
1.9k
AIコーディングの理想と現実
tomohisa
29
34k
Road to RubyKaigi: Making Tinny Chiptunes with Ruby
makicamel
4
440
エンジニアが挑む、限界までの越境
nealle
1
270
The Evolution of the CRuby Build System
kateinoigakukun
0
720
Rollupのビルド時間高速化によるプレビュー表示速度改善とバンドラとASTを駆使したプロダクト開発の難しさ
plaidtech
PRO
1
180
GitHub Copilot for Azureを使い倒したい
ymd65536
1
180
Featured
See All Featured
Docker and Python
trallard
44
3.3k
Rebuilding a faster, lazier Slack
samanthasiow
80
8.9k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
357
30k
Rails Girls Zürich Keynote
gr2m
94
13k
Java REST API Framework Comparison - PWX 2021
mraible
30
8.5k
RailsConf 2023
tenderlove
30
1.1k
Designing for humans not robots
tammielis
252
25k
The Art of Programming - Codeland 2020
erikaheidi
53
13k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
26k
How STYLIGHT went responsive
nonsquared
99
5.5k
Stop Working from a Prison Cell
hatefulcrawdad
268
20k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.2k
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