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
480
[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
840
Blending Functional and OO programming in Ruby
solnic
22
2.4k
Deep Dive Into ROM
solnic
7
1.1k
Clean Code Cowboy
solnic
4
970
Convenience vs Simplicity
solnic
4
1.7k
Micro Libraries FTW
solnic
2
540
DataMapper 2 - an object mapping toolkit
solnic
6
1k
Beyond the ORM - RuLu Conf 2012
solnic
1
1.3k
Other Decks in Programming
See All in Programming
生成AIでGitHubソースコード取得して仕様書を作成
shukob
0
150
menu基盤チームによるGoogle Cloudの活用事例~Application Integration, Cloud Tasks編~
yoshifumi_ishikura
0
110
数十万行のプロジェクトを Scala 2から3に完全移行した
xuwei_k
0
260
今年のアップデートで振り返るCDKセキュリティのシフトレフト/2024-cdk-security-shift-left
tomoki10
0
190
fs2-io を試してたらバグを見つけて直した話
chencmd
0
220
バグを見つけた?それAppleに直してもらおう!
uetyo
0
170
SymfonyCon Vienna 2025: Twig, still relevant in 2025?
fabpot
3
1.2k
Fibonacci Function Gallery - Part 1
philipschwarz
PRO
0
200
KubeCon + CloudNativeCon NA 2024 Overviewat Kubernetes Meetup Tokyo #68 / amsy810_k8sjp68
masayaaoyama
0
250
17年周年のWebアプリケーションにTanStack Queryを導入する / Implementing TanStack Query in a 17th Anniversary Web Application
saitolume
0
250
N.E.X.T LEVEL
pluu
2
300
今年一番支援させていただいたのは認証系サービスでした
satoshi256kbyte
1
250
Featured
See All Featured
Intergalactic Javascript Robots from Outer Space
tanoku
270
27k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
229
52k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
5
440
Fontdeck: Realign not Redesign
paulrobertlloyd
82
5.3k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Building an army of robots
kneath
302
44k
The Art of Programming - Codeland 2020
erikaheidi
53
13k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
365
25k
Scaling GitHub
holman
458
140k
RailsConf 2023
tenderlove
29
940
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
10
810
Building a Scalable Design System with Sketch
lauravandoore
460
33k
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