Particularly for the sake of unit testing, I'm trying to implement an application with a layered architecture. I'm coding in C# and using ASP.NET Web API for the service layer. I'm aiming at a 3-layer architecture:
- Service Layer
- Business Logic Layer
- Data Access Layer
The Problem
I'm uncomfortable with having to choose between a leaky DAL or anemic BLL classes and I'm wondering whether it's because I'm approaching this the wrong way.
My reason for not wanting a leaky DAL: makes it easy for business logic to be mistakenly bypassed.
My reason for not wanting anemic BLL classes: don't want a plethora of classes that do nothing other than pass calls between the service layer and DAL.
Example
Here's some sample code. First, a service layer method supporting the registration of a student in a class:
public class ClassRegistrationController : ApiController
{
private readonly BusinessLogic.ClassRegistration _classRegistrationLogic;
public ClassRegistrationController(
BusinessLogic.ClassRegistration classRegistrationLogic)
{
_classRegistrationLogic = classRegistrationLogic;
}
[Route("/api/class/{classId}/register")]
[HttpPost]
public IHttpActionResult RegisterForClass(
int classId,
[FromBody]ClassRegistrationRequest registrationRequest)
{
try
{
_classRegistrationLogic.RegisterStudentForClass(
classId,
registrationRequest.StudentId);
}
catch (ClassFullException)
{
return BadRequest("Cannot register for class, it is full");
}
return Ok();
}
}
And the business logic class supporting it:
public class ClassRegistration
{
private DataLayer.IClassDataLayer _classDataLayer;
private DataLayer.IClassRegistrationDataLayer _classRegistrationDataLayer;
public ClassRegistration(
DataLayer.IClassDataLayer classDataLayer,
DataLayer.IClassRegistrationDataLayer classRegistrationDataLayer)
{
_classDataLayer = classDataLayer;
_classRegistrationDataLayer = classRegistrationDataLayer;
}
public void RegisterStudentForClass(int classId, int studentId)
{
var classDetails = _classDataLayer.GetClassDetails(classId);
if (classDetails.ClassIsFull)
{
throw new ClassFullException();
}
_classRegistrationDataLayer.AddStudentToClass(classId, studentId);
}
}
The data layer interfaces (implementation isn't relevant to the question):
public interface IClassDataLayer
{
Models.ClassDetails GetClassDetails(int classId);
}
public interface IClassRegistrationDataLayer
{
int AddStudentToClass(int classId, int studentId);
}
Now, I think this all looks reasonable, and each class has a single (and not insignificant) responsibility. But, now let's say I want to add a service layer method that exposes details of a class, like:
[Route("/api/class/{classId}")]
[HttpGet]
public IHttpActionResult GetClassDetails(int classId)
{
var classDetails = _classDetailsGetter.GetClassDetails(classId);
if (classDetails == null)
{
return NotFound();
}
return Ok(classDetails);
}
The question is, will _classDetailsGetter
be an instance of a class from the DAL or from the BLL?
If it's from the DAL, then I'm effectively saying it's "OK" for the service layer to go directly to the DAL, which opens up the risk of, say, somebody doing maintenance on the RegisterForClass service layer method deciding to go directly to IClassDataLayer instead of BusinessLogic.ClassRegistration.
But if it's from the BLL, then that BLL class would only be responsible for 1-to-1 call mapping, which also rubs me the wrong way. Like:
public class ClassDetailsLogic
{
private readonly DataLayer.IClassDataLayer _classDataLayer;
public ClassDetailsLogic(DataLayer.IClassDataLayer classDataLayer)
{
_classDataLayer = classDataLayer;
}
public Models.ClassDetails GetClassDetails(int classId)
{
return _classDataLayer.GetClassDetails(classId);
}
}
Summary
Is there reason to favour the leaky DAL or the anemic BLL? Are my concerns regarding one (or the other) unfounded? Or is there something else altogether that I'm missing?