coordinating non blocking io melb-clj
DESCRIPTION
TRANSCRIPT
COORDINATING NON-BLOCKING IO
AND ASYNC HTTP RESPONSES
WHAT IS THE WEB STACK
• HttpKit as web server • Compojure for routing • HttpKit for non-blocking http requests
REACTIVE APPROACH PREFERRED
• We are not using either Functional Reactive Programming or Reactive Programming libraries. Eg. Rx.java
• May satisfy other more broad definitions of reactive • Are making better use of threads than traditional approaches
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. Send request to payment gateway 2. Find how much was left to be payed !
If payment is success: render amount remaining on bill If payment fails: render error
CANDIDATES
• Synchronous promises
• Promise monad let/do
• Promise monad chain/lift-m-2
• 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: 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.1: 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 1.2: 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.3: PROMISE + ERROR MONADS
(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 (render-‐remaining-‐response details amount))))
SOLUTION 2: PROMISE CHAIN AND LIFT-M-2
(GET "/bills/:bill-‐id/payments" [bill-‐id user-‐id amount] (let [transaction-‐req (chain (promise user-‐id) auth/card-‐token (partial payment/bill bill-‐id amount)) details-‐req (bill/details bill-‐id)] (lift-‐m-‐2 (fn [transaction details] (if (success? transaction) (render-‐remaining-‐response details amount) error-‐response))) transaction-‐req details-‐req))
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
HELLO WORLD
• Single C1-Medium
• 7GB Ram
• 8 Cores
• 313,852 Concurrent Users
• 4756.79 Requests Per Second
• More meaningful results once in SVT with full implementation
ALL DONE AT AUSTRALIA POST DIGITAL MAILBOX
They're hiring. Send your CV to