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
88
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Running Rings Around Rack
Clojure NYC
Alex Wheeler
November 16, 2017
More Decks by Alex Wheeler
See All by Alex Wheeler
Golang Concurrency
alexwheeler
0
93
Rewriting Rack: A Functional Approach
alexwheeler
2
160
Man Computer Symbiosis
alexwheeler
0
150
Flipper
alexwheeler
0
140
Transducers
alexwheeler
0
72
Character Encodings
alexwheeler
0
120
Featured
See All Featured
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
SEO for Brand Visibility & Recognition
aleyda
0
4.7k
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
450
Making Projects Easy
brettharned
120
6.7k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
25
2k
Navigating the Design Leadership Dip - Product Design Week Design Leaders+ Conference 2024
apolaine
1
390
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
2.3k
Producing Creativity
orderedlist
PRO
348
40k
Embracing the Ebb and Flow
colly
88
5.1k
Scaling GitHub
holman
464
140k
Being A Developer After 40
akosma
91
590k
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