clojure at a post office
DESCRIPTION
Presented at Euroclojure June 2014.TRANSCRIPT
CLOJURE AT A POST OFFICE
HTTP-KIT
• AWS C1-Medium!
• 7GB Ram!
• 8 Cores
• 313,852 Concurrent Users!
• 4756.79 Requests Per Second
If payment is success: display amount remaining on bill!If payment fails: display error
Make a payment on a bill!- Not necessarily a full payment
POST /bills/:bill-id/payments Session: user-id Post Data: amount
1. GET credit card token for user! 1.1. POST request to payment gateway!2. GET how much was left to be payed
CANDIDATES
• Synchronous promises!
• Promise monad let/do!
• Raw promises!
• Raw callbacks!
• core.async!
• Lamina pipeline!
• Meltdown (LMAX Disrupter)!
• Pulsar promises!
• Pulsar Actors
SOLUTION 0: SYNCHRONOUS
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]"""(let'[token"""""""(auth/card/token"user*id)"""""""""details"""""(bill/details"bill*id)"""""""""transaction"(payment/bill"bill*id"amount"@token)]"""""(if'(success?"@transaction)"""""""(render/remaining/response"@details"amount)"""""""error*response)))"
SOLUTION 1.1: PROMISE MONAD DO
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]""""""(do'[token"""""""(auth/card/token"user*id)"""""""""""transaction"(payment/bill"bill*id"amount"token)"""""""""""details"""""(bill/details"bill*id)]""""""""""(return""""""""""""(if'(success?"transaction)""""""""""""""(render/remaining/response"details"amount)""""""""""""""error*response))))
SOLUTION 1.2: PROMISE MONAD LET/DO
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]""""""(let'[token*req"""(auth/card/token"user*id)""""""""""""details*req"(bill/details"bill*id)]""""""""(do'[token"""""""token*req"""""""""""""transaction"(payment/bill"bill*id"amount"token)"""""""""""""details"""""details*req]""""""""""""(return""""""""""""""(if'(success?"transaction)""""""""""""""""(render/remaining/response"details"amount)""""""""""""""""error*response)))))
SOLUTION 1.3: PROMISE MONAD LET/DO/DO
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]""""""(let'[token*req"""(auth/card/token"user*id)""""""""""""details*req"(bill/details"bill*id)]""""""""(do'[token"""""""token*req"""""""""""""transaction"(payment/bill"bill*id"amount"token)]""""""""""""(if'(success?"transaction)""""""""""""""(do'[details"details*req]""""""""""""""""""(return"(render/remaining/response"details"amount)))""""""""""""""(return"error*response)))))
SOLUTION 3: RAW PROMISES
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]""""""(let'[transaction*req"(*>"(auth/card/token"user*id)""""""""""""""""""""""""""""""""(then"(partial"payment/bill"bill*id"amount)))""""""""""""details*req"""""(bill/details"bill*id)]""""""""(when"transaction*req"details*req""""""""""(fn'[transaction"details]""""""""""""(if'(success?"transaction)""""""""""""""(render/remaining/response"details"amount)""""""""""""""error*response)))))
SOLUTION 4: RAW CALLBACKS
Not"Viable
SOLUTION 5: CORE.ASYNC
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]""""""(go"(let'[token"""""""(auth/card/token"user*id)""""""""""""""""details"""""(bill/details"bill*id)""""""""""""""""transaction"(payment/bill"bill*id"amount"(<!"token))]""""""""""""(if'(success?"(<!"transaction))""""""""""""""(render/remaining/response"(<!"details)"amount)""""""""""""""error*response))))
SOLUTION 6: LAMINA PIPELINE
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]""""""(let'[details*req"(bill/details"bill*id)]""""""""(pipeline"(auth/card/token"user*id)""""""""""""""""""(partial"payment/bill"bill*id"amount)""""""""""""""""""(fn'[transaction]""""""""""""""""""""(if'(success?"transaction)""""""""""""""""""""""(on/realized"details*req"""""""""""""""""""""""""""""""""""(fn'[details]"""""""""""""""""""""""""""""""""""""(render/remaining/response"details"amount)))""""""""""""""""""""""error*response)))))
SOLUTION 7: MELTDOWN
No"point
SOLUTION 8: PULSAR PROMISES
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]"""#(let'[token"""""""(auth/card/token"user*id)""""""""""details"""""(bill/details"bill*id)""""""""""transaction"(payment/bill"bill*id"amount"@token)]"""""(if'(success?"@transaction)"""""""(render/remaining/response"@details"amount)"""""""error*response)))
SOLUTION 9: PULSAR ACTORS
Not"Appropriate
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]"""(let'[token"""""""(auth/card/token"user*id)"""""""""details"""""(bill/details"bill*id)"""""""""transaction"(payment/bill"bill*id"amount"@token)]"""""(if'(success?"@transaction)"""""""(render/remaining/response"@details"amount)"""""""error*response))) Synchronous
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]"""(go"(let'[token"""""""(auth/card/token"user*id)"""""""""""""details"""""(bill/details"bill*id)"""""""""""""transaction"(payment/bill"bill*id"amount"(<!"token))]"""""""""(if'(success?"(<!"transaction))"""""""""""(render/remaining/response"(<!"details)"amount)"""""""""""error*response)))) core.async
(GET""/bills/:bill*id/payments""[bill*id"user*id"amount]"""#(let'[token"""""""(auth/card/token"user*id)""""""""""details"""""(bill/details"bill*id)""""""""""transaction"(payment/bill"bill*id"amount"@token)]""""""(if'(success?"@transaction)""""""""(render/remaining/response"@details"amount)""""""""(error/response)))) Pulsar
""def"payBill(billId:"Integer,"userId:"Integer,"amount:"Integer):Future[Option[Json]]"="{ """"val"seq"="for"{ """"""token"<*"Auth.cardToken(userId) """"""tr"<*"Payment.bill(token) """"}"yield"tr " """"async"{ """"""val"transactionProcess"="await(seq.run) """"""val"detailProcess"="await(BillOps.details(billId)) """"""for"{ """"""""transaction"<*"transactionProcess """"""""detail"<*"detailProcess """"""}"yield"renderRemainingResponse(amount,"detail) """"} ""}
SCALA
REQUESTS PER SECOND
CQRS!
• Want fast reads. Reduce the number of queries.!
• Don't want to have to update write code every time we add a new reader.!
• Don't want to have to update reader every time there's a new writer.!
• Would be great to have an event stream to re-calculate current state.
1.#Just#write#to#normalised#copy#1.1.# Just#read#everything#every#time#1.2.# Read#and#cache#1.2.1.# Cache#the#page#1.2.2.# Cache#the#intermediate#data;structure#
2.#Just#write#to#views#2.1.# Direct#write#2.2.# Event#stream#2.2.1.# Persisted#2.2.1.1.# Domain#events#2.2.1.1.1.#Live#update#2.2.1.1.2.#Async#update#
2.2.1.2.# CRUD#event#2.2.1.2.1.#Live#update#2.2.1.2.2.#Async#update#
2.2.2.# Ephemeral#2.2.2.1.# Domain#events#2.2.2.1.1.#Live#update#
2.2.2.2.# CRUD#event#2.2.2.2.1.#Live#update#
3.#Write#to#views#and#normalized#copy##3.1.# Direct#write#
3.2.# Event#stream#3.2.1.# Persisted#3.2.1.1.# Domain#events#3.2.1.1.1.#Live#update#3.2.1.1.2.#Async#update#
3.2.1.2.# CRUD#event#3.2.1.2.1.#Live#update#3.2.1.2.2.#Async#update#
3.2.2.# Ephemeral#3.2.2.1.# Domain#events#3.2.2.1.1.#Live#update#
3.2.2.2.# CRUD#event#3.2.2.2.1.#Live#update#
3.3.# Write#to#normalized#keyspace#causing#trigger#3.3.1.# Direct#update#to#views#3.3.2.# Event#to#be#published#3.3.2.1.# Persisted#3.3.2.1.1.#Live#Update#3.3.2.1.2.#Async#update#
3.3.2.2.# Ephemeral#3.3.2.2.1.#Live#Update
Write
!!
Cassandra
Service A
Service B Index Maintainer
Notify
Read Write
Rabbit MQ
Publish
Triggers
CASSANDRA TRIGGERS
• Can just throw the Clojure jar in there!
• Everything is byte buffers!
• Need to know the type of fields out of band!
• One class per trigger per table!
• Bizarre key names (format changes depending on value type)
User Service
Mail Service
Provider Service
Cassandra
IOS Web Android
User Service
Authentication
Multi Factor Authentication
Authorisation
User Profile
Password Reset