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
If we want to test the outcome of some transaction without affecting the production db we can apply transaction data to the
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 (
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:
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!