Introduction
Let's start with a very basic conception in developing a three tier application. These kinds of applications normally have three layers: Data access layer (DAL), Business Logic Layer (BLL) and the View or Presentation layer. The organization of the layers is like the picture below.
Figure: Old Three tier
In this organization, the BLL is directly dependent on DAL and the View layer is directly dependent on DAL and BLL layers. Here, say for example, a ProductService
class in BLL will directly use the ProductRepository
class in DAL layer like this:
public class ProductService
{
ProductRepository pr;
int getProductCount()
{
pr = new ProductRepository();
return pr.getSqlProductCount();
}
}
This is a problem; because with the direct initialization of ProductRepository
in ProductService
class makes them tightly coupled. At this point, suppose you need to change the repository to OracleProductRepository
, then you need to rewrite the ProductService
class and compile again. Moreover, it is also not possible to test the ProductService
object without the ProductRepository
object.
The same happens to the objects of the View layer which directly uses the objects of BLL and sometimes DAL. This makes View layer tightly coupled to the BLL and DAL layer which thereby restricts the View layer to test without exactly that BLL and DAL layer.
Actually our problem is: all objects instantiation of DAL layer are scattered among all the objects of BLL layer and all the BLL layer objects instantiation are scattered among all the objects of View layer objects. To solve this – somehow we will have to control the object instantiation from one center.
The Remedy
The remedy lies in IoC and DI. So lets get familiar with those.
IoC
Firstly, to get rid from this problem, we will have to use the IoC – Inversion of control. We will have to move the control of instantiation-of-objects to a separate entity – a separate object which we will call Composition Root.
DI
Secondly, notice the Figure: Old Three tier. There, the BLL depends on DAL because objects of BLL need to instantiate the objects of DAL layer. If we can remove this dependency, than we can restrict the objects of BLL to directly access the objects of DAL. But objects of BLL need to access the DAL objects – how will they do that? The answer is by Dependency Injection and Interface.
Notice the picture below.
Figure: New relation between BLL and DAL
Here, the BLL and DAL have their own interfaces and BLL is not depending on DAL, rather DAL is depending on BLL. Later, we will see the advantage of that.
Notice also that there is a separate object CRoot
that will instantiate all the objects in DAL and BLL. Here, we have moved the control of instantiation of all the BLL and DAL objects, thus implemented Inversion of Control(IoC).
CRoot
will also inject the DAL objects in the BLL objects while instantiation. This is called Dependency Injection (DI). Interface will be used to do that.
Have a look at the ProductService
class of BLL and follow the comments:
public class ProductService : IProductService
{
ISqlProductRepository sqlPrdRepo;
public ProductService(ISqlProductRepository _r)
{
sqlPrdRepo = _r;
}
}
Now the DAL object that will be injected will simply implement the Interface:
public class SqlProductRepository : ISqlProductRepository
{
}
Notice in the Figure: New relation between BLL and DAL that DAL depends on BLL – this is because sometimes DAL needs to populate the POCO (plain old CLR objects) objects or domain objects of BLL. DAL will fetch data from the Repository objects or Data layer objects and will pass these data to the BLL POCOs.
A sample POCO is as this and resides in BLL.
public class Product : IProduct
{
public string Name { get; set; }
public int Id { get; set; }
}
And this POCO is populated in the DAL layer by SqlProductRepository
object like this:
public class SqlProductRepository : ISqlProductRepository
{
public IEnumerable getSqlProductList()
{
var _l = new List()
{
new Product(){ Name = "Xion Precessor" , Id = 21},
new Product(){ Name = "Ci7 Precessor" , Id = 20},
new Product(){ Name = "Seleron Precessor" , Id = 13},
new Product(){ Name = "Ci5 Precessor" , Id = 17}
};
return _l;
}
}
Finally, this Product
list is passed to layer upwards (i.e., View layer) by the object ProductService
in BLL like this:
public class ProductService : IProductService
{
ISqlProductRepository sqlPrdRepo;
public ProductService(ISqlProductRepository _r)
{
sqlPrdRepo = _r;
}
public IEnumerable GetSqlProductList()
{
return sqlPrdRepo.getSqlProductList();
}
}
Here, all the interfaces like IProduct
, IProductService
, and ISqlProductRepository
will be resided in Interface
layer. These interface layers are separated so that all the other layers (i.e., View layer) can efficiently use the interfaces rather than the original object.
Finally, comes the CRoot
object where the BLL object – ProductService
is instantiated and the DAL object – SqlProductRepository
is injected in it.
public class Root
{
public IProductService ProuctService { get; set; }
public Root()
{
ISqlProductRepository _repo = new SqlProductRepository();
this.ProuctService = new ProductService(_repo);
}
}
Now is the time to integrate the View layer or Presentation layer. This layer should only depend on the Composition Root (i.e., CRoot
) and also should be perfectly decoupled from the BLL and DAL.
See the final diagram:
Figure: Final View,BLL and DAL
As you see, the View layer depends on CRoot
and from there, it will get all the necessary BLL and DAL objects. View layer will not use these objects directly, rather it will use interfaces of those objects. Thus, it is perfectly decoupled from the DAL and BLL layer.
The view layer could be anything from Console Application to Web or Desktop or Mobile Application and for simplicity, I am showing a console application as a view. This view layer will ask the CRoot
for an instance for IProductService
. Then, it will use this IProductService
to get the list of IProduct
.
Code from the View layer:
static void Main(string[] args)
{
Root _root = new Root();
IProductService _prdsrv = _root.ProuctService;
List _l = (List)_prdsrv.GetSqlProductList();
Console.WriteLine("The processor lists as bellow");
foreach (IProduct pr in _l)
{
Console.WriteLine("The product ID: " +
pr.Id.ToString() + " --- Name: " + pr.Name);
}
Console.Read();
}
There are significant advantages we will get from this design:
- We have centralized our object instantiation to
CRoot
object. So neither can you create DAL object in BLL layer nor BLL objects in View layer. Hence our code is highly maintainable and reusable. - We have injected the DAL objects into BLL objects through constructor injection from
CRoot
object. Hence the DAL layer is less coupled with BLL layer. Some day, if we want to replace the SQL DAL layer with Oracle DAL – we’ll just have to modify the CRoot
object. The new Oracle DAL will have to implement the interfaces. - Our view layer is also loosely coupled with the BLL and DAL because it is using the Interface layer to handle BLL and DAL objects.
- The application is easy to test. For example, we can easily make a dummy DAL layer and use it in application and test.
Possible Upgrade
Up to now, we have successfully created an application where the codes are highly maintainable and reusable, the layers are perfectly decoupled and easy to test individual layer. For further improvement, we can introduce a Container
in the CRoot
layer. The container can be any sort of – from Unity, Castle Windsor, StructureMap to Spring.NET.
Final Words
The design concept is totally my own – so I will appreciate comments and criticism.
References