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






4.74/5 (6 votes)
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.

No. | Chapter | No. | Chapter | No. | Chapter | |
I | New solution | VI | CatharsisArchitecture | XI | Controller layer | |
II | Home page observation | VII | Entity layer | XII | (UI Web layer - undone) | |
III | Access rights, Navigation |
VIII | Data layer | XIII | Tracking, PAX design pattern | |
IV | Localization, translations | IX | Business layer | XIV | Catharsis Dependency Injection (DI, IoC) | |
V | Enter into Catharsis, new Entity | X | 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. Dao
s 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 Dao
s 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.
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.
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
object) should be placed right inside the IDictionary
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.
//... 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):

IEntityFacade.cs
Entity's facade methods will be accessible via IEntityFacade
interface. This is almost empty interface - the right place for your future extension.
[ConcreteType("Firm.Product.Business.People.PersonFacade, Firm.Product.Business")]
public interface IPersonFacade : IFacade<Person> { }
IEntityFacade
gains a lot from its base declaration:
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:
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.
// 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:
- Reporting all bugs via
ErrorData
collection and break execution - 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:
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