Catharsis - Tracking, PAX design pattern
This article is the next step in Catharsis documented tutorial.
Catharsis is a web-application framework gathering best-practices,
using ASP.NET MVC (preview 5), NHibernate 2.0. All needed source code
you can find on
http://www.codeplex.com/Catharsis/
No. |
Chapter |
No. |
Chapter |
No. |
Chapter |
|
I |
New solution |
VI |
CatharsisArchitecture |
XI |
(Controller layer - undone) |
|
II |
Home page observation |
VII |
Entity layer |
XII |
(UI Web layer - undone) |
|
III |
Roles and users |
VIII |
(Data layer - undone) |
XIII |
Tracking, PAX design pattern
|
|
IV |
Localization, translations |
IX |
(Business layer - undone) |
XIV |
Catharsis Dependency Injection (DI, IoC)
|
|
V |
Enter into Catharsis, new Entity |
X |
(Model layer - undone) |
XV |
(Code Lists - undone) |
Tracking, PAX design pattern
Tracking is ethereal sprite.
Whenever new project starts, no one (business customers) is demanding tracking (saving changes to allow rollback in time). And then, when application is going to product environment, everyone (business customers) is expecting there is tracked history (and SQL servers’ logs are not solution for changes tracking)
Everyone has met at least one design for changes tracking. And it probably won’t take long time to describe ‘some’ approach how to manage changes. And it also won’t take a long time to prove – in production environment – that such a (first-in-mind) solution is slowing application, wasting storage space and even not providing what was expected.
That goes to mainly used solutions:
1) Let’s have a table Person
, there are Person
needed columns (Name
, Code
) and tracking columns (DateTime
– Inserted
, Modified
, Deleted
…) = dates are stored (and sometimes logins
) to know about when was record created or deleted and when last time modified (attention – only last modified date is stored). Is this Tracking?
2) Table Person
has tracking columns ValidFrom
and ValidTo
, every change will end validity of current row and append new row with new values. The more columns Person
has (Address, BirthDate, FireDate, HireDate, ID number, Nationality, Country….) the more space you are wasting by changing one property (whole row must be newly inserted)
Those two are from my experience 95% percent designs of so-called tracking. Better then wasting time by pointing on disadvantages, let’s talk about another approach – used by Catharsis. You can than compare yourself (and even get me(us) some notes).
Before we’ll concern on Catharsis tracking features, we’d go through the PAX design pattern. The main reason for introducing PAX
is to get the rules how and when track change. Because to track everything, does not make a sense for any business object – entity.
PAX design pattern
PAX == Prenatal
, Alive
, eXpired
– abbreviation for three life-cycle conditions of any object. This design pattern is new in the manner ‘firstly named and described as PAX
design pattern’. It comes from reality and everyday usage. Even the words for all three conditions are used for a long time (the only tricky thing is the abbreviation PAX
).
Despite of the fact, this is the ASP.NET and C# article; next few paragraphs will describe the business case. In the second half of this chapter, we’ll return back to implementation.
In practice, is PAX
used for dozens of years, for example in insurance business. The entity’s life-cycle starts with Prenatal
and could end in eXpired
condition.
Prenatal
Remember or imagine: You are visited by insurance agent, during few minutes you are signing filled form on your new policy (and feel safely). The contract must be processed by responsible person, which must verify if all needed stuff was supplied and if client is not so risky one. Even at this time, the contract goes to production system.
Until responsible person confirms the agreement any changes can be done. Incorrectness can be overwritten, amounts can be recalculated … No need to track these changes. The policy is - from the insurance company point of view – just incubating. Let’s call this life-cycle condition as Prenatal
.
What should be said: Prenatal
condition could be the last one. There is no need to go to Alive
. When we’ll follow our insurance example, the risk could be so great for the insurance company that the final agreement does not have to be expressed.
And what’s more: If any object (policy) is deleted at Prenatal
condition, it is deleted without any chance to roll-back, it’s simply and definitely removed from the storage.
Alive
After all checks are done and the policy looks profitable also for insurance company – the policy is agreed and its life-cycle condition is set to Alive
.
You (client) can move and your address could change. You can get married and have a new name. You can find some misspelling, ask for insurance changes … many other reasons can lead to adjusting your policy. If any such a demand arises, the product system must be able to update current records, but also keep the historical values. Insurance is fragile thing, the policy performance (we are talking about money!) can appear at any time, and therefore the exact records of how was the policy agreement set to specified date is crucial.
Alive
life-cycle condition could be described as opposite to Prenatal
. In Prenatal
the last change wins, nothing else then current state is stored. Alive
condition needs to track everything including when the condition was changed from Prenatal
to Alive
.
eXpired
As from the words we used for PAX
abbrev results – Alive
condition is not the last one. Nothing can live forever, and even insurance policy must expire sometime.
What is the main difference between Alive
and eXpired
conditions? Alive
object’s can be changed in any way user need, but the last change is the condition switch from Alive
to eXpired
.
eXpired
condition (in opposite to Prenatal
and Alive
) cannot be changed in anyway. It’s frozen. It’s read-only from that moment forever. Users can search for such objects - entities; list them; overview details and history (changes tracked during Alive
condition). But no changes are allowed.
What should be said: Object could be by-user-intention switched from Alive
to eXpired
. But there is one special circumstance when this change is done by default: Deleting Alive
object.
If user deletes Alive
object, the PAX
pattern will provide two changes
1) Object validity is ended. From that moment (the moment which is also tracked) is object not valid anymore. (In our developers’ language, the ValidTo property is set from positive infintity (like 31/12/9999) to e.g. Today dd/MM/yyyy 0:00)
2) Condition is changed from Alive
to eXpired
.
Tracked objects (Alive
) cannot be removed from the storage and also cannot be used anymore as the Alive
ones. eXpired
condition will prove that in the future users still can ask for those objects and their formal information which were valid to specified DateTime
.
Once more to our example with insurance company – some kinds of insured events can appear even when the policy expires. Those events can belong to ‘insured period’ (Your car was finally - after months - reported as the reason of some accident). At those situations are eXpired
records beyond price.
PAX summary
Enough was the theory. ASP.NET and C# terms should be (and will be in the rest of this article) the points of our interest. But explanation of the PAX
design pattern was needed excurse. In your future applications will similar business-rules and requirements (and maybe even from insurance industry) play the title-role.
Catharsis Tracking
Now if we now (due to the PAX
design pattern) when track changes, let’s do some observation of how tracking is provided in Catharsis.
Prerequisites to use tracking
The only task you have to do for using built-in tracking is to derive your Entity class from Tracked
base object and do not set auto-identity on entities table (it will be provided by mother object – table). That’s all. Nothing else.
The best way to do it is to use Guidance wizards. They will help you to create infrastructure for every new entity with built in tracking. You only have to confirm the checkbox Tracked
on the second wizard’s page and all needed stuff will be injected into newly created (or recreated) classes.
That’s the preferred way. Remember! Do not set auto identity on for derived entities - tables (for MS SQL Server, similar for others).
The architecture
Catharsis tracks only changes.
Every Tracked
object (due to inheritance including descendents) has these properties and methods built-in:
LifeCondition Condition { get; }
bool MakeAlive();
bool MakeExpired();
bool IsReadOnlyMode { get; }
bool IsTrackedMode { get; }
IList<IChangeSet> Changes { get; set; }
IChangeSet NewChange();
IChangeSet AddChange(IChangeSet changeSet);
DateTime ValidFrom { get; set; }
DateTime ValidTo { get; set; }
DateTime? ToDate { get; set; }
This set (and some supporting tables in storage) will provide the tracking behavior as described in the PAX
design pattern.
Entity Table
Tracked object in Alive
condition keeps its up-to-date values stored in the database - in its table in the current row. If any property is changed, the value in that row (property column) is overwritten. The previous value is stored separately in the Changes
collection (table) with the exact ValidFrom
and ValidTo
settings.
Therefore, if you are working with ‘currently valid’ records, you can use the storage directly. Every entity has only one row in the table, every row matches actually valid values.
Searching, listing or detail overview is not overloaded with other calculations (which record is valid), your database storage is keeping lightweight, fast and healthy as possible.
IChangeSet
There is a separate table named ChangeSet
which is keeping old values for every tracked entity.
IChangeSet
object is on the .NET side responsible for keeping historical values. Basically we can say it contains information about which property (name) has which previous-value valid from – to. That’s all (there are some optimizations for storing values; it is transcending the scope of this article, but you can observe the source code …)
Tracked object has its collection Changes
which could be accessed as you need. Due the NHibernate essential ability called ‘lazy
-mode’ you can be sure, that it will never burden the performance of your application if you do not use it! Without that assurance (no insurance anymore!) it could influent the performance!
You can display Changes
collection or restore previous values to asked date, because you simply have the needed information.
ValidFrom and ValidTo is a trick
Yes, there is a trick for the client code. The Tracked object in fact does not have DateTime
properties ValidFrom
and ValidTo
! These are only in run-time calculated values.
The Tracked entity has two collections ValidFroms
and ValidTos
for maybe not so obvious reason: even ValidFrom
and ValidTo
must be tracked (efficiently). There can be a ‘Department’ entity. One instance could be intentionally created for one purpose for bounded time period from 1/1/2000 till 31/12/2000 (the goal usually is to ‘improve’, ‘reengineer’ etc.). During (15th) November 2000 is clear, that there is still need of that well-doing Department, let’s say another year. The validity is therefore changed to 31/12/2001.
As you expect (in Catharsis) there is record in the collection of ValidTos which keeps information that until 15/11/2000 the Department was valid only for a year after, and from 15/11/2000 its valid for two years.
The main reason for tracking ValidFrom
and ValidTo
separately from another changes is to have possibility to directly use SQL syntax to find out correct records. The ChangeSet
design – to compare - is working on the application level where .NET is restoring properties ex post, after they are loaded from SQL to memory.
Historical mode
As already said, your Tracked
object contains the change set, which you can use as needed (e.g. to display to users the list of changes etc.)
There is more, one of Catharsis built-in features allows you to simply turn back the time. Tracked
objects can be restored to state they have at any time you need. The only needed thing is to call the Dao
with required DateTime
which are you interested in. Tracked object will completely restore its properties using the Changes
collection.
If for these purposes user will use the Catharasi UI-web switch ‘History mode’, application will also switch to read-only mode which should help to avoid any unintended results.
Value types ver. object types
All, what was described above, can be applied only on the Entity’s value-type properties. Object properties (Person
has Address
, Contract
has Supplier
) could not be tracked that way, because it does not make a sense. They are simply stored as reference (ID
)!
The solution must be based on a middle laying object - pair object (in db syntax - pair table). Let’s say there are two objects, Role
and User
, which can contain each other (Role.User
or User.Role
). If that linkage is intended to be tracked in time, then there must be the pair object with validFrom and validTo settings.
Catharsis is able to track also these properties, but you must provide their intermediate pair objects. The example could be the pair object for AppUser
and AppRole
public class AppRoleAppUserPair : TrackedPair<AppRole, AppUser>
{
public override AppRole PrimaryOwner { get; set; }
public override AppUser SecondaryOwner { get; set; }
}
And the NHibernate mapping
='1.0' ='utf-8'
<hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'
namespace='Firm.Product.Entity.Services' assembly=' Firm.Product.Entity '>
<joined-subclass name='AppRoleAppUserPair' table='xyAppRoleAppUser'
lazy='true'
extends='Catharsis.Entity.PersistentObjects.TrackedPersistentObject
, Catharsis.Entity' >
<key column='AppRoleAppUserId' />
<many-to-one name='PrimaryOwner' column='AppRoleId' lazy='false'
class='AppRole' ></many-to-one>
<many-to-one name='SecondaryOwner' column='AppUserId' lazy='false'
class='AppUser' ></many-to-one>
</joined-subclass>
</hibernate-mapping>
Test and observe built-in Role
and User
handling, it will uncover you more. Guidance provides few snippets which can help you with implementation. If you want to know examine - on the other hand you can simply start to use it; tracking is built-in and available for you in every new Catharsis solution.
Summary
Tracking is ethereal sprite. There are many copies …You can use or prefer any other approach.
Catharsis solution is ‘storage and performance efficient’ and for more use-cases ready to use. And what is more important, if you would like to change any part of it, you can do it on one place. Encapsulation will grant you, that any change made to tracking will be applied to every Tracked
object.
Source code
http://www.codeplex.com/Catharsis/
Enjoy Catharsis
History