Exploring four Datomic superpowers

Lucas Cavalcanti dos Santos
Lucas Cavalcanti dos SantosSoftware Engineer at Nubank Brasil
Exploring Four 
Datomic 
Superpowers 
Lucas Cavalcanti & Edward Wible
2 
Why build a bank 
from scratch 
in Brazil 
using Clojure and 
Datomic?
3
3
4 
1 Audit Trail
5 
event stream 
01 NOV 10:00 Customer joins waiting list for a card
5 
event stream 
01 NOV 11:00 Robot 437aae3 approves R$3K limit 
01 NOV 10:00 Customer joins waiting list for a card
5 
event stream 
09 NOV 08:00 Mastercard purchase, Starbucks, R$100 
01 NOV 11:00 Robot 437aae3 approves R$3K limit 
01 NOV 10:00 Customer joins waiting list for a card
5 
event stream 
15 NOV 15:00 Support agent increases limit to R$5K 
09 NOV 08:00 Mastercard purchase, Starbucks, R$100 
01 NOV 11:00 Robot 437aae3 approves R$3K limit 
01 NOV 10:00 Customer joins waiting list for a card
5 
event stream 
15 NOV 17:05 Customer blocks card 
15 NOV 15:00 Support agent increases limit to R$5K 
09 NOV 08:00 Mastercard purchase, Starbucks, R$100 
01 NOV 11:00 Robot 437aae3 approves R$3K limit 
01 NOV 10:00 Customer joins waiting list for a card
facts over time 
6 
15 NOV 17:05 
15 NOV 15:00 
09 NOV 08:00 
01 NOV 11:00 
01 NOV 10:00 
[<card> :card/status :card.status/blocked] 
[<card> :card/status :card.status/active] 
[<account> :account/limit 5000] 
[<account> :account/limit 3000] 
[<purchase> :purchase/card <card>] 
[<purchase> :purchase/amount 100] 
[<purchase> :purchase/merchant “Starbucks”] 
[<account> :account/customer <customer>] 
[<account> :account/limit 3000] 
[<card> :card/account <account>] 
[<card> :card/status :card.status/active] 
[<customer> :customer/id #uuid “b2c90…”]
facts over time 
6 
15 NOV 17:05 
15 NOV 15:00 
09 NOV 08:00 
01 NOV 11:00 
01 NOV 10:00 
[<card> :card/status :card.status/blocked] 
[<card> :card/status :card.status/active] 
[<account> :account/limit 5000] 
[<account> :account/limit 3000] 
[<purchase> :purchase/card <card>] 
[<purchase> :purchase/amount 100] 
[<purchase> :purchase/merchant “Starbucks”] 
[<account> :account/customer <customer>] 
[<account> :account/limit 3000] 
[<card> :card/account <account>] 
[<card> :card/status :card.status/active] 
[<customer> :customer/id #uuid “b2c90…”] 
entity attribute value
7
7 
don’t 
lose data
7 
don’t 
lose data 
what 
changed 
when
7 
don’t 
lose data 
what 
changed 
when 
as-of 
since
7 
don’t 
lose data 
what 
changed 
when 
as-of 
since 
fork 
merge
8 
What was the initial limit for the card?
8 
What was the initial limit for the card? 
At the time the Starbucks transaction occurred, which 
fraud triggers would have activated?
8 
What was the initial limit for the card? 
At the time the Starbucks transaction occurred, which 
fraud triggers would have activated? 
How long did the customer spend on each stage of the 
acquisition funnel?
8 
What was the initial limit for the card? 
At the time the Starbucks transaction occurred, which 
fraud triggers would have activated? 
How long did the customer spend on each stage of the 
acquisition funnel? 
How frequently do we see amount changes on 
Starbucks transactions?
9 
What sequence of 
events resulted in the 
Starbucks transaction 
being persisted?
9 
What sequence of 
events resulted in the 
Starbucks transaction 
being persisted? 
iOS 
http-in 
kafka-out 
kafka-in 
iZb 
iZb.jnA 
iZb.jnA.9Cd 
iZb.jnA.9Cd.l9A
9 
iOS 
http-in 
kafka-out 
kafka-in 
iZb 
iZb.jnA 
iZb.jnA.9Cd 
iZb.jnA.9Cd.l9A 
What sequence of 
events resulted in the 
Starbucks transaction 
being persisted? correlation-id
Who was responsible 
for the credit limit 
increase? 
9 
iOS 
http-in 
kafka-out 
kafka-in 
iZb 
iZb.jnA 
iZb.jnA.9Cd 
iZb.jnA.9Cd.l9A 
What sequence of 
events resulted in the 
Starbucks transaction 
being persisted? correlation-id
Who was responsible 
for the credit limit 
increase? 
9 
iOS 
http-in 
kafka-out 
kafka-in 
iZb 
iZb.jnA 
iZb.jnA.9Cd 
iZb.jnA.9Cd.l9A 
What sequence of 
events resulted in the 
Starbucks transaction 
being persisted? correlation-id 
#uuid “b2c90…” 
“lucas@nubank.com.br” 
:kafka-LIMIT-CHANGED 
:robot-437aae3
Who was responsible 
for the credit limit 
increase? 
9 
iOS 
http-in 
kafka-out 
kafka-in 
iZb 
iZb.jnA 
iZb.jnA.9Cd 
iZb.jnA.9Cd.l9A 
What sequence of 
events resulted in the 
Starbucks transaction 
being persisted? correlation-id 
user 
#uuid “b2c90…” 
“lucas@nubank.com.br” 
:kafka-LIMIT-CHANGED 
:robot-437aae3
Who was responsible 
for the credit limit 
increase? 
9 
What sequence of 
events resulted in the 
Starbucks transaction 
being persisted? 
Why was the 
customer’s card 
blocked? 
correlation-id 
iOS 
http-in 
kafka-out 
kafka-in 
iZb 
iZb.jnA 
iZb.jnA.9Cd 
iZb.jnA.9Cd.l9A 
user 
#uuid “b2c90…” 
“lucas@nubank.com.br” 
:kafka-LIMIT-CHANGED 
:robot-437aae3
Who was responsible 
for the credit limit 
increase? 
9 
What sequence of 
events resulted in the 
Starbucks transaction 
being persisted? 
Why was the 
customer’s card 
blocked? 
correlation-id 
iOS 
http-in 
kafka-out 
kafka-in 
iZb 
iZb.jnA 
iZb.jnA.9Cd 
iZb.jnA.9Cd.l9A 
:fraud-preventative 
:recurring-scheduled 
:late-payment 
:data-migration 
user 
#uuid “b2c90…” 
“lucas@nubank.com.br” 
:kafka-LIMIT-CHANGED 
:robot-437aae3
Who was responsible 
for the credit limit 
increase? 
9 
What sequence of 
events resulted in the 
Starbucks transaction 
being persisted? 
Why was the 
customer’s card 
blocked? 
correlation-id 
iOS 
http-in 
kafka-out 
kafka-in 
iZb 
iZb.jnA 
iZb.jnA.9Cd 
iZb.jnA.9Cd.l9A 
user 
#uuid “b2c90…” 
“lucas@nubank.com.br” 
:kafka-LIMIT-CHANGED 
:robot-437aae3 
:fraud-preventative reason 
:recurring-scheduled 
:late-payment 
:data-migration
(defn block-card [card reason] 
(d/transact 
(conn) 
[[:db/add card :card/status :card.status/blocked] 
])) 
10 
storing transaction metadata
(defn block-card [card reason] 
(d/transact 
(conn) 
[[:db/add card :card/status :card.status/blocked] 
{:db/id (d/tempid :db.part/tx) 
:audit/user "lucas@nubank.com.br" 
:audit/cid "iZb.jnA.9Cd.l9A" 
:audit/tags :fraud-preventative} 
])) 
10 
storing transaction metadata
11 
transactions over time with metadata 
[<card> :card/status :card.status/blocked] 
[<card> :card/status :card.status/active] 15 NOV 17:05 
[<account> :account/limit 5000M] 
15 NOV 15:00 [<account> :account/limit 3000M] 
[<purchase> :purchase/card <card>] 
[<purchase> :purchase/amount 100M] 
[<purchase> :purchase/merchant “Starbucks”] 09 NOV 08:00 
[<account> :account/customer <customer>] 
[<account> :account/limit 3000M] 
[<card> :card/account <account>] 
[<card> :card/status :card.status/active] 
01 NOV 11:00 
01 NOV 10:00 [<customer> :customer/id #uuid “b2c90…”]
11 
transactions over time with metadata 
[<card> :card/status :card.status/blocked] 
[<card> :card/status :card.status/active] 15 NOV 17:05 
[<account> :account/limit 5000M] 
15 NOV 15:00 [<account> :account/limit 3000M] 
[<purchase> :purchase/card <card>] 
[<purchase> :purchase/amount 100M] 
[<purchase> :purchase/merchant “Starbucks”] 09 NOV 08:00 
[<account> :account/customer <customer>] 
[<account> :account/limit 3000M] 
[<card> :card/account <account>] 
[<card> :card/status :card.status/active] 
01 NOV 11:00 
01 NOV 10:00 [<customer> :customer/id #uuid “b2c90…”]
12 
querying transaction metadata 
(defn who-blocked-the-card? [card] 
(d/q '{:find [?user ?cid ?tags] 
:in [$ ?status ?card] 
:where [[?card :card/status ?status ?tx] 
[?tx :audit/user ?user] 
[?tx :audit/cid ?cid] 
[?tx :audit/tags ?tags]]} 
db :card.status/blocked card))
12 
querying transaction metadata 
(defn who-blocked-the-card? [card] 
(d/q '{:find [?user ?cid ?tags] 
:in [$ ?status ?card] 
:where [[?entity attribute value txn 
[?card :card/status ?status ?tx] 
[?tx :audit/user ?user] 
[?tx :audit/cid ?cid] 
[?tx :audit/tags ?tags]]} 
db :card.status/blocked card))
12 
querying transaction metadata 
(defn who-blocked-the-card? [card] 
(d/q '{:find [?user ?cid ?tags] 
:in [$ ?status ?card] 
:where [[?card :card/status ?status ?tx] 
[?tx :audit/user ?user] 
[?tx :audit/cid ?cid] 
[?tx :audit/tags ?tags]]} 
db :card.status/blocked card))
12 
querying transaction metadata 
(defn who-blocked-the-card? [card] 
(d/q '{:find [?user ?cid ?tags] 
:in [$ ?status ?card] 
:where [[?card :card/status ?status ?tx] 
[?tx :audit/user ?user] 
[?tx :audit/cid ?cid] 
[?tx :audit/tags ?tags]]} 
db :card.status/blocked card))
12 
querying transaction metadata 
(defn who-blocked-the-card? [card] 
(d/q '{:find [?user ?cid ?tags] 
:in [$ ?status ?card] 
:where [[?card :card/status ?status ?tx] 
[?tx :audit/user ?user] 
[?tx :audit/cid ?cid] 
[?tx :audit/tags ?tags]]} 
db :card.status/blocked card))
13 
2 Authorization
should we return data? 
GET /purchases/1337/comments
should we return data? 
GET /purchases/1337/comments 
200 OK 401 Unauthorized
customer 
account 
payment 
card purchase 
comment 
should we return data? 
GET /purchases/1337/comments 
200 OK 401 Unauthorized
customer 
account 
payment 
card purchase 
comment 
should we return data? 
GET /purchases/1337/comments 
200 OK 401 Unauthorized
customer 
account 
payment 
card purchase 
comment 
should we return data? 
GET /purchases/1337/comments 
200 OK 401 Unauthorized
15 
explicit relationship traversal 
(defn owns? [customer-id purchase-id db] 
(d/q '{:find [?pur .] 
:in [$ ?customer-id ?purchase-id] 
:where [[?pur :purchase/id ?purchase-id] 
[?pur :purchase/account ?acc] 
[?acc :account/customer ?cus] 
[?cus :customer/id ?customer-id]]} 
db customer-id purchase-id))
15 
explicit relationship traversal 
(defn owns? [customer-id purchase-id db] 
(d/q '{:find [?pur .] 
:in [$ ?customer-id ?purchase-id] 
:where [[?pur :purchase/id ?purchase-id] 
[?pur :purchase/account ?acc] 
[?acc :account/customer ?cus] 
[?cus :customer/id ?customer-id]]} 
db customer-id purchase-id))
generic relationship traversal 
16 
recursive rule 
(logical OR)
generic relationship traversal 
16 
recursive rule 
(logical OR) 
(def owner-rules 
'[[(owns? ?cus-id ?e) 
[?e :customer/id ?cus-id]] 
[(owns? ?cus-id ?e) 
[?e ?ref-attr ?r] 
(owns? ?cus-id ?r)]])
generic relationship traversal 
16 
recursive rule 
(logical OR) 
(def owner-rules 
'[[(owns? ?cus-id ?e) 
[?e :customer/id ?cus-id]] 
[(owns? ?cus-id ?e) 
[?e ?ref-attr ?r] 
(owns? ?cus-id ?r)]])
generic relationship traversal 
16 
recursive rule 
(logical OR) 
(def owner-rules 
'[[(owns? ?cus-id ?e) 
[?e :customer/id ?cus-id]] 
[(owns? ?cus-id ?e) 
[?e ?ref-attr ?r] 
(owns? ?cus-id ?r)]])
generic relationship traversal 
16 
recursive rule 
(logical OR) 
(def owner-rules 
'[[(owns? ?cus-id ?e) 
[?e :customer/id ?cus-id]] 
[(owns? ?cus-id ?e) 
[?e ?ref-attr ?r] 
(owns? ?cus-id ?r)]])
generic relationship traversal 
16 
recursive rule 
(logical OR) 
(def owner-rules 
'[[(owns? ?cus-id ?e) 
[?e :customer/id ?cus-id]] 
[(owns? ?cus-id ?e) 
[?e ?ref-attr ?r] 
(owns? ?cus-id ?r)]])
generic relationship traversal 
16 
recursive rule 
(logical OR) 
(def owner-rules 
'[[(owns? ?cus-id ?e) 
[?e :customer/id ?cus-id]] 
[(owns? ?cus-id ?e) 
[?e ?ref-attr ?r] 
(owns? ?cus-id ?r)]])
generic relationship traversal 
16 
recursive rule 
(logical OR) 
query 
using rule 
(def owner-rules 
'[[(owns? ?cus-id ?e) 
[?e :customer/id ?cus-id]] 
[(owns? ?cus-id ?e) 
[?e ?ref-attr ?r] 
(owns? ?cus-id ?r)]])
generic relationship traversal 
:in [$ ?cus-id ?e %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id entity 
owner-rules)) 
16 
recursive rule 
(logical OR) 
query 
using rule 
(def owner-rules 
'[[(owns? ?cus-id ?e) 
[?e :customer/id ?cus-id]] 
[(owns? ?cus-id ?e) 
[?e ?ref-attr ?r] 
(owns? ?cus-id ?r)]]) 
(defn owns? [customer-id entity db] 
(d/q '{:find [?e .] 
(owns? customer-id [:purchase/id id] db)
generic relationship traversal 
:in [$ ?cus-id ?e %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id entity 
owner-rules)) 
16 
recursive rule 
(logical OR) 
query 
using rule 
(def owner-rules 
'[[(owns? ?cus-id ?e) 
[?e :customer/id ?cus-id]] 
[(owns? ?cus-id ?e) 
[?e ?ref-attr ?r] 
(owns? ?cus-id ?r)]]) 
(defn owns? [customer-id entity db] 
(d/q '{:find [?e .] 
(owns? customer-id [:purchase/id id] db)
17 
Recursion rules! 
But can we go further? 
Can we prevent queries from 
returning entities owned by 
other customers?
all owned entities 
18 
query binding 
attribute
all owned entities 
18 
query binding 
attribute 
(defn owns? [customer-id entity db] 
(d/q '{:find [?e .] 
:in [$ ?cus-id ?e %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id entity] 
owner-rules))
all owned entities 
18 
query binding 
attribute 
(defn owns? [customer-id entity db] 
(d/q '{:find [?e .] 
:in [$ ?cus-id ?e %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id entity] 
owner-rules))
all owned entities 
18 
query binding 
attribute 
attribute 
unbound 
(defn owns? [customer-id entity db] 
(d/q '{:find [?e .] 
:in [$ ?cus-id ?e %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id entity] 
owner-rules))
all owned entities 
18 
query binding 
attribute 
attribute 
unbound 
(defn owns? [customer-id entity db] 
(d/q '{:find [?e .] 
:in [$ ?cus-id ?e %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id entity] 
owner-rules)) 
(defn owned-entities [customer-id db] 
(d/q '{:find [[?e ...]] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id owner-rules))
all owned entities 
18 
query binding 
attribute 
attribute 
unbound 
(defn owns? [customer-id entity db] 
(d/q '{:find [?e .] 
:in [$ ?cus-id ?e %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id entity] 
owner-rules)) 
(defn owned-entities [customer-id db] 
(d/q '{:find [[?e ...]] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id owner-rules))
all owned entities 
18 
query binding 
attribute 
attribute 
unbound 
(defn owns? [customer-id entity db] 
(d/q '{:find [?e .] 
:in [$ ?cus-id ?e %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id entity] 
owner-rules)) 
(defn owned-entities [customer-id db] 
(d/q '{:find [[?e ...]] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e)]} 
db customer-id owner-rules))
filtered db (as value) 
19 
(defn owned-db [customer-id db] 
(let [owned (set (owned-entities customer-id db))] 
(d/filter db 
(fn [_ [e a v t :as datom]] 
(when (or (= e t) 
(contains? owned e)) 
datom)))))
filtered db (as value) 
19 
(defn owned-db [customer-id db] 
(let [owned (set (owned-entities customer-id db))] 
(d/filter db 
(fn [_ [e a v t :as datom]] 
(when (or (= e t) 
(contains? owned e)) 
datom)))))
filtered db (as value) 
19 
(defn owned-db [customer-id db] 
(let [owned (set (owned-entities customer-id db))] 
(d/filter db 
(fn [_ [e a v t :as datom]] 
(when (or (= e t) 
(contains? owned e)) 
datom)))))
filtered db (as value) 
19 
(defn owned-db [customer-id db] 
(let [owned (set (owned-entities customer-id db))] 
(d/filter db 
(fn [_ [e a v t :as datom]] 
(when (or (= e t) 
(contains? owned e)) 
datom)))))
filtered db (as value) 
19 
(defn owned-db [customer-id db] 
(let [owned (set (owned-entities customer-id db))] 
(d/filter db 
(fn [_ [e a v t :as datom]] 
(when (or (= e t) 
(contains? owned e)) 
datom)))))
filtered db (as value) 
(let [owned (set (owned-entities customer-id db))] 
(d/filter db 
19 
(defn owned-db [customer-id db] 
(fn [_ [e a v t :as datom]] 
(when (or (= e t) 
(contains? owned e)) 
datom))))) 
filtered db will not contain datoms 
from other owners!
passing a filtered db is transparent for queries 
20 
(defn all-purchases [account-id db] 
(d/q '{:find [[(pull ?p [*]) ...]] 
:in [$ ?acc] 
:where [[?p :purchase/account ?acc]]} 
db [:account/id account-id])) 
(all-purchases account-id db) 
(all-purchases account-id (owned-db customer-id db))
passing a filtered db is transparent for queries 
20 
(defn all-purchases [account-id db] 
(d/q '{:find [[(pull ?p [*]) ...]] 
:in [$ ?acc] 
:where [[?p :purchase/account ?acc]]} 
db [:account/id account-id])) 
(all-purchases account-id db) 
(all-purchases account-id (owned-db customer-id db))
21 
We’re Mobile! 
Bandwidth is costly
22 
3 HTTP Cache
23 
http last modified header 
GET /accounts/1234/purchases 
200 OK 
Last-Modified: Fri, 14 Nov 2014 14:28:50 UTC 
{"purchases": [...]}
http last modified header 
If-Modified-Since: Fri, 14 Nov 2014 14:28:50 UTC 
GET /accounts/1234/purchases 
304 Not Modified 
23 
GET /accounts/1234/purchases 
200 OK 
Last-Modified: Fri, 14 Nov 2014 14:28:50 UTC 
{"purchases": [...]}
24 
How to keep track of a 
good last-modified date 
for a URL?
24 
How to keep track of a 
good last-modified date 
for a URL? 
The last time any owned 
entity changed!
If no customer-owned data changed, 304 
25 
(defn last-modified [customer-id db] 
(d/q '{:find [(max ?time) .] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e) 
[?e ?a ?v ?tx] 
[?tx :db/txInstant ?time]]} 
db customer-id owner-rules))
If no customer-owned data changed, 304 
25 
(defn last-modified [customer-id db] 
(d/q '{:find [(max ?time) .] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e) 
[?e ?a ?v ?tx] 
[?tx :db/txInstant ?time]]} 
db customer-id owner-rules))
If no customer-owned data changed, 304 
25 
(defn last-modified [customer-id db] 
(d/q '{:find [(max ?time) .] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e) 
[?e ?a ?v ?tx] 
[?tx :db/txInstant ?time]]} 
db customer-id owner-rules))
If no customer-owned data changed, 304 
25 
(defn last-modified [customer-id db] 
(d/q '{:find [(max ?time) .] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e) 
[?e ?a ?v ?tx] 
[?tx :db/txInstant ?time]]} 
db customer-id owner-rules))
If no customer-owned data changed, 304 
25 
(defn last-modified [customer-id db] 
(d/q '{:find [(max ?time) .] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e) 
[?e ?a ?v ?tx] 
[?tx :db/txInstant ?time]]} 
db customer-id owner-rules))
If no customer-owned data changed, 304 
25 
(defn last-modified [customer-id db] 
(d/q '{:find [(max ?time) .] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e) 
[?e ?a ?v ?tx] 
[?tx :db/txInstant ?time]]} 
db customer-id owner-rules))
If no customer-owned data changed, 304 
25 
(defn last-modified [customer-id db] 
(d/q '{:find [(max ?time) .] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e) 
[?e ?a ?v ?tx] 
[?tx :db/txInstant ?time]]} 
db customer-id owner-rules))
If no customer-owned data changed, 304 
25 
(defn last-modified [customer-id db] 
(d/q '{:find [(max ?time) .] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e) 
[?e ?a ?v ?tx] 
[?tx :db/txInstant ?time]]} 
db customer-id owner-rules))
If no customer-owned data changed, 304 
25 
(defn last-modified [customer-id db] 
(d/q '{:find [(max ?time) .] 
:in [$ ?cus-id %] 
:where [(owns? ?cus-id ?e) 
[?e ?a ?v ?tx] 
[?tx :db/txInstant ?time]]} 
db customer-id owner-rules))
26 
Cache hits are awesome, 
but what about cache 
misses?
27 
4 Mobile Sync
desired hypermedia from the API 
28 
GET /accounts/1223/purchases 
200 OK 
{"purchases": [...] 
"_links": { 
"updates": "https://...?since=2014-11-10T11:12:13Z" 
} 
}
serve purchases added or modified since our last sync 
29 
(defn updated-purchases [account-id db since] 
(d/q '{:find [[(pull ?pur [*]) ...]] 
:in [$ $since ?acc] 
:where [[?pur :purchase/account ?acc] 
[$since ?pur]]} 
db 
(d/history (d/since db since)) 
[:account/id account-id])) 
(updated-purchases account-id db #inst "2014-11-14"))
serve purchases added or modified since our last sync 
29 
(defn updated-purchases [account-id db since] 
(d/q '{:find [[(pull ?pur [*]) ...]] 
:in [$ $since ?acc] 
:where [[?pur :purchase/account ?acc] 
[$since ?pur]]} 
db 
(d/history (d/since db since)) 
[:account/id account-id])) 
(updated-purchases account-id db #inst "2014-11-14"))
serve purchases added or modified since our last sync 
29 
(defn updated-purchases [account-id db since] 
(d/q '{:find [[(pull ?pur [*]) ...]] 
:in [$ $since ?acc] 
:where [[?pur :purchase/account ?acc] 
[$since ?pur]]} 
db 
(d/history (d/since db since)) 
[:account/id account-id])) 
(updated-purchases account-id db #inst "2014-11-14"))
serve purchases added or modified since our last sync 
29 
(defn updated-purchases [account-id db since] 
(d/q '{:find [[(pull ?pur [*]) ...]] 
:in [$ $since ?acc] 
:where [[?pur :purchase/account ?acc] 
[$since ?pur]]} 
db 
(d/history (d/since db since)) 
[:account/id account-id])) 
(updated-purchases account-id db #inst "2014-11-14"))
serve purchases added or modified since our last sync 
29 
(defn updated-purchases [account-id db since] 
(d/q '{:find [[(pull ?pur [*]) ...]] 
:in [$ $since ?acc] 
:where [[?pur :purchase/account ?acc] 
[$since ?pur]]} 
db 
(d/history (d/since db since)) 
[:account/id account-id])) 
(updated-purchases account-id db #inst "2014-11-14"))
serve purchases added or modified since our last sync 
29 
(defn updated-purchases [account-id db since] 
(d/q '{:find [[(pull ?pur [*]) ...]] 
:in [$ $since ?acc] 
:where [[?pur :purchase/account ?acc] 
[$since ?pur]]} 
db 
(d/history (d/since db since)) 
[:account/id account-id])) 
(updated-purchases account-id db #inst "2014-11-14"))
serve purchases added or modified since our last sync 
29 
(defn updated-purchases [account-id db since] 
(d/q '{:find [[(pull ?pur [*]) ...]] 
:in [$ $since ?acc] 
:where [[?pur :purchase/account ?acc] 
[$since ?pur]]} 
db 
(d/history (d/since db since)) 
[:account/id account-id])) 
(updated-purchases account-id db #inst "2014-11-14"))
30 
Bonus
31 
5 Future DBs
generating a virtual db 
(defn db-with-virtual-charges [purchases db] 
(:db-after 
(d/with db 
(mapcat purchase->virtual-charges purchases)))) 
32
generating a virtual db 
(defn db-with-virtual-charges [purchases db] 
(:db-after 
(d/with db 
(mapcat purchase->virtual-charges purchases)))) 
32
generating a virtual db 
(defn db-with-virtual-charges [purchases db] 
(:db-after 
(d/with db 
(mapcat purchase->virtual-charges purchases)))) 
32
generating a virtual db 
(defn db-with-virtual-charges [purchases db] 
(:db-after 
(d/with db 
(mapcat purchase->virtual-charges purchases)))) 
32
generating a virtual db 
(defn db-with-virtual-charges [purchases db] 
(:db-after 
(d/with db 
(mapcat purchase->virtual-charges purchases)))) 
32
33 
6 Testing
db testing - locally scoped 
34 
(defn virtual-db [updates db] 
(:db-after 
(d/with db updates))) 
(let [one-account {...} 
one-card {...} 
one-purchase {...} 
wrong-purchase {...} 
db (virtual-db [one-account one-card 
one-purchase wrong-purchase] db)] 
(my-weird-db-query db) => [one-purchase])
db testing - locally scoped 
34 
(defn virtual-db [updates db] 
(:db-after 
(d/with db updates))) 
(let [one-account {...} 
one-card {...} 
one-purchase {...} 
wrong-purchase {...} 
db (virtual-db [one-account one-card 
one-purchase wrong-purchase] db)] 
(my-weird-db-query db) => [one-purchase])
db testing - locally scoped 
34 
(defn virtual-db [updates db] 
(:db-after 
(d/with db updates))) 
(let [one-account {...} 
one-card {...} 
one-purchase {...} 
wrong-purchase {...} 
db (virtual-db [one-account one-card 
one-purchase wrong-purchase] db)] 
(my-weird-db-query db) => [one-purchase])
db testing - locally scoped 
34 
(defn virtual-db [updates db] 
(:db-after 
(d/with db updates))) 
(let [one-account {...} 
one-card {...} 
one-purchase {...} 
wrong-purchase {...} 
db (virtual-db [one-account one-card 
one-purchase wrong-purchase] db)] 
(my-weird-db-query db) => [one-purchase])
db testing - locally scoped 
34 
(defn virtual-db [updates db] 
(:db-after 
(d/with db updates))) 
(let [one-account {...} 
one-card {...} 
one-purchase {...} 
wrong-purchase {...} 
db (virtual-db [one-account one-card 
one-purchase wrong-purchase] db)] 
(my-weird-db-query db) => [one-purchase])
db testing - locally scoped 
34 
(defn virtual-db [updates db] 
(:db-after 
(d/with db updates))) 
(let [one-account {...} 
one-card {...} 
one-purchase {...} 
wrong-purchase {...} 
db (virtual-db [one-account one-card 
one-purchase wrong-purchase] db)] 
(my-weird-db-query db) => [one-purchase])
db testing - vector of datoms 
35 
(fact "on last-modification-for" 
(let [customer-id (uuid) 
db [[42 :account/customer-id customer-id 99] 
[99 :db/txInstant #inst "2014-10-10T12:00:00.909Z" 99] 
[1337 :purchase/account 42 100] 
[1337 :purchase/id (uuid) 100] 
[1337 :purchase/merchant-name "Coiso" 100] 
[100 :db/txInstant #inst "2014-10-11T12:00:00.888Z" 100] 
[9001 :line-item/account 42 101] 
[9001 :charge/id (uuid) 101] 
[9001 :line-item/precise-amount 100.00M 101] 
[101 :db/txInstant #inst "2014-10-12T13:00:00.777Z" 101] 
[45 :account/customer-id (uuid) 102] 
[102 :db/txInstant #inst "2014-10-15T12:00:00.909Z" 102]]] 
(last-modification-for customer-id db) => #nu/time "2014-10-12T13:00:00.777Z"))
db testing - vector of datoms 
35 
(fact "on last-modification-for" 
(let [customer-id (uuid) 
db [[42 :account/customer-id customer-id 99] 
[99 :db/txInstant #inst "2014-10-10T12:00:00.909Z" 99] 
[1337 :purchase/account 42 100] 
[1337 :purchase/id (uuid) 100] 
[1337 :purchase/merchant-name "Coiso" 100] 
[100 :db/txInstant #inst "2014-10-11T12:00:00.888Z" 100] 
[9001 :line-item/account 42 101] 
[9001 :charge/id (uuid) 101] 
[9001 :line-item/precise-amount 100.00M 101] 
[101 :db/txInstant #inst "2014-10-12T13:00:00.777Z" 101] 
[45 :account/customer-id (uuid) 102] 
[102 :db/txInstant #inst "2014-10-15T12:00:00.909Z" 102]]] 
(last-modification-for customer-id db) => #nu/time "2014-10-12T13:00:00.777Z"))
36 
7 Schema Extension
schema is data! 
37 
(d/transact conn [{:db/valueType :db.type/string, 
:db.install/_attribute :db.part/db, 
:db/id (d/tempid :db.part/db), 
:db/cardinality :db.cardinality/one, 
:db/ident :customer/name 
}])
:nubank/transform :pii 
schema is data! 
37 
(d/transact conn [{:db/valueType :db.type/string, 
:db.install/_attribute :db.part/db, 
:db/id (d/tempid :db.part/db), 
:db/cardinality :db.cardinality/one, 
:db/ident :customer/name 
}])
38 
8 Sharding Reads
39 
sharding to the transactor 
customers 
accounts 
notification 
acquisition 
processor 
auth
39 
sharding to the transactor 
customers 
accounts 
notification 
acquisition 
processor 
auth
customers 
1 2 … 
39 
sharding to the transactor 
customers 
accounts 
notification 
acquisition 
processor 
auth
customers 
1 
2 … 
39 
sharding to the transactor 
customers 
accounts 
notification 
acquisition 
processor 
auth 
shard-specific peer cache
customers 
1 
2 
… 
39 
sharding to the transactor 
customers 
accounts 
notification 
acquisition 
processor 
auth 
shard-specific peer cache 
inter-shard ACID transactions
40 
9 Db Aggregation
41 
data science aggregation 
customers 
accounts 
notification 
acquisition 
processor 
auth 
data 
science
42 
querying multiple databases 
(defn multi-join [customer-id cus-db acc-db acq-db ntf-db] 
(d/q '{:find [...] 
:in [$cus $acc $acq $ntf ?cus-id] 
:where [[$cus ?cus :customer/id ?cus-id] 
[$acc ?acc :account/customer-id ?cus-id] 
[$acq ?ar :request/customer-id ?cus-id] 
[$ntf ?evt :event/customer-id ?cus-id] 
[...]]} 
cus-db acc-db acq-db ntf-db customer-id))
42 
querying multiple databases 
(defn multi-join [customer-id cus-db acc-db acq-db ntf-db] 
(d/q '{:find [...] 
:in [$cus $acc $acq $ntf ?cus-id] 
:where [[$cus ?cus :customer/id ?cus-id] 
[$acc ?acc :account/customer-id ?cus-id] 
[$acq ?ar :request/customer-id ?cus-id] 
[$ntf ?evt :event/customer-id ?cus-id] 
[...]]} 
cus-db acc-db acq-db ntf-db customer-id))
43 
1 Audit Trail 
2 Authorization 
3 HTTP Cache 
4 Mobile Sync 
5 Future DBs 
6 Testing 
7 Schema Extension 
8 Sharding Reads 
9 Db Aggregation
44 
We’re hiring
45 
We’re hiring
Thanks! 
Lucas Cavalcanti & Edward Wible 
@lucascs 
lucas@nubank.com.br 
edward@nubank.com.br
1 of 131

Recommended

Low latency in java 8 by Peter Lawrey by
Low latency in java 8 by Peter Lawrey Low latency in java 8 by Peter Lawrey
Low latency in java 8 by Peter Lawrey J On The Beach
9.3K views54 slides
NuBank - Hyperlocal Banking by
NuBank - Hyperlocal BankingNuBank - Hyperlocal Banking
NuBank - Hyperlocal BankingTristan Gomez
19.4K views23 slides
Microservices in Clojure by
Microservices in ClojureMicroservices in Clojure
Microservices in ClojureLucas Cavalcanti dos Santos
4.6K views36 slides
Testes em uma startup do mundo financeiro by
Testes em uma startup do mundo financeiroTestes em uma startup do mundo financeiro
Testes em uma startup do mundo financeiroLuiz Alberto Hespanha
3.2K views36 slides
Building a powerful double entry accounting system by
Building a powerful double entry accounting systemBuilding a powerful double entry accounting system
Building a powerful double entry accounting systemLucas Cavalcanti dos Santos
4.4K views34 slides
Complex made bearable Clojure conj 2019 by
Complex made bearable Clojure conj 2019Complex made bearable Clojure conj 2019
Complex made bearable Clojure conj 2019Lucas Cavalcanti dos Santos
1.6K views39 slides

More Related Content

Recently uploaded

Roadmap y Novedades de producto by
Roadmap y Novedades de productoRoadmap y Novedades de producto
Roadmap y Novedades de productoNeo4j
50 views33 slides
SUGCON ANZ Presentation V2.1 Final.pptx by
SUGCON ANZ Presentation V2.1 Final.pptxSUGCON ANZ Presentation V2.1 Final.pptx
SUGCON ANZ Presentation V2.1 Final.pptxJack Spektor
22 views34 slides
Tridens DevOps by
Tridens DevOpsTridens DevOps
Tridens DevOpsTridens
9 views28 slides
DSD-INT 2023 Modelling litter in the Yarra and Maribyrnong Rivers (Australia)... by
DSD-INT 2023 Modelling litter in the Yarra and Maribyrnong Rivers (Australia)...DSD-INT 2023 Modelling litter in the Yarra and Maribyrnong Rivers (Australia)...
DSD-INT 2023 Modelling litter in the Yarra and Maribyrnong Rivers (Australia)...Deltares
9 views34 slides
SAP FOR TYRE INDUSTRY.pdf by
SAP FOR TYRE INDUSTRY.pdfSAP FOR TYRE INDUSTRY.pdf
SAP FOR TYRE INDUSTRY.pdfVirendra Rai, PMP
23 views3 slides
SAP FOR CONTRACT MANUFACTURING.pdf by
SAP FOR CONTRACT MANUFACTURING.pdfSAP FOR CONTRACT MANUFACTURING.pdf
SAP FOR CONTRACT MANUFACTURING.pdfVirendra Rai, PMP
11 views2 slides

Recently uploaded(20)

Roadmap y Novedades de producto by Neo4j
Roadmap y Novedades de productoRoadmap y Novedades de producto
Roadmap y Novedades de producto
Neo4j50 views
SUGCON ANZ Presentation V2.1 Final.pptx by Jack Spektor
SUGCON ANZ Presentation V2.1 Final.pptxSUGCON ANZ Presentation V2.1 Final.pptx
SUGCON ANZ Presentation V2.1 Final.pptx
Jack Spektor22 views
Tridens DevOps by Tridens
Tridens DevOpsTridens DevOps
Tridens DevOps
Tridens9 views
DSD-INT 2023 Modelling litter in the Yarra and Maribyrnong Rivers (Australia)... by Deltares
DSD-INT 2023 Modelling litter in the Yarra and Maribyrnong Rivers (Australia)...DSD-INT 2023 Modelling litter in the Yarra and Maribyrnong Rivers (Australia)...
DSD-INT 2023 Modelling litter in the Yarra and Maribyrnong Rivers (Australia)...
Deltares9 views
DSD-INT 2023 Thermobaricity in 3D DCSM-FM - taking pressure into account in t... by Deltares
DSD-INT 2023 Thermobaricity in 3D DCSM-FM - taking pressure into account in t...DSD-INT 2023 Thermobaricity in 3D DCSM-FM - taking pressure into account in t...
DSD-INT 2023 Thermobaricity in 3D DCSM-FM - taking pressure into account in t...
Deltares9 views
Consulting for Data Monetization Maximizing the Profit Potential of Your Data... by Flexsin
Consulting for Data Monetization Maximizing the Profit Potential of Your Data...Consulting for Data Monetization Maximizing the Profit Potential of Your Data...
Consulting for Data Monetization Maximizing the Profit Potential of Your Data...
Flexsin 15 views
DSD-INT 2023 Dam break simulation in Derna (Libya) using HydroMT_SFINCS - Prida by Deltares
DSD-INT 2023 Dam break simulation in Derna (Libya) using HydroMT_SFINCS - PridaDSD-INT 2023 Dam break simulation in Derna (Libya) using HydroMT_SFINCS - Prida
DSD-INT 2023 Dam break simulation in Derna (Libya) using HydroMT_SFINCS - Prida
Deltares18 views
Upgrading Incident Management with Icinga - Icinga Camp Milan 2023 by Icinga
Upgrading Incident Management with Icinga - Icinga Camp Milan 2023Upgrading Incident Management with Icinga - Icinga Camp Milan 2023
Upgrading Incident Management with Icinga - Icinga Camp Milan 2023
Icinga38 views
BushraDBR: An Automatic Approach to Retrieving Duplicate Bug Reports by Ra'Fat Al-Msie'deen
BushraDBR: An Automatic Approach to Retrieving Duplicate Bug ReportsBushraDBR: An Automatic Approach to Retrieving Duplicate Bug Reports
BushraDBR: An Automatic Approach to Retrieving Duplicate Bug Reports
Neo4j y GenAI by Neo4j
Neo4j y GenAI Neo4j y GenAI
Neo4j y GenAI
Neo4j42 views
DSD-INT 2023 Simulation of Coastal Hydrodynamics and Water Quality in Hong Ko... by Deltares
DSD-INT 2023 Simulation of Coastal Hydrodynamics and Water Quality in Hong Ko...DSD-INT 2023 Simulation of Coastal Hydrodynamics and Water Quality in Hong Ko...
DSD-INT 2023 Simulation of Coastal Hydrodynamics and Water Quality in Hong Ko...
Deltares11 views
Cycleops - Automate deployments on top of bare metal.pptx by Thanassis Parathyras
Cycleops - Automate deployments on top of bare metal.pptxCycleops - Automate deployments on top of bare metal.pptx
Cycleops - Automate deployments on top of bare metal.pptx
DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J... by Deltares
DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J...DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J...
DSD-INT 2023 3D hydrodynamic modelling of microplastic transport in lakes - J...
Deltares9 views
El Arte de lo Possible by Neo4j
El Arte de lo PossibleEl Arte de lo Possible
El Arte de lo Possible
Neo4j38 views
MariaDB stored procedures and why they should be improved by Federico Razzoli
MariaDB stored procedures and why they should be improvedMariaDB stored procedures and why they should be improved
MariaDB stored procedures and why they should be improved

Featured

ChatGPT and the Future of Work - Clark Boyd by
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd Clark Boyd
21.3K views69 slides
Getting into the tech field. what next by
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next Tessa Mero
5.2K views22 slides
Google's Just Not That Into You: Understanding Core Updates & Search Intent by
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentLily Ray
5.9K views99 slides
How to have difficult conversations by
How to have difficult conversations How to have difficult conversations
How to have difficult conversations Rajiv Jayarajah, MAppComm, ACC
4.5K views19 slides
Introduction to Data Science by
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data ScienceChristy Abraham Joy
82.2K views51 slides
Time Management & Productivity - Best Practices by
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best PracticesVit Horky
169.7K views42 slides

Featured(20)

ChatGPT and the Future of Work - Clark Boyd by Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
Clark Boyd21.3K views
Getting into the tech field. what next by Tessa Mero
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
Tessa Mero5.2K views
Google's Just Not That Into You: Understanding Core Updates & Search Intent by Lily Ray
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Lily Ray5.9K views
Time Management & Productivity - Best Practices by Vit Horky
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
Vit Horky169.7K views
The six step guide to practical project management by MindGenius
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
MindGenius36.6K views
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright... by RachelPearson36
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
RachelPearson3612.6K views
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present... by Applitools
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Applitools55.4K views
12 Ways to Increase Your Influence at Work by GetSmarter
12 Ways to Increase Your Influence at Work12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work
GetSmarter401.6K views
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G... by DevGAMM Conference
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
DevGAMM Conference3.6K views
Barbie - Brand Strategy Presentation by Erica Santiago
Barbie - Brand Strategy PresentationBarbie - Brand Strategy Presentation
Barbie - Brand Strategy Presentation
Erica Santiago25.1K views
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them well by Saba Software
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them wellGood Stuff Happens in 1:1 Meetings: Why you need them and how to do them well
Good Stuff Happens in 1:1 Meetings: Why you need them and how to do them well
Saba Software25.2K views
Introduction to C Programming Language by Simplilearn
Introduction to C Programming LanguageIntroduction to C Programming Language
Introduction to C Programming Language
Simplilearn8.4K views
The Pixar Way: 37 Quotes on Developing and Maintaining a Creative Company (fr... by Palo Alto Software
The Pixar Way: 37 Quotes on Developing and Maintaining a Creative Company (fr...The Pixar Way: 37 Quotes on Developing and Maintaining a Creative Company (fr...
The Pixar Way: 37 Quotes on Developing and Maintaining a Creative Company (fr...
Palo Alto Software88.3K views
9 Tips for a Work-free Vacation by Weekdone.com
9 Tips for a Work-free Vacation9 Tips for a Work-free Vacation
9 Tips for a Work-free Vacation
Weekdone.com7.2K views
How to Map Your Future by SlideShop.com
How to Map Your FutureHow to Map Your Future
How to Map Your Future
SlideShop.com275.1K views

Exploring four Datomic superpowers

  • 1. Exploring Four Datomic Superpowers Lucas Cavalcanti & Edward Wible
  • 2. 2 Why build a bank from scratch in Brazil using Clojure and Datomic?
  • 3. 3
  • 4. 3
  • 5. 4 1 Audit Trail
  • 6. 5 event stream 01 NOV 10:00 Customer joins waiting list for a card
  • 7. 5 event stream 01 NOV 11:00 Robot 437aae3 approves R$3K limit 01 NOV 10:00 Customer joins waiting list for a card
  • 8. 5 event stream 09 NOV 08:00 Mastercard purchase, Starbucks, R$100 01 NOV 11:00 Robot 437aae3 approves R$3K limit 01 NOV 10:00 Customer joins waiting list for a card
  • 9. 5 event stream 15 NOV 15:00 Support agent increases limit to R$5K 09 NOV 08:00 Mastercard purchase, Starbucks, R$100 01 NOV 11:00 Robot 437aae3 approves R$3K limit 01 NOV 10:00 Customer joins waiting list for a card
  • 10. 5 event stream 15 NOV 17:05 Customer blocks card 15 NOV 15:00 Support agent increases limit to R$5K 09 NOV 08:00 Mastercard purchase, Starbucks, R$100 01 NOV 11:00 Robot 437aae3 approves R$3K limit 01 NOV 10:00 Customer joins waiting list for a card
  • 11. facts over time 6 15 NOV 17:05 15 NOV 15:00 09 NOV 08:00 01 NOV 11:00 01 NOV 10:00 [<card> :card/status :card.status/blocked] [<card> :card/status :card.status/active] [<account> :account/limit 5000] [<account> :account/limit 3000] [<purchase> :purchase/card <card>] [<purchase> :purchase/amount 100] [<purchase> :purchase/merchant “Starbucks”] [<account> :account/customer <customer>] [<account> :account/limit 3000] [<card> :card/account <account>] [<card> :card/status :card.status/active] [<customer> :customer/id #uuid “b2c90…”]
  • 12. facts over time 6 15 NOV 17:05 15 NOV 15:00 09 NOV 08:00 01 NOV 11:00 01 NOV 10:00 [<card> :card/status :card.status/blocked] [<card> :card/status :card.status/active] [<account> :account/limit 5000] [<account> :account/limit 3000] [<purchase> :purchase/card <card>] [<purchase> :purchase/amount 100] [<purchase> :purchase/merchant “Starbucks”] [<account> :account/customer <customer>] [<account> :account/limit 3000] [<card> :card/account <account>] [<card> :card/status :card.status/active] [<customer> :customer/id #uuid “b2c90…”] entity attribute value
  • 13. 7
  • 15. 7 don’t lose data what changed when
  • 16. 7 don’t lose data what changed when as-of since
  • 17. 7 don’t lose data what changed when as-of since fork merge
  • 18. 8 What was the initial limit for the card?
  • 19. 8 What was the initial limit for the card? At the time the Starbucks transaction occurred, which fraud triggers would have activated?
  • 20. 8 What was the initial limit for the card? At the time the Starbucks transaction occurred, which fraud triggers would have activated? How long did the customer spend on each stage of the acquisition funnel?
  • 21. 8 What was the initial limit for the card? At the time the Starbucks transaction occurred, which fraud triggers would have activated? How long did the customer spend on each stage of the acquisition funnel? How frequently do we see amount changes on Starbucks transactions?
  • 22. 9 What sequence of events resulted in the Starbucks transaction being persisted?
  • 23. 9 What sequence of events resulted in the Starbucks transaction being persisted? iOS http-in kafka-out kafka-in iZb iZb.jnA iZb.jnA.9Cd iZb.jnA.9Cd.l9A
  • 24. 9 iOS http-in kafka-out kafka-in iZb iZb.jnA iZb.jnA.9Cd iZb.jnA.9Cd.l9A What sequence of events resulted in the Starbucks transaction being persisted? correlation-id
  • 25. Who was responsible for the credit limit increase? 9 iOS http-in kafka-out kafka-in iZb iZb.jnA iZb.jnA.9Cd iZb.jnA.9Cd.l9A What sequence of events resulted in the Starbucks transaction being persisted? correlation-id
  • 26. Who was responsible for the credit limit increase? 9 iOS http-in kafka-out kafka-in iZb iZb.jnA iZb.jnA.9Cd iZb.jnA.9Cd.l9A What sequence of events resulted in the Starbucks transaction being persisted? correlation-id #uuid “b2c90…” “lucas@nubank.com.br” :kafka-LIMIT-CHANGED :robot-437aae3
  • 27. Who was responsible for the credit limit increase? 9 iOS http-in kafka-out kafka-in iZb iZb.jnA iZb.jnA.9Cd iZb.jnA.9Cd.l9A What sequence of events resulted in the Starbucks transaction being persisted? correlation-id user #uuid “b2c90…” “lucas@nubank.com.br” :kafka-LIMIT-CHANGED :robot-437aae3
  • 28. Who was responsible for the credit limit increase? 9 What sequence of events resulted in the Starbucks transaction being persisted? Why was the customer’s card blocked? correlation-id iOS http-in kafka-out kafka-in iZb iZb.jnA iZb.jnA.9Cd iZb.jnA.9Cd.l9A user #uuid “b2c90…” “lucas@nubank.com.br” :kafka-LIMIT-CHANGED :robot-437aae3
  • 29. Who was responsible for the credit limit increase? 9 What sequence of events resulted in the Starbucks transaction being persisted? Why was the customer’s card blocked? correlation-id iOS http-in kafka-out kafka-in iZb iZb.jnA iZb.jnA.9Cd iZb.jnA.9Cd.l9A :fraud-preventative :recurring-scheduled :late-payment :data-migration user #uuid “b2c90…” “lucas@nubank.com.br” :kafka-LIMIT-CHANGED :robot-437aae3
  • 30. Who was responsible for the credit limit increase? 9 What sequence of events resulted in the Starbucks transaction being persisted? Why was the customer’s card blocked? correlation-id iOS http-in kafka-out kafka-in iZb iZb.jnA iZb.jnA.9Cd iZb.jnA.9Cd.l9A user #uuid “b2c90…” “lucas@nubank.com.br” :kafka-LIMIT-CHANGED :robot-437aae3 :fraud-preventative reason :recurring-scheduled :late-payment :data-migration
  • 31. (defn block-card [card reason] (d/transact (conn) [[:db/add card :card/status :card.status/blocked] ])) 10 storing transaction metadata
  • 32. (defn block-card [card reason] (d/transact (conn) [[:db/add card :card/status :card.status/blocked] {:db/id (d/tempid :db.part/tx) :audit/user "lucas@nubank.com.br" :audit/cid "iZb.jnA.9Cd.l9A" :audit/tags :fraud-preventative} ])) 10 storing transaction metadata
  • 33. 11 transactions over time with metadata [<card> :card/status :card.status/blocked] [<card> :card/status :card.status/active] 15 NOV 17:05 [<account> :account/limit 5000M] 15 NOV 15:00 [<account> :account/limit 3000M] [<purchase> :purchase/card <card>] [<purchase> :purchase/amount 100M] [<purchase> :purchase/merchant “Starbucks”] 09 NOV 08:00 [<account> :account/customer <customer>] [<account> :account/limit 3000M] [<card> :card/account <account>] [<card> :card/status :card.status/active] 01 NOV 11:00 01 NOV 10:00 [<customer> :customer/id #uuid “b2c90…”]
  • 34. 11 transactions over time with metadata [<card> :card/status :card.status/blocked] [<card> :card/status :card.status/active] 15 NOV 17:05 [<account> :account/limit 5000M] 15 NOV 15:00 [<account> :account/limit 3000M] [<purchase> :purchase/card <card>] [<purchase> :purchase/amount 100M] [<purchase> :purchase/merchant “Starbucks”] 09 NOV 08:00 [<account> :account/customer <customer>] [<account> :account/limit 3000M] [<card> :card/account <account>] [<card> :card/status :card.status/active] 01 NOV 11:00 01 NOV 10:00 [<customer> :customer/id #uuid “b2c90…”]
  • 35. 12 querying transaction metadata (defn who-blocked-the-card? [card] (d/q '{:find [?user ?cid ?tags] :in [$ ?status ?card] :where [[?card :card/status ?status ?tx] [?tx :audit/user ?user] [?tx :audit/cid ?cid] [?tx :audit/tags ?tags]]} db :card.status/blocked card))
  • 36. 12 querying transaction metadata (defn who-blocked-the-card? [card] (d/q '{:find [?user ?cid ?tags] :in [$ ?status ?card] :where [[?entity attribute value txn [?card :card/status ?status ?tx] [?tx :audit/user ?user] [?tx :audit/cid ?cid] [?tx :audit/tags ?tags]]} db :card.status/blocked card))
  • 37. 12 querying transaction metadata (defn who-blocked-the-card? [card] (d/q '{:find [?user ?cid ?tags] :in [$ ?status ?card] :where [[?card :card/status ?status ?tx] [?tx :audit/user ?user] [?tx :audit/cid ?cid] [?tx :audit/tags ?tags]]} db :card.status/blocked card))
  • 38. 12 querying transaction metadata (defn who-blocked-the-card? [card] (d/q '{:find [?user ?cid ?tags] :in [$ ?status ?card] :where [[?card :card/status ?status ?tx] [?tx :audit/user ?user] [?tx :audit/cid ?cid] [?tx :audit/tags ?tags]]} db :card.status/blocked card))
  • 39. 12 querying transaction metadata (defn who-blocked-the-card? [card] (d/q '{:find [?user ?cid ?tags] :in [$ ?status ?card] :where [[?card :card/status ?status ?tx] [?tx :audit/user ?user] [?tx :audit/cid ?cid] [?tx :audit/tags ?tags]]} db :card.status/blocked card))
  • 41. should we return data? GET /purchases/1337/comments
  • 42. should we return data? GET /purchases/1337/comments 200 OK 401 Unauthorized
  • 43. customer account payment card purchase comment should we return data? GET /purchases/1337/comments 200 OK 401 Unauthorized
  • 44. customer account payment card purchase comment should we return data? GET /purchases/1337/comments 200 OK 401 Unauthorized
  • 45. customer account payment card purchase comment should we return data? GET /purchases/1337/comments 200 OK 401 Unauthorized
  • 46. 15 explicit relationship traversal (defn owns? [customer-id purchase-id db] (d/q '{:find [?pur .] :in [$ ?customer-id ?purchase-id] :where [[?pur :purchase/id ?purchase-id] [?pur :purchase/account ?acc] [?acc :account/customer ?cus] [?cus :customer/id ?customer-id]]} db customer-id purchase-id))
  • 47. 15 explicit relationship traversal (defn owns? [customer-id purchase-id db] (d/q '{:find [?pur .] :in [$ ?customer-id ?purchase-id] :where [[?pur :purchase/id ?purchase-id] [?pur :purchase/account ?acc] [?acc :account/customer ?cus] [?cus :customer/id ?customer-id]]} db customer-id purchase-id))
  • 48. generic relationship traversal 16 recursive rule (logical OR)
  • 49. generic relationship traversal 16 recursive rule (logical OR) (def owner-rules '[[(owns? ?cus-id ?e) [?e :customer/id ?cus-id]] [(owns? ?cus-id ?e) [?e ?ref-attr ?r] (owns? ?cus-id ?r)]])
  • 50. generic relationship traversal 16 recursive rule (logical OR) (def owner-rules '[[(owns? ?cus-id ?e) [?e :customer/id ?cus-id]] [(owns? ?cus-id ?e) [?e ?ref-attr ?r] (owns? ?cus-id ?r)]])
  • 51. generic relationship traversal 16 recursive rule (logical OR) (def owner-rules '[[(owns? ?cus-id ?e) [?e :customer/id ?cus-id]] [(owns? ?cus-id ?e) [?e ?ref-attr ?r] (owns? ?cus-id ?r)]])
  • 52. generic relationship traversal 16 recursive rule (logical OR) (def owner-rules '[[(owns? ?cus-id ?e) [?e :customer/id ?cus-id]] [(owns? ?cus-id ?e) [?e ?ref-attr ?r] (owns? ?cus-id ?r)]])
  • 53. generic relationship traversal 16 recursive rule (logical OR) (def owner-rules '[[(owns? ?cus-id ?e) [?e :customer/id ?cus-id]] [(owns? ?cus-id ?e) [?e ?ref-attr ?r] (owns? ?cus-id ?r)]])
  • 54. generic relationship traversal 16 recursive rule (logical OR) (def owner-rules '[[(owns? ?cus-id ?e) [?e :customer/id ?cus-id]] [(owns? ?cus-id ?e) [?e ?ref-attr ?r] (owns? ?cus-id ?r)]])
  • 55. generic relationship traversal 16 recursive rule (logical OR) query using rule (def owner-rules '[[(owns? ?cus-id ?e) [?e :customer/id ?cus-id]] [(owns? ?cus-id ?e) [?e ?ref-attr ?r] (owns? ?cus-id ?r)]])
  • 56. generic relationship traversal :in [$ ?cus-id ?e %] :where [(owns? ?cus-id ?e)]} db customer-id entity owner-rules)) 16 recursive rule (logical OR) query using rule (def owner-rules '[[(owns? ?cus-id ?e) [?e :customer/id ?cus-id]] [(owns? ?cus-id ?e) [?e ?ref-attr ?r] (owns? ?cus-id ?r)]]) (defn owns? [customer-id entity db] (d/q '{:find [?e .] (owns? customer-id [:purchase/id id] db)
  • 57. generic relationship traversal :in [$ ?cus-id ?e %] :where [(owns? ?cus-id ?e)]} db customer-id entity owner-rules)) 16 recursive rule (logical OR) query using rule (def owner-rules '[[(owns? ?cus-id ?e) [?e :customer/id ?cus-id]] [(owns? ?cus-id ?e) [?e ?ref-attr ?r] (owns? ?cus-id ?r)]]) (defn owns? [customer-id entity db] (d/q '{:find [?e .] (owns? customer-id [:purchase/id id] db)
  • 58. 17 Recursion rules! But can we go further? Can we prevent queries from returning entities owned by other customers?
  • 59. all owned entities 18 query binding attribute
  • 60. all owned entities 18 query binding attribute (defn owns? [customer-id entity db] (d/q '{:find [?e .] :in [$ ?cus-id ?e %] :where [(owns? ?cus-id ?e)]} db customer-id entity] owner-rules))
  • 61. all owned entities 18 query binding attribute (defn owns? [customer-id entity db] (d/q '{:find [?e .] :in [$ ?cus-id ?e %] :where [(owns? ?cus-id ?e)]} db customer-id entity] owner-rules))
  • 62. all owned entities 18 query binding attribute attribute unbound (defn owns? [customer-id entity db] (d/q '{:find [?e .] :in [$ ?cus-id ?e %] :where [(owns? ?cus-id ?e)]} db customer-id entity] owner-rules))
  • 63. all owned entities 18 query binding attribute attribute unbound (defn owns? [customer-id entity db] (d/q '{:find [?e .] :in [$ ?cus-id ?e %] :where [(owns? ?cus-id ?e)]} db customer-id entity] owner-rules)) (defn owned-entities [customer-id db] (d/q '{:find [[?e ...]] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e)]} db customer-id owner-rules))
  • 64. all owned entities 18 query binding attribute attribute unbound (defn owns? [customer-id entity db] (d/q '{:find [?e .] :in [$ ?cus-id ?e %] :where [(owns? ?cus-id ?e)]} db customer-id entity] owner-rules)) (defn owned-entities [customer-id db] (d/q '{:find [[?e ...]] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e)]} db customer-id owner-rules))
  • 65. all owned entities 18 query binding attribute attribute unbound (defn owns? [customer-id entity db] (d/q '{:find [?e .] :in [$ ?cus-id ?e %] :where [(owns? ?cus-id ?e)]} db customer-id entity] owner-rules)) (defn owned-entities [customer-id db] (d/q '{:find [[?e ...]] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e)]} db customer-id owner-rules))
  • 66. filtered db (as value) 19 (defn owned-db [customer-id db] (let [owned (set (owned-entities customer-id db))] (d/filter db (fn [_ [e a v t :as datom]] (when (or (= e t) (contains? owned e)) datom)))))
  • 67. filtered db (as value) 19 (defn owned-db [customer-id db] (let [owned (set (owned-entities customer-id db))] (d/filter db (fn [_ [e a v t :as datom]] (when (or (= e t) (contains? owned e)) datom)))))
  • 68. filtered db (as value) 19 (defn owned-db [customer-id db] (let [owned (set (owned-entities customer-id db))] (d/filter db (fn [_ [e a v t :as datom]] (when (or (= e t) (contains? owned e)) datom)))))
  • 69. filtered db (as value) 19 (defn owned-db [customer-id db] (let [owned (set (owned-entities customer-id db))] (d/filter db (fn [_ [e a v t :as datom]] (when (or (= e t) (contains? owned e)) datom)))))
  • 70. filtered db (as value) 19 (defn owned-db [customer-id db] (let [owned (set (owned-entities customer-id db))] (d/filter db (fn [_ [e a v t :as datom]] (when (or (= e t) (contains? owned e)) datom)))))
  • 71. filtered db (as value) (let [owned (set (owned-entities customer-id db))] (d/filter db 19 (defn owned-db [customer-id db] (fn [_ [e a v t :as datom]] (when (or (= e t) (contains? owned e)) datom))))) filtered db will not contain datoms from other owners!
  • 72. passing a filtered db is transparent for queries 20 (defn all-purchases [account-id db] (d/q '{:find [[(pull ?p [*]) ...]] :in [$ ?acc] :where [[?p :purchase/account ?acc]]} db [:account/id account-id])) (all-purchases account-id db) (all-purchases account-id (owned-db customer-id db))
  • 73. passing a filtered db is transparent for queries 20 (defn all-purchases [account-id db] (d/q '{:find [[(pull ?p [*]) ...]] :in [$ ?acc] :where [[?p :purchase/account ?acc]]} db [:account/id account-id])) (all-purchases account-id db) (all-purchases account-id (owned-db customer-id db))
  • 74. 21 We’re Mobile! Bandwidth is costly
  • 75. 22 3 HTTP Cache
  • 76. 23 http last modified header GET /accounts/1234/purchases 200 OK Last-Modified: Fri, 14 Nov 2014 14:28:50 UTC {"purchases": [...]}
  • 77. http last modified header If-Modified-Since: Fri, 14 Nov 2014 14:28:50 UTC GET /accounts/1234/purchases 304 Not Modified 23 GET /accounts/1234/purchases 200 OK Last-Modified: Fri, 14 Nov 2014 14:28:50 UTC {"purchases": [...]}
  • 78. 24 How to keep track of a good last-modified date for a URL?
  • 79. 24 How to keep track of a good last-modified date for a URL? The last time any owned entity changed!
  • 80. If no customer-owned data changed, 304 25 (defn last-modified [customer-id db] (d/q '{:find [(max ?time) .] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e) [?e ?a ?v ?tx] [?tx :db/txInstant ?time]]} db customer-id owner-rules))
  • 81. If no customer-owned data changed, 304 25 (defn last-modified [customer-id db] (d/q '{:find [(max ?time) .] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e) [?e ?a ?v ?tx] [?tx :db/txInstant ?time]]} db customer-id owner-rules))
  • 82. If no customer-owned data changed, 304 25 (defn last-modified [customer-id db] (d/q '{:find [(max ?time) .] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e) [?e ?a ?v ?tx] [?tx :db/txInstant ?time]]} db customer-id owner-rules))
  • 83. If no customer-owned data changed, 304 25 (defn last-modified [customer-id db] (d/q '{:find [(max ?time) .] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e) [?e ?a ?v ?tx] [?tx :db/txInstant ?time]]} db customer-id owner-rules))
  • 84. If no customer-owned data changed, 304 25 (defn last-modified [customer-id db] (d/q '{:find [(max ?time) .] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e) [?e ?a ?v ?tx] [?tx :db/txInstant ?time]]} db customer-id owner-rules))
  • 85. If no customer-owned data changed, 304 25 (defn last-modified [customer-id db] (d/q '{:find [(max ?time) .] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e) [?e ?a ?v ?tx] [?tx :db/txInstant ?time]]} db customer-id owner-rules))
  • 86. If no customer-owned data changed, 304 25 (defn last-modified [customer-id db] (d/q '{:find [(max ?time) .] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e) [?e ?a ?v ?tx] [?tx :db/txInstant ?time]]} db customer-id owner-rules))
  • 87. If no customer-owned data changed, 304 25 (defn last-modified [customer-id db] (d/q '{:find [(max ?time) .] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e) [?e ?a ?v ?tx] [?tx :db/txInstant ?time]]} db customer-id owner-rules))
  • 88. If no customer-owned data changed, 304 25 (defn last-modified [customer-id db] (d/q '{:find [(max ?time) .] :in [$ ?cus-id %] :where [(owns? ?cus-id ?e) [?e ?a ?v ?tx] [?tx :db/txInstant ?time]]} db customer-id owner-rules))
  • 89. 26 Cache hits are awesome, but what about cache misses?
  • 90. 27 4 Mobile Sync
  • 91. desired hypermedia from the API 28 GET /accounts/1223/purchases 200 OK {"purchases": [...] "_links": { "updates": "https://...?since=2014-11-10T11:12:13Z" } }
  • 92. serve purchases added or modified since our last sync 29 (defn updated-purchases [account-id db since] (d/q '{:find [[(pull ?pur [*]) ...]] :in [$ $since ?acc] :where [[?pur :purchase/account ?acc] [$since ?pur]]} db (d/history (d/since db since)) [:account/id account-id])) (updated-purchases account-id db #inst "2014-11-14"))
  • 93. serve purchases added or modified since our last sync 29 (defn updated-purchases [account-id db since] (d/q '{:find [[(pull ?pur [*]) ...]] :in [$ $since ?acc] :where [[?pur :purchase/account ?acc] [$since ?pur]]} db (d/history (d/since db since)) [:account/id account-id])) (updated-purchases account-id db #inst "2014-11-14"))
  • 94. serve purchases added or modified since our last sync 29 (defn updated-purchases [account-id db since] (d/q '{:find [[(pull ?pur [*]) ...]] :in [$ $since ?acc] :where [[?pur :purchase/account ?acc] [$since ?pur]]} db (d/history (d/since db since)) [:account/id account-id])) (updated-purchases account-id db #inst "2014-11-14"))
  • 95. serve purchases added or modified since our last sync 29 (defn updated-purchases [account-id db since] (d/q '{:find [[(pull ?pur [*]) ...]] :in [$ $since ?acc] :where [[?pur :purchase/account ?acc] [$since ?pur]]} db (d/history (d/since db since)) [:account/id account-id])) (updated-purchases account-id db #inst "2014-11-14"))
  • 96. serve purchases added or modified since our last sync 29 (defn updated-purchases [account-id db since] (d/q '{:find [[(pull ?pur [*]) ...]] :in [$ $since ?acc] :where [[?pur :purchase/account ?acc] [$since ?pur]]} db (d/history (d/since db since)) [:account/id account-id])) (updated-purchases account-id db #inst "2014-11-14"))
  • 97. serve purchases added or modified since our last sync 29 (defn updated-purchases [account-id db since] (d/q '{:find [[(pull ?pur [*]) ...]] :in [$ $since ?acc] :where [[?pur :purchase/account ?acc] [$since ?pur]]} db (d/history (d/since db since)) [:account/id account-id])) (updated-purchases account-id db #inst "2014-11-14"))
  • 98. serve purchases added or modified since our last sync 29 (defn updated-purchases [account-id db since] (d/q '{:find [[(pull ?pur [*]) ...]] :in [$ $since ?acc] :where [[?pur :purchase/account ?acc] [$since ?pur]]} db (d/history (d/since db since)) [:account/id account-id])) (updated-purchases account-id db #inst "2014-11-14"))
  • 100. 31 5 Future DBs
  • 101. generating a virtual db (defn db-with-virtual-charges [purchases db] (:db-after (d/with db (mapcat purchase->virtual-charges purchases)))) 32
  • 102. generating a virtual db (defn db-with-virtual-charges [purchases db] (:db-after (d/with db (mapcat purchase->virtual-charges purchases)))) 32
  • 103. generating a virtual db (defn db-with-virtual-charges [purchases db] (:db-after (d/with db (mapcat purchase->virtual-charges purchases)))) 32
  • 104. generating a virtual db (defn db-with-virtual-charges [purchases db] (:db-after (d/with db (mapcat purchase->virtual-charges purchases)))) 32
  • 105. generating a virtual db (defn db-with-virtual-charges [purchases db] (:db-after (d/with db (mapcat purchase->virtual-charges purchases)))) 32
  • 107. db testing - locally scoped 34 (defn virtual-db [updates db] (:db-after (d/with db updates))) (let [one-account {...} one-card {...} one-purchase {...} wrong-purchase {...} db (virtual-db [one-account one-card one-purchase wrong-purchase] db)] (my-weird-db-query db) => [one-purchase])
  • 108. db testing - locally scoped 34 (defn virtual-db [updates db] (:db-after (d/with db updates))) (let [one-account {...} one-card {...} one-purchase {...} wrong-purchase {...} db (virtual-db [one-account one-card one-purchase wrong-purchase] db)] (my-weird-db-query db) => [one-purchase])
  • 109. db testing - locally scoped 34 (defn virtual-db [updates db] (:db-after (d/with db updates))) (let [one-account {...} one-card {...} one-purchase {...} wrong-purchase {...} db (virtual-db [one-account one-card one-purchase wrong-purchase] db)] (my-weird-db-query db) => [one-purchase])
  • 110. db testing - locally scoped 34 (defn virtual-db [updates db] (:db-after (d/with db updates))) (let [one-account {...} one-card {...} one-purchase {...} wrong-purchase {...} db (virtual-db [one-account one-card one-purchase wrong-purchase] db)] (my-weird-db-query db) => [one-purchase])
  • 111. db testing - locally scoped 34 (defn virtual-db [updates db] (:db-after (d/with db updates))) (let [one-account {...} one-card {...} one-purchase {...} wrong-purchase {...} db (virtual-db [one-account one-card one-purchase wrong-purchase] db)] (my-weird-db-query db) => [one-purchase])
  • 112. db testing - locally scoped 34 (defn virtual-db [updates db] (:db-after (d/with db updates))) (let [one-account {...} one-card {...} one-purchase {...} wrong-purchase {...} db (virtual-db [one-account one-card one-purchase wrong-purchase] db)] (my-weird-db-query db) => [one-purchase])
  • 113. db testing - vector of datoms 35 (fact "on last-modification-for" (let [customer-id (uuid) db [[42 :account/customer-id customer-id 99] [99 :db/txInstant #inst "2014-10-10T12:00:00.909Z" 99] [1337 :purchase/account 42 100] [1337 :purchase/id (uuid) 100] [1337 :purchase/merchant-name "Coiso" 100] [100 :db/txInstant #inst "2014-10-11T12:00:00.888Z" 100] [9001 :line-item/account 42 101] [9001 :charge/id (uuid) 101] [9001 :line-item/precise-amount 100.00M 101] [101 :db/txInstant #inst "2014-10-12T13:00:00.777Z" 101] [45 :account/customer-id (uuid) 102] [102 :db/txInstant #inst "2014-10-15T12:00:00.909Z" 102]]] (last-modification-for customer-id db) => #nu/time "2014-10-12T13:00:00.777Z"))
  • 114. db testing - vector of datoms 35 (fact "on last-modification-for" (let [customer-id (uuid) db [[42 :account/customer-id customer-id 99] [99 :db/txInstant #inst "2014-10-10T12:00:00.909Z" 99] [1337 :purchase/account 42 100] [1337 :purchase/id (uuid) 100] [1337 :purchase/merchant-name "Coiso" 100] [100 :db/txInstant #inst "2014-10-11T12:00:00.888Z" 100] [9001 :line-item/account 42 101] [9001 :charge/id (uuid) 101] [9001 :line-item/precise-amount 100.00M 101] [101 :db/txInstant #inst "2014-10-12T13:00:00.777Z" 101] [45 :account/customer-id (uuid) 102] [102 :db/txInstant #inst "2014-10-15T12:00:00.909Z" 102]]] (last-modification-for customer-id db) => #nu/time "2014-10-12T13:00:00.777Z"))
  • 115. 36 7 Schema Extension
  • 116. schema is data! 37 (d/transact conn [{:db/valueType :db.type/string, :db.install/_attribute :db.part/db, :db/id (d/tempid :db.part/db), :db/cardinality :db.cardinality/one, :db/ident :customer/name }])
  • 117. :nubank/transform :pii schema is data! 37 (d/transact conn [{:db/valueType :db.type/string, :db.install/_attribute :db.part/db, :db/id (d/tempid :db.part/db), :db/cardinality :db.cardinality/one, :db/ident :customer/name }])
  • 118. 38 8 Sharding Reads
  • 119. 39 sharding to the transactor customers accounts notification acquisition processor auth
  • 120. 39 sharding to the transactor customers accounts notification acquisition processor auth
  • 121. customers 1 2 … 39 sharding to the transactor customers accounts notification acquisition processor auth
  • 122. customers 1 2 … 39 sharding to the transactor customers accounts notification acquisition processor auth shard-specific peer cache
  • 123. customers 1 2 … 39 sharding to the transactor customers accounts notification acquisition processor auth shard-specific peer cache inter-shard ACID transactions
  • 124. 40 9 Db Aggregation
  • 125. 41 data science aggregation customers accounts notification acquisition processor auth data science
  • 126. 42 querying multiple databases (defn multi-join [customer-id cus-db acc-db acq-db ntf-db] (d/q '{:find [...] :in [$cus $acc $acq $ntf ?cus-id] :where [[$cus ?cus :customer/id ?cus-id] [$acc ?acc :account/customer-id ?cus-id] [$acq ?ar :request/customer-id ?cus-id] [$ntf ?evt :event/customer-id ?cus-id] [...]]} cus-db acc-db acq-db ntf-db customer-id))
  • 127. 42 querying multiple databases (defn multi-join [customer-id cus-db acc-db acq-db ntf-db] (d/q '{:find [...] :in [$cus $acc $acq $ntf ?cus-id] :where [[$cus ?cus :customer/id ?cus-id] [$acc ?acc :account/customer-id ?cus-id] [$acq ?ar :request/customer-id ?cus-id] [$ntf ?evt :event/customer-id ?cus-id] [...]]} cus-db acc-db acq-db ntf-db customer-id))
  • 128. 43 1 Audit Trail 2 Authorization 3 HTTP Cache 4 Mobile Sync 5 Future DBs 6 Testing 7 Schema Extension 8 Sharding Reads 9 Db Aggregation
  • 131. Thanks! Lucas Cavalcanti & Edward Wible @lucascs lucas@nubank.com.br edward@nubank.com.br