Skip to content

Instantly share code, notes, and snippets.

@toff63
Created January 15, 2013 11:44
Show Gist options
  • Select an option

  • Save toff63/4538072 to your computer and use it in GitHub Desktop.

Select an option

Save toff63/4538072 to your computer and use it in GitHub Desktop.
Datomic:
Store facts that are immutable.
Access via a Peer Library with extensive caching (LRU)
Add new facts using transactor that will take care of ACID properties and inform all peers that a new fact is available.
Data power into application
Why Datomic
Before: Client Server. Everything about data maninpulation done by server:
* Queries
* Transaction
* Consistency
* Storage
App pretty dumb in this architecture
Datomic:
Brain back to the application.
* Query engine on app
* Storage on app: only READ
Transactor: coordinate transaction and ensure consistency. Then inform peers of the modification
App side: Query, communication and memory engine for cache (LRU) => db is local, so no round trip issue. Isolation as you are the only one using the db. Long running queries doesn't impact other apps. Db is local and data is local :)
Logic engine: declarative query engine
Query language is Datalog
* simple rules and data patterns (like pattern matching)
Declarative => joins are implicit. Meaning is evident
Can be used for db and non-db sources (collection you use in your program)
Perception:
* obtain a queue of transactions with all transactions in the system (not only your own)
* Query transactions for filtering/triggering
Consistency:
* ACID transactions add new facts
* DB presented to app as a value
* Data in storage service is immutable
Immutable => connection to the database gives you a value
=> consistency.
=> caching is easy :)
Programmability
* No API: Transactions/Rules/Queries/Results are data (list, map ...)
* extensible types, predicate, etc
* Queries can invoke your code because the engine is local :)
Memory and Records (Data model)
* Mental memory is associative and open (outside computer science world)
* Real records are enduring: you never erase them and overwrite!
What we have right now?
PLOP: Place Oriented Programming
Taking new info, find where to put it, and put it there. This due to old computer science: small ram and disks => you have to optimize it. Today we have wide Ram and disks.
FACTS vs Places
Places:
Db Table with overwrite of data
Documents with overwrite of data
Reuse of space
Facts:
info with a point of time (can use transaction that created it) NO OVERWRITE creation of a new fact with NEW DATA and new Transaction
The new fact is sotred in a new place
Datomic is a db of facts: the datom
=> Entity/Attribute/Value/Transaction
Attribute definition is the only 'schema'
Adaptability: easy to represent new shapes of data
Single an multi-valued attribute
No structural rigidity: how the structure of table, data envade your application.
A datom doesn't have any structure implication so we don't have this problem.
Time Built-in
Every datom retains its transaction
Transactions are totalyy ordered because it is faster :)
Transactions are first class entities, so you can access it and get all info related :)
DB is a value and you can get the value it had at a certain point of time and query it.
Simpler and more powerfull
Query Language:
Datoms: Entity/Attribute/Value and Transactions
"John likes pizza" (T42)
Entity: John
Attributes: likes
Value: pizza
T42: transaction 42
Query Components:
variable (?javaName): ?customer
constants: usual constants + keywords + instant (#inst "2012-02-29")
Data pattern: list with entity attribute and value:
[?customer :email ?email]
Entity: ?customer
attribute: :email
value: ?email
Fine a particular customer's 42 email
[42 :email ?email]
What other attributes does customer 42 have?
[42 ?attribute]
What other attributes and values does customer 42 have?
[42 ?attribute ?values]
Find clause
[:find ?customer
:where [?customer :email]
[?customer :orders]]
=> all customers with order and mail
Query:
import static datomic.Peer.q;
q("[:find ?customer
:where [?customer :id]
[?customer :orders]]",
db);
Filter by email (multiple variable $ prefix datasource variable)
q("[:find ?customer
:in $database ?email
:where [$database ?customer :email ?email]]",db,"[email protected]")
syntax sugar:
q("[:find ?customer
:in $ ?email
:where [?customer :email ?email]]",db,"[email protected]")
Predicates
Example find item where price > 50
[:find ?item
:where [?item :item/price ?price]
[(<50 ?price)]]
Functions
[(shipping ?zip ?weight) ?cost]
Find customer and product where shipping price higher than product price
[:find ?customer ?product
:where [?customer :shipAddress ?addr]
[?addr :zip ?zip]
[?product :product/weight ?weight]
[?product :product/price ?price]
[(Shipping/estimate ?zip ?weight) ?shipCost]
[(<= ?price ?shipCost)]]
BYO Data
What Jar files are in my system property paths?
(q '[:find ?pathElem
:in [[?k ?v]]
:where [(.endsWith ?k "path")]
[(.split ?v ":") [?pathElem ...]]
[(.endsWith ?pathElem ".jar")]](System/getProperties)')
Datomic comes with Lucene => Full text search :)
"Find chocolate"
[:find ?product
:where
[(fulltext $ :desciption "chocolate")[[?product]]]]
$ => fn of entire database
?product => returning a relation
Rules:
"Products are related if they have a common category"
[(relatedProduct ?p1 ?p2) <-- this is the rulename com os argumentos) the rest is rule body
[?p1 :category ?c]
[?p2 :category ?c]
[(!= ?p1 ?p2)]]
You can filter by rules:
q("[:find ?p2
:in $ %
:where [(expensiveChocolate p1)
(relatedProduct p1 p2)]",
db, rules)
$ => datasource
% => collection of rules
Rules are like db views except Rules lives in the application and can be created at runtime :)
By default the query is done on the latest values
You can run a query for db with it value in the past:
q(query,db.asOf(lastMonth,rules);
query = [:find ?customer
:in $%
:where [(good-customer ?customer]]
Can query the database como se tinha inserido novos dados:
q(query, db.with(newData), rules);
query = [:find ?customer
:in $%
:where [(good-customer ?customer]]
Event driven app:
"Send a notification when a customer achieves good customer status"
change = txReportQueue.get()
q(query,change.dbBefore(),
change.dbAfoter(),
change.txData(),
rules);
Database Function
Java
public static Object greet(String name){
return "Hello, " + name;
Datomic function:
#db/fn {:lang "java"
:params [name]
:code "return \"Hello, \" + name;"}
you can generate this function using:
fmap = Util.map("lang",
"java",
"params",
Util.list("name"),
"code",
"return \"Hello, \" + name;")
greep = Peer.function(fmap);
Fn in transaction data:
[{:db/id #db/id [:db.part/user]}
:db/ident :greeting
:db/fn #db/fn {:lang "java"
:params [name]
:code "return \"Hello, \" + name;"}]
Put fns in Database
conn.transact(txDataWithSomeFns);
Retrieve a fn
Entity greet = db.entity("greet");
Call a Fn
greet.get("db/fn").invoke("world");
Use:
Usage Example
atomic update inc
constructors constructPerson
validators validatePerson
distribution (all)
Assert and retract
[[:db/add john :likes pizza]
[:db/retract john :like iceCream]]
What about update and race conditions?
[[:db/add john :likes pizza]
[:db/retract john :like iceCream]
[:db/add john :balance 110?]]
Atomic inc
[[:db/add john :likes pizza]
[:db/retract john :like iceCream]
[:inc john :balance 10]]
So inc is defined in the language you want (java and clojure supported so far) and make it executed by the db so it is atomic and you avoid race conditions :))
Transaction functions:
* subset of db Fns
* run inside transactions => have access to tx info
Tx Function expansion:
[[:db/add john :likes pizza]
[:db/retract john :like iceCream]
[:inc john :balance 10]]
||
\/
[[:db/add john :likes pizza]
[:db/retract john :like iceCream]
[:db/add john :balance 110]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment