C# – Relaxed architecture DAL or anemic BLL

cdesign-patternslayers

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:

  1. Service Layer
  2. Business Logic Layer
  3. Data Access Layer

The Problem

I'm uncomfortable with having to choose between a relaxed architecture DAL or anemic BLL classes and I'm wondering whether it's because I'm approaching this the wrong way. When I refer to "relaxed architecture DAL", I mean a DAL that can be called by layers other than the BLL directly above it, i.e. the service layer.

My reason for not wanting a relaxed architecture 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 relaxed architecture DAL or the anemic BLL? Are my concerns regarding one (or the other) unfounded? Or is there something else altogether that I'm missing?

Best Answer

Data Transfer Objects (DTO's) have one purpose, and one purpose only: to retrieve data from a database and work with it in an object-oriented form.

There's no such thing as an "anemic BLL object." It's either a first-class BLL object, or it's a DTO. It's seldom both.

There are reasons why you have things like ViewModel objects. A View Model object contains data from your Model, but it's tailored to a View. It's not a DTO, at least not in the sense that you're transferring data back and forth from a database. It is an object in its own right, having its own specific purpose.

So where does that leave your Business Logic Layer?

The purpose of a Business Logic Layer is to convert business domain operations (such as Transfer Money or Build Widget) into CRUD operations. It doesn't actually perform the CRUD operations, though; that's what your DAL and DTO's do.

It seems like a lot of objects, doesn't it? But your DTO's are typically going to be generated by your Object-Relational Mapper, unless you plan on creating the database from your object domain (a perfectly valid technique).

Make use of partial classes.

So how do you marry your business logic code to your business objects? One way to do it is with partial classes. C# allows you to define your own class alongside the code-generated DTO, which will add your custom validation or whatever business logic you choose to add to the business domain object. Because your hand-written logic is in a separate class (having the same name), your code will be protected against being overwritten by the code-generated class, should you choose to re-generate your DTO's. Your DAL classes will no longer be anemic, because you will have added your own intelligence to them via partial classes.

Related Topic