I assigned a POC project to someone where I asked to implement both Command Query Responsibility Segregation, Inversion of Control (with Dependency Injection) and Repository pattern. “Someone” gave me a POC solution project but I am not sure whether this is the way it is done. I will brief here about the POC project
- The project is a simple 3-tier application – the Presentation Layer (PL), the Business Logic Layer (BLL) and the Data Access Layer (DAL); each tier being a separate project
- The Presentation Layer is a Web Application, the BLL and DAL are class library projects
- In the Business Layer, there are defined Repository Interfaces. The reference of BLL library is added to DAL project and inside the DAL project there are concrete classes that implement the Repository Interfaces. This is how Inversion of Control is applied
- Since Command-Query-Responsibility-Segregation is done, the repository interfaces in the Business Layer only declare Add/Update and Delete methods. For read, there are “Read” interfaces directly in the DAL and in the DAL there are concrete classes that implement these interfaces.
- The Presentation Layer contains reference to both the BLL library and the DAL library. Calls to Add/Update/Delete are routed through the BLL to the DAL while any read is done directly from the DAL. I believe this conforms to Command-Query-Responsibility-Segregation concept of bypassing the BLL for doing reads.
Here is an illustration of how this is all setup. There are three projects
- NW.Web
- NW.Business
- NW.DataAccess
Below is a snapshot of code in the different layers.
-- NW.Web --
// A class in the Presentation Layer
public class CustomerPage
{
// Business layer Interface from NW.Business namespace
private ICustomerBusiness ICustB;
//DAL Read interface from NW.DataAccess.Read namepsace
private ICustomerRead<Guid> ICustR;
//Constructor for the Customer Page that uses Constructor Injection
public CustomerPage(ICustomerBusiness ICustB, ICustomerRead<Guid> ICustR)
{
this.ICustB = ICustB;
this.ICustR = ICustR;
}
}
-- NW.Business --
//Declaration of business interface in the Business Layer
interface ICustomerBusiness
{
void Persist();
}
// A class in the Business Layer that implements the business interface
public class Customer: ICustomerBusiness
{
//Repository interface object that will be injected by Constructor Injection.
private ICustomerRepository ICustRep;
public Customer(ICustomerRepository ICustRep)
{
this.ICustRep = ICustRep;
}
public void Persist()
{
ICustRep.AddOrUpdate();
}
}
//Declaration of Repository interface in the Business Layer
public interface ICustomerRepository
{
void AddOrUpdate();
void Delete();
}
-- NW.DataAccess--
public class CustomerRepository : ICustomerRepository
{
public void AddOrUpdate()
{
//implementation of Add or Update
}
public void Delete()
{
//implementation of Delete
}
}
//A Read interface in the Data Access Layer
interface ICustomerRead<T>
{
// A read is returned as DTO since in Database this may map to more than 1 table
CustomerDTO GetCustomerDetails(T id);
}
// An implementation of the Read Interface in the Data Access Layer
namespace NW.DataAccess.Read
{
public class CustomerRead<T> : ICustomerRead<T>
{
public CustomerDTO GetCustomerDetails(T id)
{
//implementation here
}
}
}
My gut feeling is that there is something wrong here. It seems CQRS or at least the above implementation does not address some requirements
- The Customer Business object (Customer class) may need to read from Database for its internal purpose (like initializing variables etc). With read directly defined in DAL layer, the only way to do this would be to reference the DAL dll in the BLL. But this would create circular reference and go against the IOC that is done
- What happens when there is some common Read requirement across all business objects ?