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
alien-signals と自作 OSS で実現する フレームワーク非依存な ロジック共通化の探求 / Exploring Framework-Agnostic Logic Sharing with alien-signals and Custom OSS
aoseyuu
3
5.9k
開発生産性が組織文化になるまでの軌跡
tonegawa07
0
140
競馬で学ぶ機械学習の基本と実践 / Machine Learning with Horse Racing
shoheimitani
0
110
AsyncSequenceとAsyncStreamのプロポーザルを全部読む!!
s_shimotori
1
270
アーキテクチャと考える迷子にならない開発者テスト
irof
2
120
ビルドプロセスをデバッグしよう!
yt8492
0
280
FlutterKaigi 2025 システム裏側
yumnumm
0
700
なぜ強調表示できず ** が表示されるのか — Perlで始まったMarkdownの歴史と日本語文書における課題
kwahiro
8
4.7k
Bakuraku E2E Scenario Test System Architecture #bakuraku_qa_study
teyamagu
PRO
0
670
CSC305 Lecture 15
javiergs
PRO
0
190
AI時代に必須!状況言語化スキル / ai-context-verbalization
minodriven
3
370
ネストしたdata classの面倒な更新にさようなら!Lensを作って理解するArrowのOpticsの世界
shiita0903
1
290
Featured
See All Featured
Testing 201, or: Great Expectations
jmmastey
46
7.8k
Facilitating Awesome Meetings
lara
57
6.6k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.2k
How To Stay Up To Date on Web Technology
chriscoyier
791
250k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
231
22k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
Build your cross-platform service in a week with App Engine
jlugia
234
18k
Unsuck your backbone
ammeep
671
58k
Producing Creativity
orderedlist
PRO
348
40k
Side Projects
sachag
455
43k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.3k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.6k
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