Thursday, February 28, 2008

N-Tier with ASP.NET

1. Abstract:
For a last couple of weeks I've been looking for a good example of implementing N-tier approach for ASP.NET application. Our company is switching its Intranet from plain old ASP to a modern ASP.NET (3.5) application. It's been proposed to follow a modular N-tier approach.

2. Big Ball of Mud:
I was googling for N-Tier ASP.NET application. All I found was unbelievable bad. It looks like that all those authors from just heard buzzwords like N-Tier, O/RM, loose coupling, but never understood them. Instead they went ahead and implemented their little poor-designed, overcomplicated examples where all the layers are tied together in a Big Ball of Mud (BBM) where each layer directly uses ADO.NET and heavily depends on datasets with millions of useless methods;   - and they immediately published their articles. BBM examples:
Designing N-Tiered Data Access Layer Using Datasets
N-Tier Web Applications using ASP.NET 2.0 and SQL Server 2005

3. I still hope:
I'm still looking for a good example of N-Tier approach with ASP.NET and ADO.NET. I still hope that one of the following articles might be good:
Building Layered Web Applications with Microsoft ASP.NET 2.0
Implementing a Generic Data Access Layer in ADO.NET
Architecting LINQ To SQL Applications

4. Foundations of Programming (N-Tier, DDD, DI, NHibernate):
I was happy to find Foundations of Programming series of articles written by Karl Seguin at the very beginning of my exercises. Despite its theoretical name, this series shows fairly clear and practical approach of designing modular C# [Web] application with separated business layer (Problem Domain), Data access layer (DAL), and presentation layer.

There are simple C# examples. You implement business (domain) objects like a Car, a Model, an Upgrade; type-safe collections of those objects are implemented as generic lists - List<Car>, List<Model>, List<Upgrade>.

Domain objects are almost persistent-agnostic: they do not know in details how to save themselves to a database or retrieve themselves from a database. All they really should know is how to create an instance of class which implements IDataAccess interface and then to call one of its GetCar(), GetAllModels(), SaveCar(), etc. overloaded methods.

Domain objects are completely presentation-agnostic. That's a task of presentation layer to databind to business objects and collections. As Karl said, "You'll also be happy to know that ASP.NET and WinForms deal with domain-centric code just as well as with data-centric classes. You can databind to any .NET collection, use sessions and caches like you normally do, and anything else you're used to doing."

Then we can create or own DAL methods or we can use one of O/RM tools like NHibernate.
Everybody agrees that NHibernate (Hibernate for .NET) is very good. It's one of the most known and widely used O/RM tools. It's free and well-documented. For me - it's a way to go.

We also can use Dependency Injection tools like StructureMap to test pieces of code.

5. Our possible design outlines:

So, a picture is pretty clear. We need

1) To implement set of classes which represent our business logic. There would be

    public class Employee {}
    public class Project {}
    public class WorkActivityReport {}
    public class Contract : WorkActivityReport {}
    public class CorporateTask : WorkActivityReport {}


2) To implement collection classes which also represent business logic. I would encapsulate collections into static container/factory classes. There would be

public static class ContractList
      // private static List<Contract> m_contracts;
      public static List<Contract> getContractList(int projectId, int sortOrder) {}

It makes perfect sense to delegate part of business classes' functionality to "Factory" classes. So, it could be a

public static class ContractManger
      public bool saveContract(Contract thisContract) {}
      public List<Contract> getContractList(ContractType thisContractType) {}

With Factory classes we might not need custom collection classes, but just use generic collections instead. All calls to DAL layer methods will be performed by Factory classes' methods. It makes sense. I even used this approach in my little poor VBScript classes (ASP). Imar Spaanjaars recommends it too.

3) To implement DAL. (I vote for O/RM or, at least, custom DAL classes.)

4) To implement ASP.NET presentation layer using standard technique - master pages, code-behind, sessions, cache, etc.

I've been through implementing business logic before. In our current classic ASP application I implemented Roles Rights Management System (RRM) using VBScript classes. While VBScript is a bad tool, it still was a real pleasure to program with classes, to make a set of methods representing Roles/Rights business logic and to bind system to a normalized MS Access database. It's surprisingly easy to do, if you understand that OOP business layer comes first. It should not be database-driven design. It should be Domain Driven Design. You just create classes, properties, and methods for every entity your business needs. Everything else comes after it.