SlideShare a Scribd company logo
Reitit
The Ancient Art of Data-Driven
tommi@metosin.fi
Clojure/North
Me(tosin)
Sinclair Spectrum in 1983
...
Co-founded Metosin 2012
20 developers, Tampere & Helsinki / Finland
Projects, Startups and Design
FP, mostly Clojure/Script
https://github.com/metosin
ClojuTRE 2019 (8th)
September 26-27th 2019, Helsinki
FP & Clojure, 300++ on last two years
Basic flights & hotels for speakers
Free Student & Diversity tickets
Call For Speakers Open
https://clojutre.org
This talk
Routing and dispatching
Reitit, the library
Reitit, the framework
Performance
Takeaways
Routing in Clojure/Script
Lot's of options: Ataraxy, Bide, Bidi, Compojure,
Keechma, Pedestal, Silk, Secretary, ...
Something we need in mostly all the (web)apps
Mapping from path -> match
Mapping from name -> match (reverse routing)
HTTP (both server & browser)
Messaging
Others
Dispatching in Clojure/Script
Here: composing end executing functionality in
the request-response pipeline
Common patterns
Middleware (Ring, nREPL, ..)
Interceptors (Pedestal, Re-Frame, ..)
Controllers (Keechma)
Promises
Sync & Async
New Routing Library?
More data-driven
Leveraging clojure.spec
Built for performance
Friendly & explicit API
Reach: browser, JVM, Node, Others
Both routing and dispatching
The Yellow (Hiccup) Castle of Routing?
Reitit, the Library
reitit-core
(c) https://www.artstation.com/kinixuki
Basics
reitit.core/router to create a router
Matching by path or by name (reverse routing)
Functions to browse the route trees
(defprotocol Router
(router-name [this])
(routes [this])
(compiled-routes [this])
(options [this])
(route-names [this])
(match-by-path [this path])
(match-by-name [this name] [this name path-params]))
(require '[reitit.core :as r])
(def router
(r/router
[["/ping" ::ping]
["/users/:id" ::user]]))
(r/match-by-path router "/ping")
;#Match{:template "/ping"
; :data {:name :user/ping}
; :result nil
; :path-params {}
; :path "/ping"}
(r/match-by-name router ::ping)
;#Match{:template "/ping"
; :data {:name :user/ping}
; :result nil
; :path-params {}
; :path "/ping"}
Route Syntax (~hiccup)
Paths are concatenated, route data meta-merged
(r/router
["/api" {:interceptors [::api]}
["/ping" ::ping]
["/admin" {:roles #{:admin}}
["/users" ::users]
["/db" {:interceptors [::db]
:roles ^:replace #{:db-admin}}]]]))
(r/router
[["/api/ping" {:interceptors [::api]
:name ::ping}]
["/api/admin/users" {:interceptors [::api]
:roles #{:admin}
:name ::users}]
["/api/admin/db" {:interceptors [::api ::db]
:roles #{:db-admin}}]])
Programming routes
nil s and nested sequences are flattened
(defn router [dev?]
(r/router
[["/command/"
(for [mutation [:olipa :kerran :avaruus]]
[(name mutation) mutation])]
(if dev? ["/dev-tools" :dev-tools])]))
(-> (router true) (r/routes))
;[["/command/olipa" {:name :olipa}]
; ["/command/kerran" {:name :kerran}]
; ["/command/avaruus" {:name :avaruus}]
; ["/dev-tools" {:name :dev-tools}]]
Composing routes
It's just data, merge trees
Single route tree allows to optimize the whole
Merging & nesting routers
Empty route fragments, e.g. :<> in Reagent
(-> (r/router
[["" {:interceptors [::cors]}
["/api" ::api]
["/ipa" ::ipa]]
["/ping" ::ping]])
(r/routes))
;[["/api" {:interceptors [::cors], :name ::api}]
; ["/ipa" {:interceptors [::cors], :name ::ipa}]
; ["/ping" {:name ::ping}]]
Route Conflict Resolution
Multiple sources (code/EDN/DB) for routes?
Are all routes still reachable?
Reitit fails-fast by default on path & name conflicts
(require '[reitit.core :as r])
(require '[reitit.dev.pretty :as pretty])
(r/router
[["/ping"]
["/:user-id/orders"]
["/bulk/:bulk-id"]
["/public/*path"]
["/:version/status"]]
{:exception pretty/exception})
Route Conflict Error
Community-driven Clojure
error formatter?
(c) https://www.artstation.com/kinixuki
What is in the route data?
Can be anything, the Router doesn't care.
Returned on successful Match
Can be queried from a Router
Build your own interpreters for the data
A Route First Architecture: Match -> Data -> React
(r/match-by-path router "/api/admin/db")
;#Match{:template "/api/admin/db",
; :data {:interceptors [::api ::db]
; :roles #{:db-admin}},
; :result nil,
; :path-params {},
; :path "/api/admin/db"}
Example use cases
Authorization via :roles
Frontend components via :view
Dispatching via :middleware and :interceptors
Stateful dispatching with :controllers
Coercion via :parameters and :coercion
Run requests in a server io-pool with :server/io
Deploy endpoints separately to :server/target
Frontend Example
https://router.vuejs.org/guide/essentials/nested-routes.html
(require '[reitit.frontend :as rf])
(require '[reitit.frontend.easy :as rfe])
(defn frontpage-view [match] ...)
(defn topics-view [match] ... (rf/nested-view match)
(defn topic-view [match] ...)
(def router
(rf/router
[["/" {:name :frontpage
:views [frontpage-view]}]
["/topics"
{:controllers [load-topics]
:views [topics-view]}
["" {:name :topics}]
["/:id"
{:name :topic
:parameters {:path {:id int?}}
:controllers [load-topic]
:views [topic-view]}]]]))
(rfe/start! router ...)
Route Data Validation
Route Data can be anything -> data spaghetti?
Leverage clojure.spec at router creation
Define and enforce route data specs
Fail-fast, missing, extra or misspelled keys
With help of spec-tools and spell-spec
Closed specs coming to Spec2 (TBD)
Invalid Route Data Example
(require '[reitit.spec :as spec])
(require '[clojure.spec.alpha :as s])
(s/def ::role #{:admin :user})
(s/def ::roles (s/coll-of ::role :into #{}))
(r/router
["/api/admin" {::roles #{:adminz}}]
{:validate spec/validate
:exception pretty/exception})
Route Data Error
Closed Functional Specs
Failing Fast
During static analysis (e.g. linter)
At compile-time (macros, defs)
At development-time (schema/spec annos)
At creation-time
At runtime
At runtime special condition
(c) https://www.artstation.com/kinixuki
Coercion (Déjà-vu)
Coercion is a process of transforming values between
domain (e.g. JSON->EDN, String->EDN)
Route data keys :parameters & :coercion
Utilities to apply coercion in reitit.coercion
Implementation for Schema & clojure.spec <-- !!!
(defprotocol Coercion
"Pluggable coercion protocol"
(-get-name [this])
(-get-options [this])
(-get-apidocs [this specification data])
(-compile-model [this model name])
(-open-model [this model])
(-encode-error [this error])
(-request-coercer [this type model])
(-response-coercer [this model]))
Reitit, the Framework
(c) https://www.artstation.com/kinixuki
Framework
You call the library, but the framework calls you
Reitit ships with multiple Routing Frameworks
reitit-ring Middleware-dispatch for Ring
reitit-http Async Interceptor-dispatch for http
reitit-pedestal Reitit Pedestal
reitit-frontend (Keechma-style) Controllers,
History & Fragment Router, helpers
metosin/reitit-ring
(c) https://www.artstation.com/kinixuki
Ring Routing
A separate lightweight module
Routing based on path and :request-method
Adds support for :middleware dispatch
chain is executed after a match
ring-handler to create a ring-compatible handler
Supports Both sync & async
Supports Both JVM & Node
No magic, no default middleware
Ring Application
(require '[reitit.ring :as ring])
(def app
(ring/ring-handler
(ring/router
["/api" {:middleware [wrap-api wrap-roles]}
["/ping" {:get ping-handler]}
["/users" {:middleware [db-middleware]
:roles #{:admin} ;; who reads this?
:get get-users
:post add-user}]])
(ring/create-default-handler)))
(app {:uri "/api/ping" :request-method :get})
; {:status 200, :body "pong"}
Accessing route data
Match and Router are injected into the request
Components can read these at request time and
do what ever they want, "Ad-hoc extensions"
Pattern used in Kekkonen and in Yada
(defn wrap-roles [handler]
;; roles injected via session-middleware
(fn [{:keys [roles] :as request}]
;; read the route-data at request-time
(let [required (-> request (ring/get-match) :data :roles)]
(if (and (seq required)
(not (set/subset? required roles)))
{:status 403, :body "forbidden"}
(handler request)))))
Middleware as data
Ring middleware are opaque functions
Reitit adds a first class values, Middleware records
Recursive IntoMiddleware protocol to expand to
Attach documentation, specs, requirements, ...
Can be used in place of middleware functions
Zero runtime penalty
(defn roles-middleware []
{:name ::roles-middleware
:description "Middleware to enforce roles"
:requires #{::session-middleware}
:spec (s/keys :opt-un [::roles])
:wrap wrap-roles})
Compiling middleware
Each middleware knows the endpoint it's mounted to
We can pass the route data in at router creation time
Big win for optimizing chains
(def roles-middleware
{:name ::roles-middleware
:description "Middleware to enforce roles"
:requires #{::session-middleware}
:spec (s/keys :opt-un [::roles])
:compile (fn [{required :roles} _]
;; unmount if there are no roles required
(if (seq required)
(fn [handler]
(fn [{:keys [roles] :as request}]
(if (not (set/subset? required roles))
{:status 403, :body "forbidden"}
(handler request))))))})
Partial Specs Example
"All routes under /account should require a role"
;; look ma, not part of request processing!
(def roles-defined
{:name ::roles-defined
:description "requires a ::role for the routes"
:spec (s/keys :req-un [::roles])})
["/api" {:middleware [roles-middleware]} ;; behavior
["/ping"] ;; unmounted
["/account" {:middleware [roles-defined]} ;; :roles mandatory
["/admin" {:roles #{:admin}}] ;; ok
["/user" {:roles #{:user}}] ;; ok
["/manager"]]] ;; fail!
Middleware chain as data
Each endpoint has it's own vector of middleware
Documents of what is in the chain
Chain can be manipulated at router creation time
Reordering
Completing
Interleaving
Interleaving a request diff console printer:
reitit.ring.middleware.dev/print-request-diffs
Data definitions (as data)
The core coercion for all (OpenAPI) paramerer &
response types ( :query , :body , header , :path etc)
Separerate Middleware to apply request & response
coercion and format coercion errors
Separate modules, spec coercion via spec-tools
["/plus/:y"
{:get {:parameters {:query {:x int?},
:path {:y int?}}
:responses {200 {:body {:total pos-int?}}}
:handler (fn [{:keys [parameters]}]
;; parameters are coerced
(let [x (-> parameters :query :x)
y (-> parameters :path :y)]
{:status 200
:body {:total (+ x y)}}))}}]
Everything as data.
(c) https://www.artstation.com/kinixuki
Interceptors
metosin/reitit-http
e.g. going async
(c) https://www.artstation.com/kinixuki
Going Async
Interceptors are much better fit for async
reitit-http uses the Interceptor model
Requires an Interceptor Executor
reitit-pedestal or reitit-sieppari
Sieppari supports core.async , Manifold and Promesa
Pedestal is more proven, supports core.async
Target Both JVM & Node (via Sieppari)
Http Application
(require '[reitit.http :as http])
(require '[reitit.interceptor.sieppari :as sieppari]
(def app
(http/ring-handler
(http/router
["/api" {:interceptors [api-interceptor
roles-interceptor]}
["/ping" ping-handler]
["/users" {:interceptors [db-interceptor]
:roles #{:admin}
:get get-users
:post add-user}]])
(ring/create-default-handler)
{:executor sieppari/executor}))
(app {:uri "/api/ping" :request-method :get})
; {:status 200, :body "pong"}
Example Interceptor
(defn coerce-request-interceptor
"Interceptor for pluggable request coercion.
Expects a :coercion of type `reitit.coercion/Coercion`
and :parameters from route data, otherwise does not mount."
[]
{:name ::coerce-request
:spec ::rs/parameters
:compile (fn [{:keys [coercion parameters]} opts]
(cond
;; no coercion, skip
(not coercion) nil
;; just coercion, don't mount
(not parameters) {}
;; mount
:else
(let [coercers (coercion/request-coercers coercion parameters opts)]
{:enter (fn [ctx]
(let [request (:request ctx)
coerced (coercion/coerce-request coercers request)
request (impl/fast-assoc request :parameters coerced)]
(assoc ctx :request request)))})))})
DEMO
(c) https://www.artstation.com/kinixuki
Route-driven frameworks
Routing and dispatching is separated, middleware (or
interceptors) are applied only after a match
Each endpoint has a unique dispatch chain
Each component can be compiled and optimized against
the endpoint at creation time
Components can define partial route data specs that
only effect the routes they are mounted to.
We get both Performance & Correctness
... this is Kinda Awesome.
(c) https://www.artstation.com/kinixuki
Performance
(c) https://www.artstation.com/kinixuki
Performance
how can we make compojure-api faster?
we moved from Clojure to GO because of perf
How fast are the current Clojure libraries?
How fast can we go with Java/Clojure?
Measuring Performance
Always measure
Both micro & macro benchmarks
In the end, the order of magnitude matters
Lot's of good tools, some favourites:
clojure.core/time
criterium
com.clojure-goes-fast/clj-async-profiler
com.clojure-goes-fast/clj-java-decompiler
https://github.com/wg/wrk
(def defaults {:keywords? true})
(time
(dotimes [_ 10000]
(merge defaults {})))
; "Elapsed time: 4.413803 msecs"
(require '[criterium.core :as cc])
(cc/quick-bench
(merge defaults {}))
; Evaluation count : 2691372 in 6 samples of 448562 calls.
; Execution time mean : 230.346208 ns
; Execution time std-deviation : 10.355077 ns
; Execution time lower quantile : 221.101397 ns ( 2.5%)
; Execution time upper quantile : 245.331388 ns (97.5%)
; Overhead used : 1.881561 ns
(require '[clj-async-profiler.core :as prof])
(prof/serve-files 8080) ;; serve the svgs here
(prof/profile
(dotimes [_ 40000000] ;; ~10sec period
(merge defaults {})))
Reitit performance
Designed group up to be performant
Perf suite to see how performance evolves
Measured against Clojure/JavaScript/GO Routers
Performance toolbox:
Optimized routing algorithms
Separation of creation & request time
The Usual Suspects
Routing algorithms
When a router is created, route tree is inspected
and a best possible routing algorith is chosen
No regexps, use linear-router as a last effort
lookup-router , single-static-path-router
trie-router , linear-router
mixed-router , quarantine-router
trie-router
For non-conflicting trees with wildcards
First insert data into Trie AST, then compile it into
fast lookup functions using a TrieCompiler
On JVM, backed by a fast Java-based Radix-trie
(require '[reitit.trie :as trie])
(-> [["/v2/whoami" 1]
["/v2/users/:user-id/datasets" 2]
["/v2/public/projects/:project-id/datasets" 3]
["/v1/public/topics/:topic" 4]
["/v1/users/:user-id/orgs/:org-id" 5]
["/v1/search/topics/:term" 6]
["/v1/users/:user-id/invitations" 7]
["/v1/users/:user-id/topics" 9]
["/v1/users/:user-id/bookmarks/followers" 10]
["/v2/datasets/:dataset-id" 11]
["/v1/orgs/:org-id/usage-stats" 12]
["/v1/orgs/:org-id/devices/:client-id" 13]
["/v1/messages/user/:user-id" 14]
["/v1/users/:user-id/devices" 15]
["/v1/public/users/:user-id" 16]
["/v1/orgs/:org-id/errors" 17]
["/v1/public/orgs/:org-id" 18]
["/v1/orgs/:org-id/invitations" 19]
["/v1/users/:user-id/device-errors" 22]]
(trie/insert)
(trie/compile)
(trie/pretty))
["/v"
[["1/"
[["users/" [:user-id ["/" [["device" [["-errors" 22]
["s" 15]]]
["orgs/" [:org-id 5]]
["bookmarks/followers" 10]
["invitations" 7]
["topics" 9]]]]]
["orgs/" [:org-id ["/" [["devices/" [:client-id 13]]
["usage-stats" 12]
["invitations" 19]
["errors" 17]]]]]
["public/" [["topics/" [:topic 4]]
["users/" [:user-id 16]]
["orgs/" [:org-id 18]]]]
["search/topics/" [:term 6]]
["messages/user/" [:user-id 14]]]]
["2/"
[["public/projects/" [:project-id ["/datasets" 3]]]
["users/" [:user-id ["/datasets" 2]]]
["datasets/" [:dataset-id 11]]
["whoami" 1]]]]]
Optimization Log
160ns (httprouter/GO)
3000ns (clojure, interpreted)
... (clueless poking)
990ns (clojure-trie)
830ns (faster decode params)
560ns (java-segment-router)
490ns (java-segment-router, no injects)
440ns (java-segment-router, no injects, single-wild-optimization)
305ns (trie-router, no injects)
281ns (trie-router, no injects, optimized)
277ns (trie-router, no injects, switch-case)
273ns (trie-router, no injects, direct-data)
256ns (trie-router, pre-defined parameters)
237ns (trie-router, single-sweep wild-params)
191ns (trie-router, record parameters)
Initial Segment Trie
(defn- segment
([] (segment {} #{} nil nil))
([children wilds catch-all match]
(let [children' (impl/fast-map children)
wilds? (seq wilds)]
^{:type ::segment}
(reify
Segment
(-insert [_ [p & ps] d]
(if-not p
(segment children wilds catch-all d)
(let [[w c] ((juxt impl/wild-param impl/catch-all-param) p)
wilds (if w (conj wilds w) wilds)
catch-all (or c catch-all)
children (update children (or w c p) #(-insert (or % (segment)) ps d))]
(segment children wilds catch-all match))))
(-lookup [_ [p & ps] path-params]
(if (nil? p)
(when match (assoc match :path-params path-params))
(or (-lookup (impl/fast-get children' p) ps path-params)
(if (and wilds? (not (str/blank? p))) (some #(-lookup (impl/fast-get children' %) ps
(if catch-all (-catch-all children' catch-all path-params p ps)))))))))
Flamegraph it away
Optimizing the Java Trie
Java Trie
Set of matchers defined by the TrieCompiler
Order of magnitude faster than the original impl
@Override
public Match match(int i, int max, char[] path) {
boolean hasPercent = false;
boolean hasPlus = false;
if (i < max && path[i] != end) {
int stop = max;
for (int j = i; j < max; j++) {
final char c = path[j];
hasPercent = hasPercent || c == '%';
hasPlus = hasPlus || c == '+';
if (c == end) {
stop = j;
break;
}
}
final Match m = child.match(stop, max, path);
if (m != null) {
m.params = m.params.assoc(key, decode(new String(path, i, stop - i), hasPercent, hasPlus));
}
return m;
}
return null;
}
The Usual Suspects
Persistent Data Structures -> Records, Reify
Multimethods -> Protocols
Map Destructuring -> Manually
Unroll recursive functions ( assoc-in , ...)
Too generic functions ( walk , zip , ...)
Dynamic Binding
Manual inlining
Regexps
The Numbers
(c) https://www.artstation.com/kinixuki
RESTful api test
50+ routes, mostly wildcards
Reitit is orders of magnitude faster
220ns vs 22000ns, actually matters for busy sites
Looking out of the box
https://github.com/julienschmidt/httprouter
One of the fastest router in GO
(and source of many of the optimizations in reitit)
In Github api test, Reitit is ~40% slower
That's good! Still work to do.
Web Server Performance in
Clojure?
(c) https://www.artstation.com/kinixuki
Current Status
Most Clojure libraries don't even try to be fast
And that's totally ok for most apps
Compojure+ring-defaults vs reitit, with same response
headers, simple json echo
;; 10198tps
;; http :3000/api/ping
;; wrk -d ${DURATION:="30s"} http://127.0.0.1:3000/api/ping
(http/start-server defaults-app {:port 3000})
;; 48084tps
;; http :3002/api/ping
;; wrk -d ${DURATION:="30s"} http://127.0.0.1:3002/api/ping
(http/start-server reitit-app {:port 3002})
=> that's 5x more requests per sec.
TechEmpower Benchmark
TechEmpower Benchmark
TechEmpower Benchmark
Web Stacks
We know Clojure is a great tool for building web stuff
We can build a REALLY fast server stack for Clojure
aleph or immutant-nio as web server (nio, zero-copy)
reitit -based routing
Fast formatters like jsonista for JSON
next.jdbc (or porsas ) for database access
Good tools for async values & streams
lot's of other important components
Simple tools on top, the (coastal) castles?
Clojure Web & Data Next?
We have Ring, Pedestal, Yada & friends
We have nice templates & examples
Ring2 Spec? Spec for interceptors?
New performant reference architecture?
Bigger building blocks for rapid prototypes?
Making noice that Clojure is kinda awesome?
Community built Error Formatter?
Data-driven tools & inventories?
clojure.spec as data?
(c) https://www.artstation.com/kinixuki
Reitit bubbin' under
Support for OpenAPI3
JSON Schema validation
Spec2 support (when it's out)
Developer UI with remote debugger
More Batteries & Guides (frontend)
(Help make next.jdbc java-fast)
Sieppari.next
(c) https://www.artstation.com/kinixuki
Wrap-up
Reitit is a new routing library & framework
Embrace data-driven design, all the way down
Clojure is a Dynamic Language -> fail fast
clojure.spec to validate & transform data
Understand performance, test on your libraries
We can build beautiful & fast things with Clojure
Try out https://github.com/metosin/reitit
Chatting on #reitit in Slack
(c) https://www.artstation.com/kinixuki
 
 
 
 
Thanks.
@ikitommi
 
 
background artwork (c) Anna Dvorackova
https://www.artstation.com/kinixuki
(c) https://www.artstation.com/kinixuki

More Related Content

What's hot

Domain Modeling with FP (DDD Europe 2020)
Domain Modeling with FP (DDD Europe 2020)Domain Modeling with FP (DDD Europe 2020)
Domain Modeling with FP (DDD Europe 2020)
Scott Wlaschin
 
Java Foundations: Methods
Java Foundations: MethodsJava Foundations: Methods
Java Foundations: Methods
Svetlin Nakov
 
Templates in C++
Templates in C++Templates in C++
Templates in C++
Tech_MX
 
Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013
Scott Wlaschin
 
Operator overloading
Operator overloadingOperator overloading
Operator overloading
Pranali Chaudhari
 
Operators and Control Statements in Python
Operators and Control Statements in PythonOperators and Control Statements in Python
Operators and Control Statements in Python
RajeswariA8
 
Common Strategies for Improving Performance on Your Delta Lakehouse
Common Strategies for Improving Performance on Your Delta LakehouseCommon Strategies for Improving Performance on Your Delta Lakehouse
Common Strategies for Improving Performance on Your Delta Lakehouse
Databricks
 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Philip Schwarz
 
Data Structures Using C Practical File
Data Structures Using C Practical File Data Structures Using C Practical File
Data Structures Using C Practical File
Rahul Chugh
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to Django
Knoldus Inc.
 
Java operators
Java operatorsJava operators
Java operators
Shehrevar Davierwala
 
How to Actually Tune Your Spark Jobs So They Work
How to Actually Tune Your Spark Jobs So They WorkHow to Actually Tune Your Spark Jobs So They Work
How to Actually Tune Your Spark Jobs So They Work
Ilya Ganelin
 
Python Variable Types, List, Tuple, Dictionary
Python Variable Types, List, Tuple, DictionaryPython Variable Types, List, Tuple, Dictionary
Python Variable Types, List, Tuple, Dictionary
Soba Arjun
 
Regular Expressions in Java
Regular Expressions in JavaRegular Expressions in Java
Regular Expressions in Java
OblivionWalker
 
JavaScript - Chapter 5 - Operators
 JavaScript - Chapter 5 - Operators JavaScript - Chapter 5 - Operators
JavaScript - Chapter 5 - Operators
WebStackAcademy
 
Introduction to Apache Airflow - Data Day Seattle 2016
Introduction to Apache Airflow - Data Day Seattle 2016Introduction to Apache Airflow - Data Day Seattle 2016
Introduction to Apache Airflow - Data Day Seattle 2016
Sid Anand
 
List , tuples, dictionaries and regular expressions in python
List , tuples, dictionaries and regular expressions in pythonList , tuples, dictionaries and regular expressions in python
List , tuples, dictionaries and regular expressions in python
channa basava
 
Spring boot
Spring bootSpring boot
Spring boot
sdeeg
 
Scala Talk at FOSDEM 2009
Scala Talk at FOSDEM 2009Scala Talk at FOSDEM 2009
Scala Talk at FOSDEM 2009
Martin Odersky
 
Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)
Scott Wlaschin
 

What's hot (20)

Domain Modeling with FP (DDD Europe 2020)
Domain Modeling with FP (DDD Europe 2020)Domain Modeling with FP (DDD Europe 2020)
Domain Modeling with FP (DDD Europe 2020)
 
Java Foundations: Methods
Java Foundations: MethodsJava Foundations: Methods
Java Foundations: Methods
 
Templates in C++
Templates in C++Templates in C++
Templates in C++
 
Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013Domain Driven Design with the F# type System -- NDC London 2013
Domain Driven Design with the F# type System -- NDC London 2013
 
Operator overloading
Operator overloadingOperator overloading
Operator overloading
 
Operators and Control Statements in Python
Operators and Control Statements in PythonOperators and Control Statements in Python
Operators and Control Statements in Python
 
Common Strategies for Improving Performance on Your Delta Lakehouse
Common Strategies for Improving Performance on Your Delta LakehouseCommon Strategies for Improving Performance on Your Delta Lakehouse
Common Strategies for Improving Performance on Your Delta Lakehouse
 
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...Sum and Product Types -The Fruit Salad & Fruit Snack Example - From F# to Ha...
Sum and Product Types - The Fruit Salad & Fruit Snack Example - From F# to Ha...
 
Data Structures Using C Practical File
Data Structures Using C Practical File Data Structures Using C Practical File
Data Structures Using C Practical File
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to Django
 
Java operators
Java operatorsJava operators
Java operators
 
How to Actually Tune Your Spark Jobs So They Work
How to Actually Tune Your Spark Jobs So They WorkHow to Actually Tune Your Spark Jobs So They Work
How to Actually Tune Your Spark Jobs So They Work
 
Python Variable Types, List, Tuple, Dictionary
Python Variable Types, List, Tuple, DictionaryPython Variable Types, List, Tuple, Dictionary
Python Variable Types, List, Tuple, Dictionary
 
Regular Expressions in Java
Regular Expressions in JavaRegular Expressions in Java
Regular Expressions in Java
 
JavaScript - Chapter 5 - Operators
 JavaScript - Chapter 5 - Operators JavaScript - Chapter 5 - Operators
JavaScript - Chapter 5 - Operators
 
Introduction to Apache Airflow - Data Day Seattle 2016
Introduction to Apache Airflow - Data Day Seattle 2016Introduction to Apache Airflow - Data Day Seattle 2016
Introduction to Apache Airflow - Data Day Seattle 2016
 
List , tuples, dictionaries and regular expressions in python
List , tuples, dictionaries and regular expressions in pythonList , tuples, dictionaries and regular expressions in python
List , tuples, dictionaries and regular expressions in python
 
Spring boot
Spring bootSpring boot
Spring boot
 
Scala Talk at FOSDEM 2009
Scala Talk at FOSDEM 2009Scala Talk at FOSDEM 2009
Scala Talk at FOSDEM 2009
 
Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)Functional Design Patterns (DevTernity 2018)
Functional Design Patterns (DevTernity 2018)
 

Similar to Reitit - Clojure/North 2019

Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere
Fun with errors? - Clojure Finland Meetup 26.3.2019 TampereFun with errors? - Clojure Finland Meetup 26.3.2019 Tampere
Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere
Metosin Oy
 
Presto anatomy
Presto anatomyPresto anatomy
Presto anatomy
Dongmin Yu
 
The Road To Reactive with RxJava JEEConf 2016
The Road To Reactive with RxJava JEEConf 2016The Road To Reactive with RxJava JEEConf 2016
The Road To Reactive with RxJava JEEConf 2016
Frank Lyaruu
 
Monitoring with Prometheus
Monitoring with PrometheusMonitoring with Prometheus
Monitoring with Prometheus
Shiao-An Yuan
 
Camel as a_glue
Camel as a_glueCamel as a_glue
Camel as a_glue
Andriy Andrunevchyn
 
[245] presto 내부구조 파헤치기
[245] presto 내부구조 파헤치기[245] presto 내부구조 파헤치기
[245] presto 내부구조 파헤치기
NAVER D2
 
How to make the fastest Router in Python
How to make the fastest Router in PythonHow to make the fastest Router in Python
How to make the fastest Router in Python
kwatch
 
How and why i roll my own node.js framework
How and why i roll my own node.js frameworkHow and why i roll my own node.js framework
How and why i roll my own node.js framework
Ben Lin
 
PuppetDB: Sneaking Clojure into Operations
PuppetDB: Sneaking Clojure into OperationsPuppetDB: Sneaking Clojure into Operations
PuppetDB: Sneaking Clojure into Operations
grim_radical
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
Commit University
 
Using Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in RubyUsing Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in Ruby
LaunchAny
 
Aimaf
AimafAimaf
Aimaf
Saad RGUIG
 
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak   CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
PROIDEA
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applications
Tom Croucher
 
Server side JavaScript: going all the way
Server side JavaScript: going all the wayServer side JavaScript: going all the way
Server side JavaScript: going all the way
Oleg Podsechin
 
How Many Ohs? (An Integration Guide to Apex & Triple-o)
How Many Ohs? (An Integration Guide to Apex & Triple-o)How Many Ohs? (An Integration Guide to Apex & Triple-o)
How Many Ohs? (An Integration Guide to Apex & Triple-o)
OPNFV
 
Exploring Async PHP (SF Live Berlin 2019)
Exploring Async PHP (SF Live Berlin 2019)Exploring Async PHP (SF Live Berlin 2019)
Exploring Async PHP (SF Live Berlin 2019)
dantleech
 
Clojure and the Web
Clojure and the WebClojure and the Web
Clojure and the Web
nickmbailey
 
JRuby with Java Code in Data Processing World
JRuby with Java Code in Data Processing WorldJRuby with Java Code in Data Processing World
JRuby with Java Code in Data Processing World
SATOSHI TAGOMORI
 
Pune Clojure Course Outline
Pune Clojure Course OutlinePune Clojure Course Outline
Pune Clojure Course Outline
Baishampayan Ghose
 

Similar to Reitit - Clojure/North 2019 (20)

Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere
Fun with errors? - Clojure Finland Meetup 26.3.2019 TampereFun with errors? - Clojure Finland Meetup 26.3.2019 Tampere
Fun with errors? - Clojure Finland Meetup 26.3.2019 Tampere
 
Presto anatomy
Presto anatomyPresto anatomy
Presto anatomy
 
The Road To Reactive with RxJava JEEConf 2016
The Road To Reactive with RxJava JEEConf 2016The Road To Reactive with RxJava JEEConf 2016
The Road To Reactive with RxJava JEEConf 2016
 
Monitoring with Prometheus
Monitoring with PrometheusMonitoring with Prometheus
Monitoring with Prometheus
 
Camel as a_glue
Camel as a_glueCamel as a_glue
Camel as a_glue
 
[245] presto 내부구조 파헤치기
[245] presto 내부구조 파헤치기[245] presto 내부구조 파헤치기
[245] presto 내부구조 파헤치기
 
How to make the fastest Router in Python
How to make the fastest Router in PythonHow to make the fastest Router in Python
How to make the fastest Router in Python
 
How and why i roll my own node.js framework
How and why i roll my own node.js frameworkHow and why i roll my own node.js framework
How and why i roll my own node.js framework
 
PuppetDB: Sneaking Clojure into Operations
PuppetDB: Sneaking Clojure into OperationsPuppetDB: Sneaking Clojure into Operations
PuppetDB: Sneaking Clojure into Operations
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
Using Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in RubyUsing Sinatra to Build REST APIs in Ruby
Using Sinatra to Build REST APIs in Ruby
 
Aimaf
AimafAimaf
Aimaf
 
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak   CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
CONFidence 2015: DTrace + OSX = Fun - Andrzej Dyjak
 
Writing robust Node.js applications
Writing robust Node.js applicationsWriting robust Node.js applications
Writing robust Node.js applications
 
Server side JavaScript: going all the way
Server side JavaScript: going all the wayServer side JavaScript: going all the way
Server side JavaScript: going all the way
 
How Many Ohs? (An Integration Guide to Apex & Triple-o)
How Many Ohs? (An Integration Guide to Apex & Triple-o)How Many Ohs? (An Integration Guide to Apex & Triple-o)
How Many Ohs? (An Integration Guide to Apex & Triple-o)
 
Exploring Async PHP (SF Live Berlin 2019)
Exploring Async PHP (SF Live Berlin 2019)Exploring Async PHP (SF Live Berlin 2019)
Exploring Async PHP (SF Live Berlin 2019)
 
Clojure and the Web
Clojure and the WebClojure and the Web
Clojure and the Web
 
JRuby with Java Code in Data Processing World
JRuby with Java Code in Data Processing WorldJRuby with Java Code in Data Processing World
JRuby with Java Code in Data Processing World
 
Pune Clojure Course Outline
Pune Clojure Course OutlinePune Clojure Course Outline
Pune Clojure Course Outline
 

More from Metosin Oy

Navigating container technology for enhanced security by Niklas Saari
Navigating container technology for enhanced security by Niklas SaariNavigating container technology for enhanced security by Niklas Saari
Navigating container technology for enhanced security by Niklas Saari
Metosin Oy
 
Where is Technical Debt?
Where is Technical Debt?Where is Technical Debt?
Where is Technical Debt?
Metosin Oy
 
Creating an experimental GraphQL formatter using Clojure, Instaparse, and Gra...
Creating an experimental GraphQL formatter using Clojure, Instaparse, and Gra...Creating an experimental GraphQL formatter using Clojure, Instaparse, and Gra...
Creating an experimental GraphQL formatter using Clojure, Instaparse, and Gra...
Metosin Oy
 
Serverless Clojure and ML prototyping: an experience report
Serverless Clojure and ML prototyping: an experience reportServerless Clojure and ML prototyping: an experience report
Serverless Clojure and ML prototyping: an experience report
Metosin Oy
 
Naked Performance With Clojure
Naked Performance With ClojureNaked Performance With Clojure
Naked Performance With Clojure
Metosin Oy
 
Clojutre Real Life (2012 ClojuTRE Retro Edition)
Clojutre Real Life (2012 ClojuTRE Retro Edition)Clojutre Real Life (2012 ClojuTRE Retro Edition)
Clojutre Real Life (2012 ClojuTRE Retro Edition)
Metosin Oy
 
The Ancient Art of Data-Driven - reitit, the library -
The Ancient Art of Data-Driven - reitit, the library - The Ancient Art of Data-Driven - reitit, the library -
The Ancient Art of Data-Driven - reitit, the library -
Metosin Oy
 
Craft Beer & Clojure
Craft Beer & ClojureCraft Beer & Clojure
Craft Beer & Clojure
Metosin Oy
 
Performance and Abstractions
Performance and AbstractionsPerformance and Abstractions
Performance and Abstractions
Metosin Oy
 
ClojuTRE2016 Opening slides
ClojuTRE2016 Opening slidesClojuTRE2016 Opening slides
ClojuTRE2016 Opening slides
Metosin Oy
 
Schema tools-and-trics-and-quick-intro-to-clojure-spec-22.6.2016
Schema tools-and-trics-and-quick-intro-to-clojure-spec-22.6.2016Schema tools-and-trics-and-quick-intro-to-clojure-spec-22.6.2016
Schema tools-and-trics-and-quick-intro-to-clojure-spec-22.6.2016
Metosin Oy
 
ClojuTRE - a (very) brief history
ClojuTRE - a (very) brief historyClojuTRE - a (very) brief history
ClojuTRE - a (very) brief history
Metosin Oy
 
Wieldy remote apis with Kekkonen - ClojureD 2016
Wieldy remote apis with Kekkonen - ClojureD 2016Wieldy remote apis with Kekkonen - ClojureD 2016
Wieldy remote apis with Kekkonen - ClojureD 2016
Metosin Oy
 
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesomeClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
Metosin Oy
 
Clojure in real life 17.10.2014
Clojure in real life 17.10.2014Clojure in real life 17.10.2014
Clojure in real life 17.10.2014
Metosin Oy
 
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesomeEuroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
Metosin Oy
 
Swaggered web apis in Clojure
Swaggered web apis in ClojureSwaggered web apis in Clojure
Swaggered web apis in Clojure
Metosin Oy
 

More from Metosin Oy (17)

Navigating container technology for enhanced security by Niklas Saari
Navigating container technology for enhanced security by Niklas SaariNavigating container technology for enhanced security by Niklas Saari
Navigating container technology for enhanced security by Niklas Saari
 
Where is Technical Debt?
Where is Technical Debt?Where is Technical Debt?
Where is Technical Debt?
 
Creating an experimental GraphQL formatter using Clojure, Instaparse, and Gra...
Creating an experimental GraphQL formatter using Clojure, Instaparse, and Gra...Creating an experimental GraphQL formatter using Clojure, Instaparse, and Gra...
Creating an experimental GraphQL formatter using Clojure, Instaparse, and Gra...
 
Serverless Clojure and ML prototyping: an experience report
Serverless Clojure and ML prototyping: an experience reportServerless Clojure and ML prototyping: an experience report
Serverless Clojure and ML prototyping: an experience report
 
Naked Performance With Clojure
Naked Performance With ClojureNaked Performance With Clojure
Naked Performance With Clojure
 
Clojutre Real Life (2012 ClojuTRE Retro Edition)
Clojutre Real Life (2012 ClojuTRE Retro Edition)Clojutre Real Life (2012 ClojuTRE Retro Edition)
Clojutre Real Life (2012 ClojuTRE Retro Edition)
 
The Ancient Art of Data-Driven - reitit, the library -
The Ancient Art of Data-Driven - reitit, the library - The Ancient Art of Data-Driven - reitit, the library -
The Ancient Art of Data-Driven - reitit, the library -
 
Craft Beer & Clojure
Craft Beer & ClojureCraft Beer & Clojure
Craft Beer & Clojure
 
Performance and Abstractions
Performance and AbstractionsPerformance and Abstractions
Performance and Abstractions
 
ClojuTRE2016 Opening slides
ClojuTRE2016 Opening slidesClojuTRE2016 Opening slides
ClojuTRE2016 Opening slides
 
Schema tools-and-trics-and-quick-intro-to-clojure-spec-22.6.2016
Schema tools-and-trics-and-quick-intro-to-clojure-spec-22.6.2016Schema tools-and-trics-and-quick-intro-to-clojure-spec-22.6.2016
Schema tools-and-trics-and-quick-intro-to-clojure-spec-22.6.2016
 
ClojuTRE - a (very) brief history
ClojuTRE - a (very) brief historyClojuTRE - a (very) brief history
ClojuTRE - a (very) brief history
 
Wieldy remote apis with Kekkonen - ClojureD 2016
Wieldy remote apis with Kekkonen - ClojureD 2016Wieldy remote apis with Kekkonen - ClojureD 2016
Wieldy remote apis with Kekkonen - ClojureD 2016
 
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesomeClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
 
Clojure in real life 17.10.2014
Clojure in real life 17.10.2014Clojure in real life 17.10.2014
Clojure in real life 17.10.2014
 
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesomeEuroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
 
Swaggered web apis in Clojure
Swaggered web apis in ClojureSwaggered web apis in Clojure
Swaggered web apis in Clojure
 

Recently uploaded

June Patch Tuesday
June Patch TuesdayJune Patch Tuesday
June Patch Tuesday
Ivanti
 
Letter and Document Automation for Bonterra Impact Management (fka Social Sol...
Letter and Document Automation for Bonterra Impact Management (fka Social Sol...Letter and Document Automation for Bonterra Impact Management (fka Social Sol...
Letter and Document Automation for Bonterra Impact Management (fka Social Sol...
Jeffrey Haguewood
 
Finale of the Year: Apply for Next One!
Finale of the Year: Apply for Next One!Finale of the Year: Apply for Next One!
Finale of the Year: Apply for Next One!
GDSC PJATK
 
Overcoming the PLG Trap: Lessons from Canva's Head of Sales & Head of EMEA Da...
Overcoming the PLG Trap: Lessons from Canva's Head of Sales & Head of EMEA Da...Overcoming the PLG Trap: Lessons from Canva's Head of Sales & Head of EMEA Da...
Overcoming the PLG Trap: Lessons from Canva's Head of Sales & Head of EMEA Da...
saastr
 
Columbus Data & Analytics Wednesdays - June 2024
Columbus Data & Analytics Wednesdays - June 2024Columbus Data & Analytics Wednesdays - June 2024
Columbus Data & Analytics Wednesdays - June 2024
Jason Packer
 
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdfHow to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
Chart Kalyan
 
Digital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying AheadDigital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying Ahead
Wask
 
5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides
DanBrown980551
 
Ocean lotus Threat actors project by John Sitima 2024 (1).pptx
Ocean lotus Threat actors project by John Sitima 2024 (1).pptxOcean lotus Threat actors project by John Sitima 2024 (1).pptx
Ocean lotus Threat actors project by John Sitima 2024 (1).pptx
SitimaJohn
 
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing InstancesEnergy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
Alpen-Adria-Universität
 
HCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAUHCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAU
panagenda
 
Salesforce Integration for Bonterra Impact Management (fka Social Solutions A...
Salesforce Integration for Bonterra Impact Management (fka Social Solutions A...Salesforce Integration for Bonterra Impact Management (fka Social Solutions A...
Salesforce Integration for Bonterra Impact Management (fka Social Solutions A...
Jeffrey Haguewood
 
Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdfUnlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
Malak Abu Hammad
 
Skybuffer AI: Advanced Conversational and Generative AI Solution on SAP Busin...
Skybuffer AI: Advanced Conversational and Generative AI Solution on SAP Busin...Skybuffer AI: Advanced Conversational and Generative AI Solution on SAP Busin...
Skybuffer AI: Advanced Conversational and Generative AI Solution on SAP Busin...
Tatiana Kojar
 
Introduction of Cybersecurity with OSS at Code Europe 2024
Introduction of Cybersecurity with OSS  at Code Europe 2024Introduction of Cybersecurity with OSS  at Code Europe 2024
Introduction of Cybersecurity with OSS at Code Europe 2024
Hiroshi SHIBATA
 
Building Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and MilvusBuilding Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and Milvus
Zilliz
 
AWS Cloud Cost Optimization Presentation.pptx
AWS Cloud Cost Optimization Presentation.pptxAWS Cloud Cost Optimization Presentation.pptx
AWS Cloud Cost Optimization Presentation.pptx
HarisZaheer8
 
Serial Arm Control in Real Time Presentation
Serial Arm Control in Real Time PresentationSerial Arm Control in Real Time Presentation
Serial Arm Control in Real Time Presentation
tolgahangng
 
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdfMonitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Tosin Akinosho
 
Azure API Management to expose backend services securely
Azure API Management to expose backend services securelyAzure API Management to expose backend services securely
Azure API Management to expose backend services securely
Dinusha Kumarasiri
 

Recently uploaded (20)

June Patch Tuesday
June Patch TuesdayJune Patch Tuesday
June Patch Tuesday
 
Letter and Document Automation for Bonterra Impact Management (fka Social Sol...
Letter and Document Automation for Bonterra Impact Management (fka Social Sol...Letter and Document Automation for Bonterra Impact Management (fka Social Sol...
Letter and Document Automation for Bonterra Impact Management (fka Social Sol...
 
Finale of the Year: Apply for Next One!
Finale of the Year: Apply for Next One!Finale of the Year: Apply for Next One!
Finale of the Year: Apply for Next One!
 
Overcoming the PLG Trap: Lessons from Canva's Head of Sales & Head of EMEA Da...
Overcoming the PLG Trap: Lessons from Canva's Head of Sales & Head of EMEA Da...Overcoming the PLG Trap: Lessons from Canva's Head of Sales & Head of EMEA Da...
Overcoming the PLG Trap: Lessons from Canva's Head of Sales & Head of EMEA Da...
 
Columbus Data & Analytics Wednesdays - June 2024
Columbus Data & Analytics Wednesdays - June 2024Columbus Data & Analytics Wednesdays - June 2024
Columbus Data & Analytics Wednesdays - June 2024
 
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdfHow to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
How to Interpret Trends in the Kalyan Rajdhani Mix Chart.pdf
 
Digital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying AheadDigital Marketing Trends in 2024 | Guide for Staying Ahead
Digital Marketing Trends in 2024 | Guide for Staying Ahead
 
5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides5th LF Energy Power Grid Model Meet-up Slides
5th LF Energy Power Grid Model Meet-up Slides
 
Ocean lotus Threat actors project by John Sitima 2024 (1).pptx
Ocean lotus Threat actors project by John Sitima 2024 (1).pptxOcean lotus Threat actors project by John Sitima 2024 (1).pptx
Ocean lotus Threat actors project by John Sitima 2024 (1).pptx
 
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing InstancesEnergy Efficient Video Encoding for Cloud and Edge Computing Instances
Energy Efficient Video Encoding for Cloud and Edge Computing Instances
 
HCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAUHCL Notes and Domino License Cost Reduction in the World of DLAU
HCL Notes and Domino License Cost Reduction in the World of DLAU
 
Salesforce Integration for Bonterra Impact Management (fka Social Solutions A...
Salesforce Integration for Bonterra Impact Management (fka Social Solutions A...Salesforce Integration for Bonterra Impact Management (fka Social Solutions A...
Salesforce Integration for Bonterra Impact Management (fka Social Solutions A...
 
Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdfUnlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
Unlock the Future of Search with MongoDB Atlas_ Vector Search Unleashed.pdf
 
Skybuffer AI: Advanced Conversational and Generative AI Solution on SAP Busin...
Skybuffer AI: Advanced Conversational and Generative AI Solution on SAP Busin...Skybuffer AI: Advanced Conversational and Generative AI Solution on SAP Busin...
Skybuffer AI: Advanced Conversational and Generative AI Solution on SAP Busin...
 
Introduction of Cybersecurity with OSS at Code Europe 2024
Introduction of Cybersecurity with OSS  at Code Europe 2024Introduction of Cybersecurity with OSS  at Code Europe 2024
Introduction of Cybersecurity with OSS at Code Europe 2024
 
Building Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and MilvusBuilding Production Ready Search Pipelines with Spark and Milvus
Building Production Ready Search Pipelines with Spark and Milvus
 
AWS Cloud Cost Optimization Presentation.pptx
AWS Cloud Cost Optimization Presentation.pptxAWS Cloud Cost Optimization Presentation.pptx
AWS Cloud Cost Optimization Presentation.pptx
 
Serial Arm Control in Real Time Presentation
Serial Arm Control in Real Time PresentationSerial Arm Control in Real Time Presentation
Serial Arm Control in Real Time Presentation
 
Monitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdfMonitoring and Managing Anomaly Detection on OpenShift.pdf
Monitoring and Managing Anomaly Detection on OpenShift.pdf
 
Azure API Management to expose backend services securely
Azure API Management to expose backend services securelyAzure API Management to expose backend services securely
Azure API Management to expose backend services securely
 

Reitit - Clojure/North 2019

  • 1. Reitit The Ancient Art of Data-Driven tommi@metosin.fi Clojure/North
  • 2. Me(tosin) Sinclair Spectrum in 1983 ... Co-founded Metosin 2012 20 developers, Tampere & Helsinki / Finland Projects, Startups and Design FP, mostly Clojure/Script https://github.com/metosin
  • 3. ClojuTRE 2019 (8th) September 26-27th 2019, Helsinki FP & Clojure, 300++ on last two years Basic flights & hotels for speakers Free Student & Diversity tickets Call For Speakers Open https://clojutre.org
  • 4. This talk Routing and dispatching Reitit, the library Reitit, the framework Performance Takeaways
  • 5. Routing in Clojure/Script Lot's of options: Ataraxy, Bide, Bidi, Compojure, Keechma, Pedestal, Silk, Secretary, ... Something we need in mostly all the (web)apps Mapping from path -> match Mapping from name -> match (reverse routing) HTTP (both server & browser) Messaging Others
  • 6. Dispatching in Clojure/Script Here: composing end executing functionality in the request-response pipeline Common patterns Middleware (Ring, nREPL, ..) Interceptors (Pedestal, Re-Frame, ..) Controllers (Keechma) Promises Sync & Async
  • 7. New Routing Library? More data-driven Leveraging clojure.spec Built for performance Friendly & explicit API Reach: browser, JVM, Node, Others Both routing and dispatching The Yellow (Hiccup) Castle of Routing?
  • 8.
  • 9. Reitit, the Library reitit-core (c) https://www.artstation.com/kinixuki
  • 10. Basics reitit.core/router to create a router Matching by path or by name (reverse routing) Functions to browse the route trees (defprotocol Router (router-name [this]) (routes [this]) (compiled-routes [this]) (options [this]) (route-names [this]) (match-by-path [this path]) (match-by-name [this name] [this name path-params]))
  • 11. (require '[reitit.core :as r]) (def router (r/router [["/ping" ::ping] ["/users/:id" ::user]])) (r/match-by-path router "/ping") ;#Match{:template "/ping" ; :data {:name :user/ping} ; :result nil ; :path-params {} ; :path "/ping"} (r/match-by-name router ::ping) ;#Match{:template "/ping" ; :data {:name :user/ping} ; :result nil ; :path-params {} ; :path "/ping"}
  • 12. Route Syntax (~hiccup) Paths are concatenated, route data meta-merged (r/router ["/api" {:interceptors [::api]} ["/ping" ::ping] ["/admin" {:roles #{:admin}} ["/users" ::users] ["/db" {:interceptors [::db] :roles ^:replace #{:db-admin}}]]])) (r/router [["/api/ping" {:interceptors [::api] :name ::ping}] ["/api/admin/users" {:interceptors [::api] :roles #{:admin} :name ::users}] ["/api/admin/db" {:interceptors [::api ::db] :roles #{:db-admin}}]])
  • 13. Programming routes nil s and nested sequences are flattened (defn router [dev?] (r/router [["/command/" (for [mutation [:olipa :kerran :avaruus]] [(name mutation) mutation])] (if dev? ["/dev-tools" :dev-tools])])) (-> (router true) (r/routes)) ;[["/command/olipa" {:name :olipa}] ; ["/command/kerran" {:name :kerran}] ; ["/command/avaruus" {:name :avaruus}] ; ["/dev-tools" {:name :dev-tools}]]
  • 14. Composing routes It's just data, merge trees Single route tree allows to optimize the whole Merging & nesting routers Empty route fragments, e.g. :<> in Reagent (-> (r/router [["" {:interceptors [::cors]} ["/api" ::api] ["/ipa" ::ipa]] ["/ping" ::ping]]) (r/routes)) ;[["/api" {:interceptors [::cors], :name ::api}] ; ["/ipa" {:interceptors [::cors], :name ::ipa}] ; ["/ping" {:name ::ping}]]
  • 15. Route Conflict Resolution Multiple sources (code/EDN/DB) for routes? Are all routes still reachable? Reitit fails-fast by default on path & name conflicts (require '[reitit.core :as r]) (require '[reitit.dev.pretty :as pretty]) (r/router [["/ping"] ["/:user-id/orders"] ["/bulk/:bulk-id"] ["/public/*path"] ["/:version/status"]] {:exception pretty/exception})
  • 17. Community-driven Clojure error formatter? (c) https://www.artstation.com/kinixuki
  • 18. What is in the route data? Can be anything, the Router doesn't care. Returned on successful Match Can be queried from a Router Build your own interpreters for the data A Route First Architecture: Match -> Data -> React (r/match-by-path router "/api/admin/db") ;#Match{:template "/api/admin/db", ; :data {:interceptors [::api ::db] ; :roles #{:db-admin}}, ; :result nil, ; :path-params {}, ; :path "/api/admin/db"}
  • 19. Example use cases Authorization via :roles Frontend components via :view Dispatching via :middleware and :interceptors Stateful dispatching with :controllers Coercion via :parameters and :coercion Run requests in a server io-pool with :server/io Deploy endpoints separately to :server/target
  • 20. Frontend Example https://router.vuejs.org/guide/essentials/nested-routes.html (require '[reitit.frontend :as rf]) (require '[reitit.frontend.easy :as rfe]) (defn frontpage-view [match] ...) (defn topics-view [match] ... (rf/nested-view match) (defn topic-view [match] ...) (def router (rf/router [["/" {:name :frontpage :views [frontpage-view]}] ["/topics" {:controllers [load-topics] :views [topics-view]} ["" {:name :topics}] ["/:id" {:name :topic :parameters {:path {:id int?}} :controllers [load-topic] :views [topic-view]}]]])) (rfe/start! router ...)
  • 21. Route Data Validation Route Data can be anything -> data spaghetti? Leverage clojure.spec at router creation Define and enforce route data specs Fail-fast, missing, extra or misspelled keys With help of spec-tools and spell-spec Closed specs coming to Spec2 (TBD)
  • 22. Invalid Route Data Example (require '[reitit.spec :as spec]) (require '[clojure.spec.alpha :as s]) (s/def ::role #{:admin :user}) (s/def ::roles (s/coll-of ::role :into #{})) (r/router ["/api/admin" {::roles #{:adminz}}] {:validate spec/validate :exception pretty/exception})
  • 25. Failing Fast During static analysis (e.g. linter) At compile-time (macros, defs) At development-time (schema/spec annos) At creation-time At runtime At runtime special condition (c) https://www.artstation.com/kinixuki
  • 26. Coercion (Déjà-vu) Coercion is a process of transforming values between domain (e.g. JSON->EDN, String->EDN) Route data keys :parameters & :coercion Utilities to apply coercion in reitit.coercion Implementation for Schema & clojure.spec <-- !!! (defprotocol Coercion "Pluggable coercion protocol" (-get-name [this]) (-get-options [this]) (-get-apidocs [this specification data]) (-compile-model [this model name]) (-open-model [this model]) (-encode-error [this error]) (-request-coercer [this type model]) (-response-coercer [this model]))
  • 27. Reitit, the Framework (c) https://www.artstation.com/kinixuki
  • 28. Framework You call the library, but the framework calls you Reitit ships with multiple Routing Frameworks reitit-ring Middleware-dispatch for Ring reitit-http Async Interceptor-dispatch for http reitit-pedestal Reitit Pedestal reitit-frontend (Keechma-style) Controllers, History & Fragment Router, helpers
  • 30. Ring Routing A separate lightweight module Routing based on path and :request-method Adds support for :middleware dispatch chain is executed after a match ring-handler to create a ring-compatible handler Supports Both sync & async Supports Both JVM & Node No magic, no default middleware
  • 31. Ring Application (require '[reitit.ring :as ring]) (def app (ring/ring-handler (ring/router ["/api" {:middleware [wrap-api wrap-roles]} ["/ping" {:get ping-handler]} ["/users" {:middleware [db-middleware] :roles #{:admin} ;; who reads this? :get get-users :post add-user}]]) (ring/create-default-handler))) (app {:uri "/api/ping" :request-method :get}) ; {:status 200, :body "pong"}
  • 32. Accessing route data Match and Router are injected into the request Components can read these at request time and do what ever they want, "Ad-hoc extensions" Pattern used in Kekkonen and in Yada (defn wrap-roles [handler] ;; roles injected via session-middleware (fn [{:keys [roles] :as request}] ;; read the route-data at request-time (let [required (-> request (ring/get-match) :data :roles)] (if (and (seq required) (not (set/subset? required roles))) {:status 403, :body "forbidden"} (handler request)))))
  • 33. Middleware as data Ring middleware are opaque functions Reitit adds a first class values, Middleware records Recursive IntoMiddleware protocol to expand to Attach documentation, specs, requirements, ... Can be used in place of middleware functions Zero runtime penalty (defn roles-middleware [] {:name ::roles-middleware :description "Middleware to enforce roles" :requires #{::session-middleware} :spec (s/keys :opt-un [::roles]) :wrap wrap-roles})
  • 34. Compiling middleware Each middleware knows the endpoint it's mounted to We can pass the route data in at router creation time Big win for optimizing chains (def roles-middleware {:name ::roles-middleware :description "Middleware to enforce roles" :requires #{::session-middleware} :spec (s/keys :opt-un [::roles]) :compile (fn [{required :roles} _] ;; unmount if there are no roles required (if (seq required) (fn [handler] (fn [{:keys [roles] :as request}] (if (not (set/subset? required roles)) {:status 403, :body "forbidden"} (handler request))))))})
  • 35. Partial Specs Example "All routes under /account should require a role" ;; look ma, not part of request processing! (def roles-defined {:name ::roles-defined :description "requires a ::role for the routes" :spec (s/keys :req-un [::roles])}) ["/api" {:middleware [roles-middleware]} ;; behavior ["/ping"] ;; unmounted ["/account" {:middleware [roles-defined]} ;; :roles mandatory ["/admin" {:roles #{:admin}}] ;; ok ["/user" {:roles #{:user}}] ;; ok ["/manager"]]] ;; fail!
  • 36. Middleware chain as data Each endpoint has it's own vector of middleware Documents of what is in the chain Chain can be manipulated at router creation time Reordering Completing Interleaving Interleaving a request diff console printer: reitit.ring.middleware.dev/print-request-diffs
  • 37.
  • 38. Data definitions (as data) The core coercion for all (OpenAPI) paramerer & response types ( :query , :body , header , :path etc) Separerate Middleware to apply request & response coercion and format coercion errors Separate modules, spec coercion via spec-tools ["/plus/:y" {:get {:parameters {:query {:x int?}, :path {:y int?}} :responses {200 {:body {:total pos-int?}}} :handler (fn [{:keys [parameters]}] ;; parameters are coerced (let [x (-> parameters :query :x) y (-> parameters :path :y)] {:status 200 :body {:total (+ x y)}}))}}]
  • 39. Everything as data. (c) https://www.artstation.com/kinixuki
  • 40. Interceptors metosin/reitit-http e.g. going async (c) https://www.artstation.com/kinixuki
  • 41. Going Async Interceptors are much better fit for async reitit-http uses the Interceptor model Requires an Interceptor Executor reitit-pedestal or reitit-sieppari Sieppari supports core.async , Manifold and Promesa Pedestal is more proven, supports core.async Target Both JVM & Node (via Sieppari)
  • 42. Http Application (require '[reitit.http :as http]) (require '[reitit.interceptor.sieppari :as sieppari] (def app (http/ring-handler (http/router ["/api" {:interceptors [api-interceptor roles-interceptor]} ["/ping" ping-handler] ["/users" {:interceptors [db-interceptor] :roles #{:admin} :get get-users :post add-user}]]) (ring/create-default-handler) {:executor sieppari/executor})) (app {:uri "/api/ping" :request-method :get}) ; {:status 200, :body "pong"}
  • 43. Example Interceptor (defn coerce-request-interceptor "Interceptor for pluggable request coercion. Expects a :coercion of type `reitit.coercion/Coercion` and :parameters from route data, otherwise does not mount." [] {:name ::coerce-request :spec ::rs/parameters :compile (fn [{:keys [coercion parameters]} opts] (cond ;; no coercion, skip (not coercion) nil ;; just coercion, don't mount (not parameters) {} ;; mount :else (let [coercers (coercion/request-coercers coercion parameters opts)] {:enter (fn [ctx] (let [request (:request ctx) coerced (coercion/coerce-request coercers request) request (impl/fast-assoc request :parameters coerced)] (assoc ctx :request request)))})))})
  • 45. Route-driven frameworks Routing and dispatching is separated, middleware (or interceptors) are applied only after a match Each endpoint has a unique dispatch chain Each component can be compiled and optimized against the endpoint at creation time Components can define partial route data specs that only effect the routes they are mounted to. We get both Performance & Correctness ... this is Kinda Awesome. (c) https://www.artstation.com/kinixuki
  • 47. Performance how can we make compojure-api faster? we moved from Clojure to GO because of perf How fast are the current Clojure libraries? How fast can we go with Java/Clojure?
  • 48. Measuring Performance Always measure Both micro & macro benchmarks In the end, the order of magnitude matters Lot's of good tools, some favourites: clojure.core/time criterium com.clojure-goes-fast/clj-async-profiler com.clojure-goes-fast/clj-java-decompiler https://github.com/wg/wrk
  • 49. (def defaults {:keywords? true}) (time (dotimes [_ 10000] (merge defaults {}))) ; "Elapsed time: 4.413803 msecs" (require '[criterium.core :as cc]) (cc/quick-bench (merge defaults {})) ; Evaluation count : 2691372 in 6 samples of 448562 calls. ; Execution time mean : 230.346208 ns ; Execution time std-deviation : 10.355077 ns ; Execution time lower quantile : 221.101397 ns ( 2.5%) ; Execution time upper quantile : 245.331388 ns (97.5%) ; Overhead used : 1.881561 ns
  • 50. (require '[clj-async-profiler.core :as prof]) (prof/serve-files 8080) ;; serve the svgs here (prof/profile (dotimes [_ 40000000] ;; ~10sec period (merge defaults {})))
  • 51. Reitit performance Designed group up to be performant Perf suite to see how performance evolves Measured against Clojure/JavaScript/GO Routers Performance toolbox: Optimized routing algorithms Separation of creation & request time The Usual Suspects
  • 52. Routing algorithms When a router is created, route tree is inspected and a best possible routing algorith is chosen No regexps, use linear-router as a last effort lookup-router , single-static-path-router trie-router , linear-router mixed-router , quarantine-router
  • 53. trie-router For non-conflicting trees with wildcards First insert data into Trie AST, then compile it into fast lookup functions using a TrieCompiler On JVM, backed by a fast Java-based Radix-trie
  • 54. (require '[reitit.trie :as trie]) (-> [["/v2/whoami" 1] ["/v2/users/:user-id/datasets" 2] ["/v2/public/projects/:project-id/datasets" 3] ["/v1/public/topics/:topic" 4] ["/v1/users/:user-id/orgs/:org-id" 5] ["/v1/search/topics/:term" 6] ["/v1/users/:user-id/invitations" 7] ["/v1/users/:user-id/topics" 9] ["/v1/users/:user-id/bookmarks/followers" 10] ["/v2/datasets/:dataset-id" 11] ["/v1/orgs/:org-id/usage-stats" 12] ["/v1/orgs/:org-id/devices/:client-id" 13] ["/v1/messages/user/:user-id" 14] ["/v1/users/:user-id/devices" 15] ["/v1/public/users/:user-id" 16] ["/v1/orgs/:org-id/errors" 17] ["/v1/public/orgs/:org-id" 18] ["/v1/orgs/:org-id/invitations" 19] ["/v1/users/:user-id/device-errors" 22]] (trie/insert) (trie/compile) (trie/pretty))
  • 55. ["/v" [["1/" [["users/" [:user-id ["/" [["device" [["-errors" 22] ["s" 15]]] ["orgs/" [:org-id 5]] ["bookmarks/followers" 10] ["invitations" 7] ["topics" 9]]]]] ["orgs/" [:org-id ["/" [["devices/" [:client-id 13]] ["usage-stats" 12] ["invitations" 19] ["errors" 17]]]]] ["public/" [["topics/" [:topic 4]] ["users/" [:user-id 16]] ["orgs/" [:org-id 18]]]] ["search/topics/" [:term 6]] ["messages/user/" [:user-id 14]]]] ["2/" [["public/projects/" [:project-id ["/datasets" 3]]] ["users/" [:user-id ["/datasets" 2]]] ["datasets/" [:dataset-id 11]] ["whoami" 1]]]]]
  • 56. Optimization Log 160ns (httprouter/GO) 3000ns (clojure, interpreted) ... (clueless poking) 990ns (clojure-trie) 830ns (faster decode params) 560ns (java-segment-router) 490ns (java-segment-router, no injects) 440ns (java-segment-router, no injects, single-wild-optimization) 305ns (trie-router, no injects) 281ns (trie-router, no injects, optimized) 277ns (trie-router, no injects, switch-case) 273ns (trie-router, no injects, direct-data) 256ns (trie-router, pre-defined parameters) 237ns (trie-router, single-sweep wild-params) 191ns (trie-router, record parameters)
  • 57. Initial Segment Trie (defn- segment ([] (segment {} #{} nil nil)) ([children wilds catch-all match] (let [children' (impl/fast-map children) wilds? (seq wilds)] ^{:type ::segment} (reify Segment (-insert [_ [p & ps] d] (if-not p (segment children wilds catch-all d) (let [[w c] ((juxt impl/wild-param impl/catch-all-param) p) wilds (if w (conj wilds w) wilds) catch-all (or c catch-all) children (update children (or w c p) #(-insert (or % (segment)) ps d))] (segment children wilds catch-all match)))) (-lookup [_ [p & ps] path-params] (if (nil? p) (when match (assoc match :path-params path-params)) (or (-lookup (impl/fast-get children' p) ps path-params) (if (and wilds? (not (str/blank? p))) (some #(-lookup (impl/fast-get children' %) ps (if catch-all (-catch-all children' catch-all path-params p ps)))))))))
  • 60. Java Trie Set of matchers defined by the TrieCompiler Order of magnitude faster than the original impl @Override public Match match(int i, int max, char[] path) { boolean hasPercent = false; boolean hasPlus = false; if (i < max && path[i] != end) { int stop = max; for (int j = i; j < max; j++) { final char c = path[j]; hasPercent = hasPercent || c == '%'; hasPlus = hasPlus || c == '+'; if (c == end) { stop = j; break; } } final Match m = child.match(stop, max, path); if (m != null) { m.params = m.params.assoc(key, decode(new String(path, i, stop - i), hasPercent, hasPlus)); } return m; } return null; }
  • 61. The Usual Suspects Persistent Data Structures -> Records, Reify Multimethods -> Protocols Map Destructuring -> Manually Unroll recursive functions ( assoc-in , ...) Too generic functions ( walk , zip , ...) Dynamic Binding Manual inlining Regexps
  • 63. RESTful api test 50+ routes, mostly wildcards Reitit is orders of magnitude faster 220ns vs 22000ns, actually matters for busy sites
  • 64.
  • 65. Looking out of the box https://github.com/julienschmidt/httprouter One of the fastest router in GO (and source of many of the optimizations in reitit) In Github api test, Reitit is ~40% slower That's good! Still work to do.
  • 66. Web Server Performance in Clojure? (c) https://www.artstation.com/kinixuki
  • 67. Current Status Most Clojure libraries don't even try to be fast And that's totally ok for most apps Compojure+ring-defaults vs reitit, with same response headers, simple json echo ;; 10198tps ;; http :3000/api/ping ;; wrk -d ${DURATION:="30s"} http://127.0.0.1:3000/api/ping (http/start-server defaults-app {:port 3000}) ;; 48084tps ;; http :3002/api/ping ;; wrk -d ${DURATION:="30s"} http://127.0.0.1:3002/api/ping (http/start-server reitit-app {:port 3002}) => that's 5x more requests per sec.
  • 71. Web Stacks We know Clojure is a great tool for building web stuff We can build a REALLY fast server stack for Clojure aleph or immutant-nio as web server (nio, zero-copy) reitit -based routing Fast formatters like jsonista for JSON next.jdbc (or porsas ) for database access Good tools for async values & streams lot's of other important components Simple tools on top, the (coastal) castles?
  • 72. Clojure Web & Data Next? We have Ring, Pedestal, Yada & friends We have nice templates & examples Ring2 Spec? Spec for interceptors? New performant reference architecture? Bigger building blocks for rapid prototypes? Making noice that Clojure is kinda awesome? Community built Error Formatter? Data-driven tools & inventories? clojure.spec as data?
  • 74. Reitit bubbin' under Support for OpenAPI3 JSON Schema validation Spec2 support (when it's out) Developer UI with remote debugger More Batteries & Guides (frontend) (Help make next.jdbc java-fast) Sieppari.next (c) https://www.artstation.com/kinixuki
  • 75. Wrap-up Reitit is a new routing library & framework Embrace data-driven design, all the way down Clojure is a Dynamic Language -> fail fast clojure.spec to validate & transform data Understand performance, test on your libraries We can build beautiful & fast things with Clojure Try out https://github.com/metosin/reitit Chatting on #reitit in Slack (c) https://www.artstation.com/kinixuki
  • 76.         Thanks. @ikitommi     background artwork (c) Anna Dvorackova https://www.artstation.com/kinixuki (c) https://www.artstation.com/kinixuki