This page attempts to outline the overarching concepts related to the life cycle of service transactions in Verne. A life cycle in this context is the following:
Life Cycle Definition
The journey and various states a “thing” (i.e. any root domain e.g. company, reserved name, user, business calendar, payment basket, etc.) can take throughout it’s life. Therefore this encompasses it’s creation (migration/registration) as well as any maintains or corrections performed on it.
This page relies heavily on concepts outlined in the business service concept related to the life cycle such as business services, registry data, service instances and service transactions – get very comfortable with these concepts.
Service Instance and Service Transactions
The business service concept outlines the relationship between a service instance and its service transactions. In short, there may be one service transaction used for the duration of a service instance but there can equally likely be multiple service transactions per service instance.
Also get familiar with versioning and the service transaction, in particular the following service transaction properties which are important when it comes to related service transactions for the same service instance:
| Property | Description |
|---|---|
serviceIdentifier | Identifies all service transactions relating to the same service instance |
committedDate | Marks a service transaction as being user-accepted i.e. non-committed service transactions are not seen by most system processes (except merge) |
prevSiblingId | Links back to the service transaction this one is making additional changes to (another user-saved action) |
nextSiblingId | Links to the service transaction that supersedes this one in terms of a maintenance (i.e. not a correction) if applicable |
prevMergeId | Links back to the service transaction this one was before it was successfully merged with another (referenced in the merge.fromVersionId property) |
nextMergeId | Links to the service transaction that replaced this one during a successful merge |
merge | Contains the details relating to a merge attempt. If successful, a nextMergeId will also exist, but merge details will exist regardless i.e. even for merge conflicts, so the merge details reflect whether a merge was attempted on a service transaction, whether successful or not, whereas the prevMergeId/nextMergeId values only relate to successful merges |
ghost | Whether the service transaction is considered throw-away data. e.g. views or uncommitted user edits that were discarded, either explicitly by the user or was abandoned and expired or some other system function |
ghostReason | Why the service transaction became a ghost if it is one |
Superseded Service Transactions
If a service transaction has a value for either of the next identifiers above (i.e. version, sibling or merge), then the service transaction is considered superseded and no longer "active". If a service transaction has merge details but no nextMergeId, then it is the result of an unsuccessful merge (a merge conflict). In this case it can still be active in some senses (a user can have it loaded) but it can no longer be acted upon and is considered a dead end.
The following is the list of service transaction properties that, if they have a value, define a service transaction as being in superseded state and the typical action that put them there:
nextVersionId– a new maintain service was applied/activatednextSiblingId– a more recent edit has been user-saved (if not activated); or a correction has been applied/activated (if it is activated)nextMergeId– changes were made to another service transaction that were successfully merged into this onemerge(but nonextMergeId) – changes were made to another service transaction that resulted in a conflict when trying to be merged into this one
Details about when these values are set can be found on the versioning page.
Multiple Service Transactions per Service
Verne automatically saves the service transaction whenever a field is changed, so you can’t not-save your changes as such. So how do we support scenarios like when a user starts a service, saves and exits, loads it up again (from their dashboard) to continue working on it but then decides to cancel and discard their changes?
The answer is when a saved service transaction is loaded (from a user’s dashboard) instead of the existing service transaction being loaded up, a copy is made that can either be discarded or accepted at some point, in which case it would supersede the original one it was based off (with appropriate previous/next* sibling values set).
It is worth noting that previous sibling IDs (and previous version IDs) are set (when relevant) on the service transaction as soon as it’s created, so it knows “where it came from”, but the one it was based off is left alone (more than one service transaction can be based off it after all) until one of those service transactions is user-saved/committed (activated for next version IDs) at which point the next sibling/version ID is set on the originator.
Separate service transactions per user-loaded or user-saved change allow us to support some advanced features like:
- restoring unsaved changes if a user abandons their session (i.e. closes their browser) without choosing whether to save or discard their changes
- showing only the changes made since last reviewed on the internal task review screen when a service is returned to the presenter for revision then resubmitted
Service Transaction Tips
"Tips" relate to service transactions that have no yet been applied from a business perspective. Additionally a "tip" only applies to service transactions that have not been superseded or set to a dead-end state. Therefore service transaction tips can be thought of as service transactions that are still in play and can contribute to a service instance.
Below is a simple walk through of the start of a service to illustrate the life cycle of service transaction tips:
- You start a new business service. A new service transaction is created (ST1) which is the only service transaction relating to this service instance and will be a tip (unless it was activated right away, in which case it would be a head rather than a tip but let’s stick with the more standard workflow). This service transaction will have a
serviceIdentifierassigned that is unique for this instance of the service. - You save and exit. ST1 is still the only service transaction for this service (i.e. with the same
serviceIdentifiervalue) and is now the current tip since it has been user-saved (committed) and will have itscommittedDateset - You then load the existing service via the dashboard. A new service transaction will be created (ST2) based off the current tip (ST1) and these two service transactions will be related in the following way:
- They will have the same
serviceIdentifierindicating they belong to the same service instance - ST2 will have the
_idof ST1 as itsprevSiblingId, indicating that it is based off ST1 - ST1 will not have ST2 as its
nextSiblingIdas it hasn’t been replaced by it or anything. ST1 is still the current tip for the service instance
- They will have the same
- You make some changes and save and exit ST2. The following will now occur:
- ST1 will have ST2 as its
nextSiblingIdmaking ST1 a superseded service transaction and largely ignored by the system from now on - ST2 is now committed (has a
committedDate) and is the current tip for the service instance
- ST1 will have ST2 as its
- You load the existing service via the dashboard. A new service transaction will be created (ST3) based off the current tip (ST2) and these two service transactions will be related in the usual manner (see above). There are now two tips for the service instance: S2 (current) & S3
- You make some changes but then discard/cancel ST3. S3 will get set to a ghost (
ghostset to true and aghostReasonof ‘discarded’) and you’ll be back there being only tip for the service instance (S2) which is also the current tip
As you can see from the example above, there can be multiple service transactions created during the process of submitting a service instance (i.e. those sharing the same service service identifier (serviceIdentifier), but there is only ever one one current tip. The only difference between a non-current tip and the current tip is the current tip has a committed date (committedDate).
Current Tip The term “current tip” is used to reference the non-superseded, committed service transaction relating to a non-activated service instance. The current tip is the service transaction that new edits will be based off for the given service service identifier.
A tip could relate to a service transaction that has not been user-saved/committed yet, or be the current tip if it has been user-saved/committed. A tip can be in any state of being applied e.g. pending, review, revise, etc.
Since correction services cannot be saved before being applied, correction services only ever have one tip, and it is never current since it is not committed until applied (at which point it becomes a head).
Formally, a service transaction is a tip if it is:
- not a ghost
- has not been superseded by having a next sibling or merge ID or merge details as the result of a merge conflict
- is either a correction that has not yet been committed or does not activated status
Note: A service transaction that is the result of a conflicted merge has not been superseded by another transaction but it still not considered a tip since it is in a dead-end state, therefore no longer active.
Service Transaction Heads
"Heads" relate to service transactions that have been successfully applied/activated.
There can be multiple heads (i.e. previous heads and the current one), but only one current head for a given identifier. The only difference between a non-current head and the current head is the current head does not have a next version ID, whereas all others do.
Current Head The term current head is used to reference the current, activated service transaction, reflecting the current entity (or thing) data. The current head is the service transaction that new maintain services are based off, as well as views of the current entity data.
The term “head” only ever applies to activated service transactions, whereas tips relate to service transactions that have not yet been applied. A current tip becomes a head version when it is applied (the current head in fact if it is a maintain service and either the current head or a plain old previous head if it’s a correction).
A head transaction can only become a non-head if corrected, in which case the new correction will be the new head. Conversely any head transaction (and only head transactions) can be a target for correcting. Un-applied corrections are never a head version even if the status is activated since they can not be user-saved/committed before being applied, so they go straight from being an active tip to a head (when applied) and the current head if correcting the current head.
Formally, a service transaction is considered a head if it is:
- is activated
- is committed (while you can’t usually have activated non-committed transactions, you can for corrections, so this is used to ensure we exclude non-applied corrections)
- is not a ghost
- has not been superseded by having a next sibling or merge ID or merge details as the result of a merge conflict
Dead-End States
Service transactions in a dead-end state are essentially ignored by the system. This means they are not included when retrieving service transactions for listing dashboard items, performing merges, checking conflicts etc.
A service transaction is considered in a dead end state if it can no longer be acted upon or updated by a user (whether the presenter or someone else) or any system process. The exception to this is if a user is already viewing the service transaction when it is put into a dead end state e.g. it was the target of a merge and is now either superseded by the successful merge result or is in a state of conflict.
The following are ways in which a service transaction are defined by Verne as being in a dead-end state:
- it has been superseded
- it is a ghost
- it has a status relating to an end-state. This can be configured, but Verne includes the following statuses in this category as standard:
- cancelled – the service instance has been cancelled by the presenter
- expired – the service Instance has expired by a day-end job as it was not submitted/activated within the allotted time frame
- rejected – the service instance has been rejected by an internal reviewer
Example
- (ST1) A user wishes to register a new company, so they select a ‘register company’ menu item:
- A new service transaction is created (ST1)
- ST1 gets
_id,identifierandserviceIdentifiervalues set (amongst other things) - We have one service transaction: ST1 (a tip)
- (ST1) The user saves and exits after making some changes:
- The service transaction is saved on every update but is registered as a special save-commit when the save & exit is performed. This means the
committedDateis set on it - We still have only one services transaction: ST1 (current tip)
- The service transaction is saved on every update but is registered as a special save-commit when the save & exit is performed. This means the
- (ST2) The user selects the saved, but unfinished company registration from their dashboard:
- A second service transaction is created (ST2) for these new changes, with a
prevSiblingIdlinking to the_idof ST1 - ST2 gets a unique
_idvalue, but the sameidentifierandserviceIdentifiervalues as ST1 - We now have two service transactions: ST1 (current tip) and ST2 (a tip), linked to each other
- A second service transaction is created (ST2) for these new changes, with a
- (ST2) The user submits the service and it is activated right away (i.e. there’s no workflow such as payment or review required):
- ST2 becomes the (current tip) (by virtue of being submitted) and is then activated, so becomes the (current head) since it now represents the entity data is live on the register for that
identifier - ST1 became superseded by the second one when ST2 became the current tip. This meant the
nextSiblingIdon ST1 was set to the_idof ST2 - We still have two service transactions: ST1 which is superseded (in an end state) and ST2 (current head), linked to each other
- ST2 becomes the (current tip) (by virtue of being submitted) and is then activated, so becomes the (current head) since it now represents the entity data is live on the register for that
- (ST3) A few days later the user (or someone else with authority) decides to maintain the address, so selects this option (most likely via a menu item or button):
- A new service transaction is created (ST3) targeting the current head using the internal
identifierfrom ST2 (which of course is also the same for ST1) - ST3 gets new
_idandserviceIdentifiervalues set (amongst other things) but has the sameidentifiervalue as ST1 & ST2 - ST3 gets the
domainTreeset using the exactdomainTreefrom ST2 - We have three service transactions: ST1 (superseded), ST2 (current head) and ST3 (a tip)
- A new service transaction is created (ST3) targeting the current head using the internal
- (ST3) The user saves and exits ST3 after making some changes:
- ST3 gets the
committedDateis set on it - We still have three service transactions: ST1 (superseded), ST2 (current head) and ST3 (a current tip)
- ST3 gets the
- (ST4) The user selects the saved, but unfinished maintain address service (ST3) from their dashboard:
- A new service transaction is created (ST4) for these new changes, with a
prevSiblingIdlinking to the_idof ST3 - ST4 gets a unique
_idvalue, but the sameidentifieras ST1, ST2 & ST3 and the sameserviceIdentifieras ST3 - We have four service transactions: ST1 (superseded), ST2 (current head), ST3 (a current tip) & ST4 (a tip)
- A new service transaction is created (ST4) for these new changes, with a
- (ST5) Another user with authority decides to maintain the company name, so selects this option (most likely via a menu item or button):
- A new service transaction is created (ST5) targeting the current head using the internal
identifierfrom ST2 - ST5 gets new
_idandserviceIdentifiervalues set (amongst other things) but has the sameidentifiervalue as ST1, ST2, ST3 & ST4 - ST5 gets the
domainTreeset using the exactdomainTreefrom ST2 - We have five service transactions: ST1 (superseded), ST2 (current head), ST3 (a current tip), ST4 (a tip) & ST5 (a tip)
- A new service transaction is created (ST5) targeting the current head using the internal
- (ST5) They change the name in ST5 and submit the service and it is activated right away once again:
- ST5 becomes the (current tip) (by virtue of being submitted) and is then activated, so becomes the (current head) since it now represents the entity data the is live on the register for that
identifier - ST2 becomes a previous/non-current head. This means it has its
nextVersionIdset to the_idof ST5 - The address changes from ST5 (based off ST3) get merged into all service transactions also based off ST3 that are still active, which means: ST3 & ST4
- A new service transaction is created (ST6) which is ST3 with the changes from ST5 merged in. ST6 gets a unique
_id, itsprevVersionIdset to ST5‘s_idand aprevMergeIdset to ST3‘s_id. ST3 becomes superseded by ST6 as thenextMergeIdis set to the_idof ST6 - A new service transaction is created (ST7) which is ST4 with the changes from ST5 merged in. ST7 gets a unique
_id, itsprevVersionIdset to ST5‘s_idand aprevMergeIdset to ST4‘s_id. ST4 becomes superseded by ST7 as thenextMergeIdis set to the_idof ST7. Since ST4 is still being edited (i.e. open in the user’s browser) they’ll receive a message indicating that changes have been merged, and do they want to reload with the latest changes. They select yes, so ST4 is replaced by ST7 as the loaded service transaction
- A new service transaction is created (ST6) which is ST3 with the changes from ST5 merged in. ST6 gets a unique
- We have seven service transactions: ST1 (superseded), ST2 (a head), ST3 (merged), ST4 (merged) & ST5 (current head), ST6 (a current tip) & ST7 (a tip)
- ST5 becomes the (current tip) (by virtue of being submitted) and is then activated, so becomes the (current head) since it now represents the entity data the is live on the register for that
- (ST8) Another user (let’s say it’s linked to an organisation) selects the saved, but unfinished maintain address service (ST3) from their dashboard:
- A new service transaction is created (ST8) for these new changes, with a
prevSiblingIdlinking to the_idof ST6 - ST8 gets a unique
_idvalue, but the sameidentifieras ST1, ST2, ST3, ST4, ST5, ST6 & ST7 and the sameserviceIdentifieras ST3, ST4, ST6 & ST7 - We have eight service transactions: ST1 (superseded), ST2 (a head), ST3 (merged), ST4 (merged) & ST5 (current head), ST6 (a current tip), ST7 (a ghost) & ST8 (a tip)
- A new service transaction is created (ST8) for these new changes, with a
- (ST8) The user makes some changes in the maintain address form and saves & exits:
- ST8 gets a
committedDateset on it - We have eight service transactions: ST1 (superseded), ST2 (a head), ST3 (merged), ST4 (merged) & ST5 (current head), ST6 (a current tip), ST7 (a ghost) & ST8 (a tip)
- ST8 gets a
- (ST7) The user selects to cancel their edits, discarding their changes to the saved maintain address service:
- Since ST7 was not save-committed, it is simply discarded. This means it is given a
ghostvalue of true with aghostReasonof discarded - We still have seven service transactions: ST1 (superseded), ST2 (a head), ST3 (merged), ST4 (merged) & ST5 (current head), ST6 (a current tip) & ST7 (a ghost)
- Since ST7 was not save-committed, it is simply discarded. This means it is given a

