4

I have inherited ASP.Net Web form code. Here the business layer is just calling the function of data access layer. For example

public string GetCountry(int countryCode)
{
  MyDLSrv obj = new MyDLSrv();
  return obj.GetCountry(countryCode);
}

In short, if you see method signature of function in DAL and BL, it is more or less the same. This is made-up example and is not perfect i.e. checks/validation/try-catch. But the question is what value this BL is adding other than DL not directly accessible in presentation layer? Is it good practice? If not, what would be the correct way?

  • 3
    Does the whole BL look like this, or only a few functions? – Doc Brown Feb 20 '17 at 7:24
  • 2
    Additionally, is the data access layer something that can be replaced? Are you programming to its interface, or its implementation? – user22815 Feb 20 '17 at 7:51
  • @DocBrown: Yes most of the BL is like that. – Milind Thakkar Feb 20 '17 at 12:27
  • @Snowman: The DAL code is more like typical ADO.Net functions used. i.e. Open SQL connection, Define Command, Fill Data Adapter and get DataSet back. Most of the DAL returns either DataSet or DataTable. So, if needs to be re-written, discard and write again :-( – Milind Thakkar Feb 20 '17 at 12:30
4

Is Business layer required when all it does it call data layer function?

If by reading a business layer you mean retrieving objects which are used when writing to a data store (ie. objects which contain validation, etc.) and transforming and outputting these objects to the user then the answer is a simple no.

A few years ago a new approach to building systems has been published by Greg Young, called CQRS (Command Query Responsibility Segregation).

In layman's terms, CQRS (Command - writes, Query - reads) is nothing but an approach to have at least two (or more) objects when interacting with the data store - one for writes and one for reads. When writing to a data store you would have a complex validation that the command that has been issued is valid and not against your system's state. On the other hand, if all commands go through a strict validation then you can trust your system it always contains valid data and as such queries (reads) can simply pull data out of the data store using simple DTOs and output it to the users directly (without the hassle of transforming the data between a business layer and a view).

When combined with event sourcing, CQRS takes the read layer to another level - by querying a completely different data store than the one to which the writes are being made. The writes may be made to a 3NF normalized database and during each write a denormalized (cached) version of an entity is published to a denormalized data store. Queries then query the denormalized data store, which increases performance. Taking this into consideration it's clear queires (reads) cannot go through business layer at all, because the data store used by queries is completely different.

If all you're doing is querying the data, circumventing the business layer is OK.

4

Business Layer is not always required. But, yes if you are developing an application on larger scale then you need to be quite specific interms of layers, patterns, implementations etc. Since, then you can actually distinguish and get a clear view from every perspective whether it can be a Page specific code, UI designining, UI integration, Workflows OR some other layers OR DB integration. This will be quite helpful when you will be in actual development phase.

  • Agreed. If you have an interface that exposes a function to return the string for a particular country ID, you should not need a second interface with that function. You already have an interface that abstracts the specifics of retrieving that data, so there's no good reason not to call the function on that interface from any of your policy level code (whether BLL, UI, or otherwise). DRY also applies to interfaces. – Mr Cochese Feb 20 '17 at 14:54
3

You said in a comment on your question:

The DAL code is more like typical ADO.Net functions used. i.e. Open SQL connection, Define Command, Fill Data Adapter and get DataSet back. Most of the DAL returns either DataSet or DataTable. So, if needs to be re-written, discard and write again

(emphasis, mine)

The first question you should ask is:

What is being done with those DataSet and DataTable objects?

This is your Business Logic Layer!

If your "Business Logic Layer" is just calling "Data Access Code" then you don't have a business logic layer. You have one more layer that does absolutely nothing.

This doesn't mean you don't need a Business Logic Layer. It just means the code using the DataSet and DataTable objects enforces your business logic. Everything you are describing in your original post is just data access logic.


Exactly, if DAL function returns dataset or datatable, BL is returning that. If DAL returns bool or int, BL returns same. In short, if you see method signature it is more or less same.

I think this illustrates a misunderstanding of what a "business layer" is. What you have is not a business layer at all, despite it's name. I see this very frequently with ASP.NET WebForms applications, unfortunately.

  1. Yes, you need a "Business Layer".

  2. What you have in your question is not a "Business Layer," and you don't need it.

  3. The code using the DataSet's and DataTable's (probably the code-behind in your Forms and Controls) is your real business layer mixed in with controller behavior as well as presentation behavior in a giant mess of procedural code that isn't utilizing the object oriented language it's written in.

    It's like slicing cheese with a chainsaw. Or making confetti with a wood chipper.

Now, there is a design pattern that makes this extra layer between your data access and the rest of the application worthwhile, but you need to abandon the meaningless and non type safe DataSet's in favor of real classes and objects: The Repository Pattern.

I'll use a Blog as an example, because a) it's a simple concept and b) I don't know what entities you are actually using.

First things first. Forget DataSets and DataTables. Let's create an entity class called "Blog" and start using the features of our object oriented language:

public class Blog
{
    /// <summary>
    /// Initializes a new Blog object that has not been saved yet
    /// </summary>
    /// <param name="name"></param>
    public Blog(string name)
    {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentNullException("name");

        Name = name;
    }

    /// <summary>
    /// Initializes a new Blog object that has been previously saved
    /// </summary>
    /// <param name="id"></param>
    /// <param name="name"></param>
    public Blog(int id, string name)
        : this(name)
    {
        Id = id;
    }

    public int Id { get; private set; }
    public string Name { get; private set; }
}

Not much business logic is going on here, except that a Blog requires two things: an Id and a name.

What you are calling your "BL" or "Business Layer" should actually be the Blog class. What you have now could be transformed into a Repository. To facilitate testing, it's usually a good idea to declare an interface for your repository:

public interface IBlogRepository
{
    Blog Find(int id);
}

A repository, if you aren't using an ORM like Entity Framework or NHibernate, will need two other pieces: a Gateway and a Factory. This corresponds to two more interfaces, which can make it easier to swap out the exact persistence mechanism should it change (SQL Server to Oracle, or to a Web service or flat file storage). Plus, you can implement caching at different layers of the application later on.

The gateway:

/// <summary>
/// Represents a gateway object that holds the query logic for blogs
/// </summary>
public interface IBlogGateway
{
    DataRow Find(int id);
}

Notice that it returns a DataRow here. Look again at the IBlogRepository interface and notice that it does not return a generic data object, and instead returns a strongly typed Blog object. The gateway is responsible for querying the database. This is analogous to your current DAL.

Next up is the "factory" which is responsible for taking the DataRow objects and churning out real Blog objects. This really is also a Data Mapper, if you want another buzz word (which could be yet another class if you want).

public interface IBlogFactory
{
    Blog Create(DataRow row);
}

Now that we've got our interfaces in place, let's take a look at some concrete classes that implement these interfaces.

We'll start with the "repository" since this is the intermediate layer you are calling the "BL" and are questioning it's need for existence. In this design pattern it does have a purpose, and that's to coordinate between the Gateway and the Factory:

public class BlogRepository : IBlogRepository
{
    public BlogRepository(IBlogGateway gateway = null, IBlogFactory factory = null)
    {
        this.gateway = gateway ?? new SqlServerBlogGateway();
        this.factory = factory ?? new SqlServerBlogFactory();
    }

    private IBlogGateway gateway;
    private IBlogFactory factory;

    public Blog Find(int id)
    {
        DataRow row = gateway.Find(id);

        if (row == null)
            return null;

        return factory.Create(row);
    }
}

Three things to note:

  1. The constructor takes IBlogGateway and IBlogFactory objects, which default to null

  2. If null, we fall back to a Sql Server implementation (more on those in a moment)

  3. The Find method takes the Id, and passes it to the Gateway. The Gateway returns a DataRow, which is then passed to the Factory which returns a Blog.

Next we will explore the Gateway, since your current DAL seems to fit this piece:

public class SqlServerBlogGateway : IBlogGateway
{
    private const string SQL_FIND_BY_ID = "SELECT * FROM [dbo.Blog] WHERE id = @Id";

    private string ConnectionString
    {
        get
        {
            return System.Configuration.ConfigurationManager.ConnectionStrings["MyApplication"].ConnectionString;
        }
    }

    public DataRow Find(int id)
    {
        using (var connection = new SqlConnection(ConnectionString))
        using (var command = new SqlCommand(SQL_FIND_BY_ID, connection))
        {
            var adaptor = new SqlDataAdaptor(command);
            var data = new DataSet();

            command.Parameters.Add(new SqlParameter("Id", id));
            connection.Open();
            adaptor.Fill(data);

            if (data.Tables.Count == 0 || Data.Tables[0].Rows.Count == 0)
                return null;

            return data.Tables[0].Rows[0];
        }
    }
}

In the SqlServerBlogGateway class the Find method directly interacts with the SQL Server client libraries to execute a query against the database.

The last piece of the puzzle is the factory, which takes a generic data object and returns a strongly typed Blog object:

public class SqlServerBlogFactory : IBlogFactory
{
    public Blog Create(DataRow row)
    {
        int id = Convert.ToInt32(row["ID"]);
        string name = row["NAME"].ToString();

        return new Blog(id, name);
    }
}

It knows which constructor of the Blog class to invoke, as well as what the names of the columns are in the database and how to map those to the Blog object.

And of course some hypothetical code using the Repository:

IBlogRepository blogs = new BlogRepository(); // Defaults to SQL Server

// Now we have a real object to use, instead of a meaningless,
// generic data structure with no compile time checks:
Blog blog = blogs.Find(123);

We can even introduce a layer of caching:

public CachedBlogGateway : IBlogGateway
{
    public CachedBlogGateway(IBlogGateway innerGateway)
    {
        this.innerGateway = innerGateway;
        this.cache = new Dictionary<int, DataRow>();
    }

    private IBlogGateway innerGateway;
    private Dictionary<int, DataRow> cache;

    public DataRow Find(int id)
    {
        if (cache.ContainsKey(id))
            return cache[id];

        DataRow row = innerGateway.Find(id);

        if (row != null)
            cache[id] = row;

        return row;
    }
}

Now to use this in-memory cache:

IBlogRepository blogs = new BlogRepository(
    new CachedBlogGateway(new SqlServerBlogGateway()));

// Hit the database for this one:
Blog blog1 = blogs.Find(123);

// Hit the cache for this one:
Blog blog2 = blogs.Find(123);

While this was a long answer, I hope it addresses the main issue here, which is if your "BL" is needed, and how this intermediate layer could be used to do something more than just be another layer of the application.

  • Exactly, if DAL function returns dataset or datatable, BL is returning that. If DAL returns bool or int, BL returns same. In short, if you see method signature it is more or less same. – Milind Thakkar Feb 22 '17 at 4:46
  • I doesn't do absolutely nothing, it acts as a layer of abstraction. – Chris Wohlert Feb 22 '17 at 13:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.