Click here to Skip to main content
15,888,113 members
Articles / Web Development / ASP.NET

Web-Application Framework - Catharsis - part IX - Business layer

Rate me:
Please Sign up or sign in to vote.
4.74/5 (6 votes)
5 Oct 2008CPOL11 min read 33.6K   24   5
Catharsis web-app framework - Business layer

Introduction  

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 here.

Catharsis-title.png
No.ChapterNo.ChapterNo. Chapter
I  New solution VI CatharsisArchitectureXI Controller layer
II Home page observationVII Entity layer XII (UI Web layer - undone)
III Access rights,
Navigation
VIII Data layer XIII Tracking, PAX design pattern
IV Localization, translationsIX Business layerXIV Catharsis Dependency Injection (DI, IoC)
V Enter into Catharsis, new EntityX Model layer XV (Code Lists - undone)

Business layer  

Catharsis framework has real multi-tier architecture, and is strongly OOP; everything is subordinated to Entity’s instance handling.  

Entities are persisted in storages like database, xml etc. All CRUD (create, read, update, delete) activities are done using data-access-objects (Dao) on data layer. Daos provide in fact only the CRUD operations. 

Business layer brings order & low and serves as the only gateway between any (upper) layer which needs to access entities. Not only 'upper’ layer - there are for example Providers nested in ‘lower’ level like .Common project, which also access entities using IFacades.

Rules   

Applying business rules is one of the main tasks for the business tier. Rules can check ranges for numbers, length for strings, existence of inner objects etc. For example customer may wish to use application like evidence for an Entity and wants to store only country abbr. matching the code list, First and Second name longer then 2 letters, address containing number and so on. 

Another example of business rule could be request: 'delete' Entity. There must be check for users-access-rights and assurance that there is no reference remaining. And maybe other ‘customer’ rules which you can imagine or even remember.  

These rules contain business logic and they must be handled on a business tier. They are placed on one place and therefore can be simply maintained and reused. The same checks are applied for new Entity coming from UI as well for those coming through batch importing application.

Database features  

There are many features on database side like indexes, constraints, triggers ... ready to use. Some of them (indexes, keys, statistics) are vital components of your application and its future performance. Other can help you mainly during development (constraints).

Constraints are usually used, that’s really not contra productive, at least in development phase. They can quickly remind you where the business rules are missing (deleting). But it still means that every such a rule should be applied on a Façade too. SQL can help you but never should be the only guard. 

Forget stored procedures

Well, there are also features on db side, which should be never used when we are talking about multi-tier architecture (and OOP). That goes mainly to triggers and – stored procedures! Yes, I am serious! 

What can in fact do triggers or stored procedures do for you? Nothing more than to provide business rules checks. There are many applications using stored procedures as the only way to access stored data. Direct access to tables is disabled, application interfaces are the 'returned values' from SPs.   

Those application are not multi-tier ones. So called 'data' layer in such scenarios does not access data, but something like 'business' layer which was morphed into SPs. And that's really upside-down. Those application in fact act as the UI to database. (Don't you agree? good place for conflict, isn’t it?) 

One of unintended advantages of such approach is improved debugging. (Would you prefer to debug and trace: C# code or T-SQL?). Intended benefit is storage type independency. After some time you realize that users, roles, localized phrases etc. can be reused. Well, move them to another database or even xml, change CRUD methods on your Daos and that’s all. Rules are still applied even if the storage was changed.  

Gateway

There is solely standing, independent web-application with one database; users access data exclusively via web-application UI.  

Opposite to that dream is reality with ‘always’ needed export and import ability, usually based on MS Excel at least. If your application stands in the chain you simply must provide interfaces for data exchange, incoming and outgoing.

To satisfy customer’s requirements you will probably have to develop batch applications for exporting and importing, maybe some WebServices etc.

The gateway for them all must be already created, tested and working - Business layer. Anything or anyone who wants to access ‘your’ Entities must pass through your Customs office (called façade). If all rights are checked and requirements met, entity could be processed. The reuse is obvious. And what’s more, all hours spent on precious tuning of all these rules can give you the feeling of safety. 

Caching

There are objects from day to day usage of your application which could be cached (e.g. localized resources). Performance gain could be in these cases beyond price. Good candidates for caching are in time stable, not so often changing values. In spite of that suggestion they still must be up-to-date.  

There are nice SQL and File cache dependency handlers in .NET. In a nutshell, if you are using cache object, there is mechanism which will let your data expire, when any change in datasource appears. 

C#
SqlCacheDependency sqlDependency = new SqlCacheDependency("Northwind", "Products");
HttpContext.Current.Cache.Insert("ProductsList", products, 
                     sqlDependency, DateTime.Now.AddDays(1), Cache.NoSlidingExpiration);

Application cache - static dictionary object

Different type of Application cache are static objects. They are not thrown away when the memory starts to be full (in opposite to cache object) and they can store types not only objects.   

C#
protected static IDictionary<string, Translator> Translations { get; set; } 

On the other hand you need another way to handle refreshing. And that’s where Business layer can help you again.   

Cache (e.g. static <code>IDictionary object) should be placed right inside the façade. It gives you assurance, that every layer accessing Entity will be provided with fresh data. Controllers and models are created per request and then thrown away. There is no place for page or control caching either!   

Secondly, cache (static object) is lazily loaded - item by item. When first request for entity comes, Entity is loaded from storage and also cached. In contrast - cleared are all at once (after any change is reported). When the announcement for change arise, the whole object is cleared.   

Expiration monitor  

You can in some cases decide to cache data on a separate level. Good example are Providers (lists of roles, users, localization phrases...). In such situations you need the way how to let your cache know that change has appeared. That's the goal for OnChange event. (the same mechanism is or can be used for the expiration of cache inside the façade).   

You can sign on 'ClearDictionary' and clear your static cache.  

C#
  //... somewhere in the constructor
  ITranslatorFacade.OnChange += ClearDictionary;
  //...

public static void ClearDictionary(object sender, EventArgs args)
{
    Translations.Clear()
} 

Current Catharsis implementation expects that only way to change Entity is the web-application UI. There are watches on add(), update() and delete() facade's methods which will rise OnChange event. If you will extend your application with batches like data importers, you have to also extend the mechanism for rising OnChange event.  

AOP filters

ASP.NET MVC profits from the fact that there is no ballast from the past, from previous versions. MVC is growing on a best-practices and therefore the AOP (aspect-oriented-programming) is an essential pillar. In another articles on codeproject.com you can read about many AOP frameworks, solutions, approaches ...
But they very often act as the compensation of the original bad design (e.g. AOP for better viewstate handling in a web-form - is it AOP or bug fix?) 

Situation in ASP.NET MVC is totally different. There are filters based on attributes which could be ran before or after controller's action. Nice example is [Transaction] attribute. That AOP filter will open transaction before action is executing and commit (or rollback) it when action is executed. You do not have to care for that aspect, just decorate action with attribute [Transaction]

Other example is [Navigation]. Catharsis uses for work-flow handling a very very rigorous solution. There is switch-case clause dependent on a CurrentRole which fills the list of navigation links. These are stored in the model and rendered as a list of links on a master page.

But the power is in the approach. [Navigation] object is the AOP filter, which is ALWAYS evaluated and is the only responsible for filling the list of navigation links. You (or someone else) can create much more sophisticated handler e.g. based on xml scenarios... and replace current [Navigation] implementation. The change will take place only inside [Navigation] filter attribute, but the improvement will inflict the whole application.  

That's the Aspect-Oriented-Programming - AOP.  

Catharsis.Guidance

USE Guidance! They will save your time and improve effectiveness. On a Business project right-click on a folder and the context menu item will appear. Next steps are same as in the previous chapters... We'll continue with Person entity example (wizard-details are described in previous chapters):  

Create_business.png

IEntityFacade.cs  

Entity's facade methods will be accessible via IEntityFacade interface. This is almost empty interface - the right place for your future extension.   

C#
[ConcreteType("Firm.Product.Business.People.PersonFacade, Firm.Product.Business")]
public interface IPersonFacade : IFacade<Person> { }

IEntityFacade gains a lot from its base declaration: 

C#
public interface IFacade<T>
    where T : IPersistentObject
    {
        T GetById(int id);
        T GetById(int id, DateTime? historicalDateTime);
        IList<T> GetBySearch(ISearchObject<T> searchObject);

        T Add(T item);
        T Update(T item);
        void Delete(T item);

        IDictionary<string, object> ErrorData { get; set; }        

        EventHandler OnChange { get; set; }
    }

As we've discussed above, there is also the OnChange event.

EntityFacade.cs   

FacadeFactory, when asked for IPersonFacade interface, will provide new instance of just created PersonFacade. Guidance has created the skeleton in .business project for you: 

C#
public class PersonFacade : BaseFacade<Person>, IPersonFacade
{
    public PersonFacade() : base(DaoFactory.CreateDao<IPersonDao>()) { }

    public override IList<Person> GetBySearch(ISearchObject<Person> searchObject)
    {
        return Dao.GetBySearch(searchObject);
    }

    protected new IPersonDao Dao { get { return base.Dao as IPersonDao; } }
 }

BaseFacade has already implemented a lot of common behaviour. It is simple but too large to list it here, please see the source code. The attention should go to OnChange handling. I believe that the code is self documenting and a small extract will uncover the secret. 

C#
// extract BaseFacade

   ...
   // Example of a 'Set' method
   public virtual T Update(T item)
   {
       var t = Dao.Update(item);
       Change();                      // your listener is called
       return t;
   }        
   
   // OnChange works even for static objects like Providers!!   
   private static EventHandler _onChange { get; set; }
   
   public EventHandler OnChange    // Append your ClearDictionary here
   {                               
        get { return _onChange; }  
        set { _onChange = value; } 
   }

   protected virtual void Change()
   {
       if (OnChange != null)
       {
            OnChange(this, new EventArgs());
       }
   }
   ... // BaseFacade continues 

Error data

Well, we are discussing the Business layer as the place for business rules checking. This key goal can work only if there is a way how handle error states - situations when the business rules are not met.

ErrorData collection is the solution. And center-point of every facade. As you can see in the IFacade declaration, every facade has ErrorData collection with one simple purpose:

Fill ErrorData with any incorrectness you'll find during business rules checking.

Every business rule check must succeed or must be reported. If evaluation fails, put record into ErrorData. Business rules accomplished on facade must result in:

  1. Reporting all bugs via ErrorData collection and break execution
  2. Execute required changes only in case there are no errors.

Instead of throwing an exception the facade caller gets the list of errors. Empty collection means that everything went right. If there is any error message, required changes should not be persisted.  

The above code extract could be extended: 

C#
public virtual T Update(T item)
{
    if ( CheckBusinessRules(item) )
    {
        var t = Dao.Update(item);
        Change();                      // your listener si called
        return t;
    }
    // ErrorData are filled with messages,
    // which should be used on calling layer

    return t;  // unchanged 't' is returned
}

public bool CheckBusinessRules(T item)
{
    bool success = true;
    if ( item.Name.Length < 2 )
    {
        ErrorData["NameShort"] = "NameIsShort";
        success = false;
    }

    // TODO check rules and fill ErrorData
      ...

    return success;
}

Catharsis ErrorData handling

Model (in next chapters we'll discuss it more) also contains its own ErrorData collection. Catharsis always renders the content of the Model.ErrorData. It means that if there is any message in the ErrorData - its always displayed on UI. This error collection in Model is used by controller in cases of type-incorrectness (e.g. date was 2008/31/13...).  

There is more for you - whenever controller gets the instance of IFacade from FacadeFactory, the IFacade.ErrorData is set to reference to Model.ErrorData!!! 

The gain is obvious, and again beyond price! When filling ErrorData collection on facade,  the Facade.ErrorData are in fact the Model.ErrorData. Its content is than always directly displayed to users, and keep them informed about troubles.

Separation of concern wins again. On facade you are filling collection ErrorData. On UI you are displaying ErrorData (if any). Catharsis infrastructure ensures that you are working with one ErrorData instance and don't have to care about Error-data-exchange among layers. 

That behavior is working for Catharsis web-application framework! If you will use facade in another application (batch importer) you have to take care! You must evaluate Facade.ErrorData and made some steps e.g. logging ... if error appears. 

Catharsis built-in Facades  

There are roles and localizations Providers built-in Catharsis and ready for use. They are nested in .Common project (which is 'lower layer' then business). But IAppRoleFacade, IAppUserFacade and ITranslatorFacade interfaces are known even on Common layer (where are stored) and therefore even Providers can access them.   

That's another good example of very-well working separation of concern! 

Roles & users

Users can be added to application in run-time (in production). Because users's access rights are evaluated almost on every method call, the list of roles and users is cached. Whenever there's a change reported (e.g. new user added) the cache is cleared. (please, observe)

Translation

Localized phrases are the pillar of Catharsis user-friendly UI. They are used almost on every page and when adjusted they are fixed. This is another good example of application cache usage, which have crucial impact on application performance.  

CodeLists  

There'll be separate chapter for CodeLists. Those are value lists which are very rigid and localized to provide better comfort for user. E.g. Country, Currency, scale (bad, neutral, good), type (inner, outer) and so on...  

Batch export, import  

Final note goes to application extensions - exporters, importers, queue listeners and responders. Always build your design on Business layer. Once you are sure that facade is adjusted, you can expose and reuse it in any other app like batch or service.
Such application should take care about messages stored in facade's ErrorData collection.

Enjoy Catharsis!

Source code

http://www.codeplex.com/Catharsis

History

  • 5th October, 2008: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Czech Republic Czech Republic
Web developer since 2002, .NET since 2005

Competition is as important as Cooperation.

___

Comments and Discussions

 
GeneralSo is the business layer a partial class, or a whole facade class Pin
philmee953-Nov-08 9:41
philmee953-Nov-08 9:41 
GeneralRe: So is the business layer a partial class, or a whole facade class Pin
Radim Köhler3-Nov-08 19:19
Radim Köhler3-Nov-08 19:19 
GeneralRe: So is the business layer a partial class, or a whole facade class Pin
philmee954-Nov-08 6:37
philmee954-Nov-08 6:37 
GeneralCode Lists Pin
Eng. Jalal19-Oct-08 23:33
Eng. Jalal19-Oct-08 23:33 
GeneralRe: Code Lists Pin
Radim Köhler19-Oct-08 23:38
Radim Köhler19-Oct-08 23:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.