Table of Contents generated with DocToc
entity-txn is a simple library that manages the state of domain type instances, which are maps or records, during a set of CRUD operations as a transaction. It requires the following
A protocol is provided to make these connections, and the library comes with an implementation that bridges to entity-core.
Of course, it's not very Clojure-like to update or delete things, but there
are a lot of such applications out there and how you implement your domain model is
up to you, for example accretion of rows instead of deletion. There are places in entity-txn
where you can veto deletion, either globally or on a per-type basis.
Each domain type must define its identity. This is one or more of the map
values from from an instance. Additionally, there must be included something
that makes the identity unique across all domain types. For example, if we have
two types, Fruit
and Nutrition
, instances might be:
:foo/Fruit ; Fruit type
{:Fruit "Strawberry" ; identity field
:Description "Soft Summer Fruit"
:ShelfLife 14}
:foo/Nutrition ; Nutrition type
{:Fruit "Strawberry" ; identity field
:KCalPer100g 28
:Fat 0
:Salt 0}
Both types define their identity as the :Fruit
field. The type system must
provide some meta data to resolve this ambiguity, so the respective identities
could look like
{:Fruit "Strawberry" ; identity field
:entity :foo/Fruit}
and
{:Fruit "Strawberry" ; identity field
:entity :foo/Nutrition}
Use the in-transaction
macro, supplying transaction arguments and body:
(in-transaction
{:on-commit my-fn}
( ... ))
Use set-transaction-defaults
during startup to avoid supplying transaction arguments on
each use, for example:
(set-transaction-defaults
:events (entity-txn-events) ; use entity-core as domain type system
:on-commit (fn [participants actions] ; write the participating instances to the DB using a DB transaction
(sql/with-transaction [*fruit-db*]
(write-txn-state participants)))))
See the api documentation for a full ilst of options.
Instances are obtained from the golden source by (read-instance ...)
. Such
instances are marked as managed. entity-txn
provides the vars assoc
and merge
that maintain the original state of the instance and its current
state within the transaction. Using these functions the latest state of any
instance is maintained in the transaction ready for commit. Note
that read-instance
will include the current state of any instance presently
mutated or deleted in the transaction: mutated intances will return the present value
and deleted instances will be excluded.
Instances are only managed when obtained inside a transaction. Otherwise read-entity
returns
results from the golden source unmodified, assoc
and merge
operate without affecting transaction state and delete
will throw.
Use this function to create candidate instances that can be placed in the transaction
using create
. Arguments will specify the domain type and any initial value.
Use create
to mark the instance for creation in the transaction.
The type system has the opportunity to validate, further initialise or create further
instances, presumably related in the domain model.
It is an error to create the same identity twice. Use in-creation?
passing an
instance or its identity if it is necessary to check for this amongst a sequence
of instances being processed.
This function will mark the instance for deletion. The type system is notified the instance is being deleted and can, for example, delete related instances.
These functions will, on first use, record the original value and any subsequent changes as the current value in the transaction. See further below for interaction with the type system.
Transaction state management allows the following
Transactions can be nested, and only the state in the current transaction is committed
when it closes. If a child transaction performs read-instance
and the results include state
held in any ancestor then this state will be represented, however it is not permitted to further
manipulate such instances in the current transaction.
The stages of transaction execution are:
A function of zero arguments called before execution of the body. This may be used, for example, to arbitrate for locks.
Execute the body
If no exception is thrown in the body, a transaction enters the commit phase.
The type system is informed of each mutation, passing the original and current values. It then has a chance to perform any further domain value changes, for example for reasons of domain model constraints, silently veto the change by returning the old value, or veto the entire transaction by throwing. If further state is entered into the transaction by this means this process continues until there are no new participants.
Then, call the :on-commit
function, passing the accumulated participants and their actions.
These arguments are maps:
participants -> {<identity> <value>}
actions -> {<identity> <action>}
The action is one of :create
, :mutate
or :delete
. The value is the instance or, in the
case of :mutate
a map containing {:old-val <original> :new-val <current>}
. A convenience
function write-txn-state
will perform all actions to the golden sink, as in the example, above.
If an exception occurs in the transaction body, all transaction state is discarded and
any :on-abort
function is called.
If held, this function will be called whether the transaction was committed or aborted.
The library includes a simple lock manager. If, for example, a transaction wishes to gain
exclusive access according to some domain value, :do-on-start
might be
(entitytxn.lock/lock some-value)
The lock manager can be used directly, in which case locks are not part of transaction state. Locks taken out as above will be automatically released when the transaction closes.
By now it will be clear that entity-txn
and the other entity-...
libraries are aimed
at bringing domain types, model management and I/O into the Clojure functional world.
See entity-core
and entity-sql
for further details
Often, many type instances must be brought together in a nested structure to support some
particular processing. This task is accomplished by entity-core
's aggregate
function
and to ensure such instances are managed, there is entitytxn.aggregate/aggregate
.
If you are not using entity-core and instead providing your own type system, you might find
it useful to provide your own such function. This is quite straightforward to do, for
example using the excellent specter
library.
[entity/entity-txn "0.1.3"]
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close