Skip to content

Time API

Choose a tag to compare
@marcgrue marcgrue released this 19 May 17:39
· 458 commits to master since this release

Datomic has some extremely powerful time functionality that Molecule now makes available in an intuitive way:

Ad-hoc time queries

Ad-hoc time queries against our database can now be made with the following time-aware getter methods on a molecule: === ... // Persons as of a point in time `t` === ... // Persons added after a point in time `t`

Person(fredId).age.getHistory === ... // Current and previous ages of Fred in the db

t can be a transaction entity id (Long), a transaction number (Long) or a java.util.Date.

If we want to test the outcome of some transaction without affecting the production db we can apply transaction data to the getWith(txData) method:
  // Testing adding some transactional data to the current db"John").age(42).saveTx, // Save transaction data
    List(("Lisa", 34), ("Pete", 55)) // Insert transaction data
  Person(fredId).age(28).updateTx, // Update transaction data
  someEntityId.retractTx // Retraction transaction data
) === ... // Persons from db including transactions tested 

By adding the Tx suffix to the standard molecule commands (save, insert, update or retract)
we can get the transactional data that those operations would normally transact directly. Here, <command>Tx methods on transaction molecules just return the transaction data so that we can apply it to the getWith(txData) method. This make it convenient for us to ask speculative questions like "what would I get if I did those transactions".

For more complex test scenarios we can now use a test database:

Test db

All molecules expect an implicit connection object to be in scope. If we then set a temporary test database on such conn object we can subsequentially freely perform tests against this temporary database as though it was a "branch" (think git).

When the connection/db goes out of scope it is simply garbage collected automatically by the JVM. At any point we can also explicitly go back to continuing using our live production db.

To make a few tests with a "branch" of our live db we can now do like this:

// Current state
Person(fredId).name.age.get.head === ("Fred", 27)

// Create "branch" of our production db as it is right now

// Perform multiple operations on test db

// Verify expected outcome of operations
Person(fredId).name.age.get.head === ("Frederik", 28)

// Then go back to production state

// Production state is unchanged!
Person(fredId).name.age.get.head === ("Fred", 27)

Test db with domain classes

When molecules are used inside domain classes we want to test the domain operations also without affecting the state of our production database. And also ideally without having to create mockups of our domain objects. This is now possible by setting a temporary test database on the implicit connection object that all molecules expect to be present in their scope - which includes the molecules inside domain classes.

When we test against a temporary database, Molecule internally uses the with function of Datomic to apply transaction data to a "branch" of the database that is simply garbage collected when it goes out of scope!

To make a few tests on a domain object that have molecule calls internally we can now do like this:

// Some domain object that we want to test
val domainObj = MyDomainClass(params..) // having molecule transactions inside...
domainObj.myState === "some state"

// Create "branch" of our production db as it is right now

// Test some domain object operations

// Verify expected outcome of operations
domainObj.myState === "some expected changed state"

// Then go back to production state

// Production state is unchanged!
domainObj.myState == "some state"

Since internal domain methods will in turn call other domain methods that also expects an implicit conn object then the same test db is even propragated recursively inside the chain of domain operations.

Multiple time views

We can apply the above approach with several time views of our database:


This make it possible to run arbitrarily complex test scenarios directly against our production data at any point in time without having to do any manual setup or tear-down of mock domain/database objects!