Time API
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:
Person.name.age.getAsOf(t) === ... // Persons as of a point in time `t`
Person.name.age.getSince(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:
Person.name.age.getWith(
// Testing adding some transactional data to the current db
Person.name("John").age(42).saveTx, // Save transaction data
Person.name.age.insertTx(
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
conn.testDbAsOfNow
// Perform multiple operations on test db
Person(fredId).name("Frederik").update
Person(fredId).age(28).update
// Verify expected outcome of operations
Person(fredId).name.age.get.head === ("Frederik", 28)
// Then go back to production state
conn.useLiveDb
// 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
conn.testDbAsOfNow
// Test some domain object operations
domainObj.doThis
domainObj.doThat
// Verify expected outcome of operations
domainObj.myState === "some expected changed state"
// Then go back to production state
conn.useLiveDb
// 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:
conn.testDbAsOfNow
conn.testDbAsOf(t)
conn.testDbSince(t)
conn.testWith(txData)
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!