https://www.youtube.com/watch?v=7lm3K8zVOdY
This session will explore four common problems, and the unique and surprising tools Datomic provides to solve them elegantly: HTTP caching - How to generically generate and validate Last-Modified and If-Modified-Since headers Audit trail - how to extend Datomic’s immutable transaction log to include arbitrary audit related metadata Mobile database sync - trivial implementation of an incremental update API for high latency/low bandwidth clients Authorization - easily determine resource ownership, and centrally isolate users from data they are not allowed to see
These problems have certainly been solved before using other databases, but Datomic provides features that make the proposed implementations concise, generic, and purely functional
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
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
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))
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?
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"))