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
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
880
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
600
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
AIを駆使して新しい技術を効率的に理解する方法
nogu66
1
620
GraalVM Native Image トラブルシューティング機能の最新状況(2025年版)
ntt_dsol_java
0
140
モデル駆動設計をやってみよう Modeling Forum2025ワークショップ/Let’s Try Model-Driven Design
haru860
0
150
オフライン対応!Flutterアプリに全文検索エンジンを実装する @FlutterKaigi2025
itsmedreamwalker
2
210
What’s Fair is FAIR: A Decentralised Future for WordPress Distribution
rmccue
0
180
Building AI with AI
inesmontani
PRO
0
180
Chart.jsで長い項目を表示するときのハマりどころ
yumechi
0
110
「正規表現をつくる」をつくる / make "make regex"
makenowjust
1
420
例外処理を理解して、設計段階からエラーを見つけやすく、起こりにくく #phpconfuk
kajitack
12
6k
AI駆動開発ライフサイクル(AI-DLC)のホワイトペーパーを解説
swxhariu5
0
930
Java_プロセスのメモリ監視の落とし穴_NMT_で見抜けない_glibc_キャッシュ問題_.pdf
ntt_dsol_java
0
190
HTTPじゃ遅すぎる! SwitchBotを自作ハブで動かして学ぶBLE通信
occhi
0
250
Featured
See All Featured
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
253
22k
Scaling GitHub
holman
463
140k
Fashionably flexible responsive web design (full day workshop)
malarkey
407
66k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
15k
Being A Developer After 40
akosma
91
590k
Java REST API Framework Comparison - PWX 2021
mraible
34
9k
How to Think Like a Performance Engineer
csswizardry
28
2.3k
Context Engineering - Making Every Token Count
addyosmani
10
390
Rebuilding a faster, lazier Slack
samanthasiow
84
9.3k
The Cult of Friendly URLs
andyhume
79
6.7k
Writing Fast Ruby
sferik
630
62k
The Power of CSS Pseudo Elements
geoffreycrofte
80
6.1k
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