Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Web-Application Framework - Catharsis - part XIII. - Tracking, PAX design pattern

0.00/5 (No votes)
25 Sep 2008 1  
Catharsis web-app framework - Tracking, PAX design pattern

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/

Catharsis-title.png

No. Chapter No. Chapter No. Chapter

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)

Enter into Catharsis, new Entity (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 (DateTimeInserted, 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.
recreateEntityW2T.png 
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   

<?xml version='1.0' encoding='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  

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here