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
Running Rings Around Rack
Search
Alex Wheeler
November 16, 2017
0
84
Running Rings Around Rack
Clojure NYC
Alex Wheeler
November 16, 2017
Tweet
Share
More Decks by Alex Wheeler
See All by Alex Wheeler
Golang Concurrency
alexwheeler
0
88
Rewriting Rack: A Functional Approach
alexwheeler
2
140
Man Computer Symbiosis
alexwheeler
0
120
Flipper
alexwheeler
0
130
Transducers
alexwheeler
0
66
Character Encodings
alexwheeler
0
120
Featured
See All Featured
Documentation Writing (for coders)
carmenintech
71
4.9k
Building Flexible Design Systems
yeseniaperezcruz
328
39k
Docker and Python
trallard
44
3.4k
Bootstrapping a Software Product
garrettdimon
PRO
307
110k
4 Signs Your Business is Dying
shpigford
183
22k
How to Ace a Technical Interview
jacobian
276
23k
RailsConf 2023
tenderlove
30
1.1k
A better future with KSS
kneath
239
17k
YesSQL, Process and Tooling at Scale
rocio
172
14k
Gamification - CAS2011
davidbonilla
81
5.3k
Unsuck your backbone
ammeep
671
58k
Become a Pro
speakerdeck
PRO
28
5.4k
Transcript
Running Rings Around Rack Alex Wheeler
• @askwheeler • github.com/alexwheeler
• value-oriented programming • web server interfaces Ring (clojure), Rack
(ruby) • middleware
Clojure data structures • immutable • read-able • manipulated via
interfaces
• manipulated via interfaces
• “It is better to have 100 functions operate on
one data structure than to have 10 functions operate on 10 data structures” - Alan Jay Perlis
• “It is better to have 100 functions operate on
one data abstraction than 10 functions on 10 data structures.” - Rich Hickey
• Collection • Sequence • Associative • Indexed • Stack
• Set • Sorted
• Collection • Associative
(conj [1 2] 3) => [1 2 3] (conj '(1
2) 3) => '(3 1 2) (conj #{1 2} 3) => #{1 3 2} (conj {1 2} {3 4}) => {1 2, 3 4}
(assoc {} :name "Alex" :city "NYC") => {:name "Alex" :city
"NYC"} (assoc [1 2] 2 3) => [1 2 3] (assoc nil :name "Alex") => {:name "Alex"}
(def company {:employee-count 24}) (def employees-per-company [1 20 500]) (update
company :employee-count inc) => {:employee-counts 25} (update employees-per-company 0 inc) => [2 20 500] (:employee-count company) => 24 (first employees-per-company) => 1
None
None
None
None
None
None
None
None
(defn handle [req])
["/features" "sort-by=recent" {:content-type "application/json"} ""]
{ :uri "/features" :query-string "sort-by=recent" :headers { "content-type" "application/json"} :body
"" }
"/featuressort-by=recent{:content-type \"application/json\"}\"\""
Request { :uri "/features" :query-string "sort-by=recent" :headers { "content-type" "application/json"}
:body "" } Response { :status 200 :headers {} :body {} }
(defn handler [request] {:status 200 :headers {"Content-Type" "text/json"} :body ""})
(run-jetty handler {:port 3000}) class App def call(env) ['200', {'Content-Type' => 'application/json'}, []] end end Rack::Handler::WEBrick.run App.new
None
None
(defn handler [req] { :status 200 :headers {} :body ""
}) (defn timestamp [handler] (fn [request] (handler (assoc request :time (java.util.Date.))))) (def app (timestamp handler)) (run-jetty app {:port 3000})
(defn handler [req] { :status 200 :headers {} :body ""
}) (defn remove-sensitive-data [handler] (fn [request] (handler (dissoc request :bank-number)))) (defn timestamp [handler] (fn [request] (handler (assoc request :time (java.util.Date.))))) (def app (remove-sensitive-data (timestamp handler)))
(def sensitive-data [:foo]) (apply dissoc {:foo "2342345"} sensitive-data) => {}
(def sensitive-data [:foo]) (apply dissoc {:foo "2342345"} sensitive-data) => {}
SENSITIVE_DATA = [:foo] {foo: 'bar'}.delete_if {|k, v| SENSITIVE_DATA.include?(k)} => {}
(def sensitive-data [“bank-account-num“ “social-security”]) (defn remove-sensitive-data [handler] (fn [request] (handler
(update-in request [:params] #(apply dissoc % sensitive-data)))))
class RemoveSensitiveData SENSITIVE_DATA = [‘bank-account-num’, ‘social-security’] def initialize(app) @app =
app end def call(env) req = Rack::Request.new(env) req['params'].delete_if { |k, v| SENSITIVE_DATA.include?(k) } @app.call(env) end end
req['params'] => {bank-account-num: ‘1234324234’} req['params'].class => Hash
None
def call(env) req = Rack::Request.new(env) SENSITIVE_DATA.each do |bad_word| req.delete_param(bad_word) end
end
req.delete_param(‘social-security') { email: ‘
[email protected]
’ }.delete('email') bank_account.remove(‘bank-account-number‘)
None
(dissoc request :name) (dissoc person :email) (dissoc bank-account :social-security-number)
def call(env) req = Rack::Request.new(env) req['params'].delete_if { |k, v| BAD_WORDS.include?(v)
} Analytics.update(req) Logging.log(req) Database.update(req) @app.call(env) end
def call(env) req = Rack::Request.new(env) Analytics.update(req) Logging.log(req) Database.update(req) req['params'].delete_if {
|k, v| BAD_WORDS.include?(v) } @app.call(env) end
(defn remove-bad-words [handler] (fn [request] (analytics/update request) (logging/log request) (database/update
request) (handler (update-in request [:params] #(apply dissoc % bad-words)))))
app = Rack::Builder.new do use RemoveSensitiveData use AddTimestamp use LogAnalytics
use WriteDatabase run App.new end run app
(def app(m1(m2(m3(m4(m5(m6(m7 handler)))))))) (run-jetty app {:port 3000})
(def app (-> handler (remove-sensitive-data) (add-timestamp) (log-analytics) (write-database) (add-timestamp) (wrap-params)))
(run-jetty app {:port 3000})
Thanks! • alexwheeler.io • @askwheeler • github.com/alexwheeler