Search code examples
c#asp.net-mvcn-tier-architecture

How is BLL (CRUD and Business Object) typically structured?


This is in relation to this question:

What to put in Business Logic Layer?

With the answers in this question (haven't yet selected an answer as there might be others willing to comment to make it clearer for me), I've came to the conclusion that BLL would include CRUD and would access DAL as needed.

My main problem now is what does my BLL look like? Say for example, an Order object. For CRUD, I see some implementations that has OrderService which is part of BLL like this:

public class OrderService
{
    public int CreateOrder(Order order)
    {
        ...
    }

    public int UpdateOrder(Order order)
    {
        ...
    }

    //... other code for CRUD
}

The thing with this is aside from the Business objects, I have business object-related services in BLL?

While on some other they do something like this:

public class Order
{
    public int ID { get; set; }
    public decimal Amount { get; set; }
    //... etc.

    public int Create()
    {
        ...
    }

    public int Update()
    {
        ...
    }
}

But this seems somehow wrong (combining the CRUD operations and the properties).

How is BLL (CRUD and Business Object) typically structured?

Also, since data would typically come from UI input then populated to the Business object, how would I validate data? For example I have property Total for order and List, Total should equal to total amount of OrderItem. When doing say CreateOrder, how would I invoke validation? I always thought that validation should be done inside the actual property setters. How would I invoke this during CRUD? Should I implement an Validate method in Business Object too?

Any input about this would be very welcome.


Solution

  • Based on my experience I'd rather use the first one. If there is a more sophisticated business logic then definitely. Usually I have the following layers in a complex application:

    • View. Razor pages
    • Model. Models are Data Transfer Objects that represent something to be displayed, not something from the database.
    • Controller. Controllers can be light-weight, only connecting the presentation and the business logic.
    • Business Logic. I prefer putting the BL into a separate library (BLL). The business logic communicates with the MVC controllers (through DTOs) and the data layer.
    • Data Layer. Manipulated through EF.

    DTOs can be created from several DB objects; they can also be a partial representation if you want to hide some details. Read more in this question. DTOs are also a good way to force yourself to prevent unintended updates -- or see here.

    BLL is not required in smaller applications where the controllers can do everything. But in larger applications where you have various services and aspects you should use it. Some would say that BLL is part of the model -- well, somewhat yes but it is much more than the stuff filling the views.

    And let me highlight again: don't use a cannon to shoot sparrows... Simple tasks require simple solutions.

    ADD-ON

    You can map the DB entities to DTO entities by selector expressions. An example is here, I've put another one below supposing there is a Kitten table in the database.

    Class KittenDto {
        public static Expression<Func<Db.Kitten, KittenDto>> = (kitten) => return new KittenDto() {
            Id = Id,
            Name = Name,
            CustomDataNotInDb = 42
        };
        public int Id;
        public string Name;
        public int CustomDataNotInDb;
    }
    

    Then you can use:

    var kittenDto = context.Kittens
        .Single(k => k.Id == givenId)
        .Select(KittenDto.Selector);
    

    Watch out, if the Selector is not an Expression<> then it is executed locally. If it is an Expression then it is transformed into a query and the DB does the rest of the work. The result from the DB will be a KittenDto object (or more precisely: it will have only the requested properties, nothing more).

    Also be aware that writing complex expressions can be difficult -- or it was when I've used it last time around .NET 4.5. For example function calls cannot be executed within DB queries, only some very common ones (like some string operations).