Architecture of Business Layer working with Entity Framework (Core and v6) – revisited

Last Updated: July 31, 2020 | Created: August 22, 2016

I wrote an article a while ago called Architecture of Business Layer working with Entity Framework, which has been popular. In that I used Eric Evans’ Domain- Driven Design (DDD) approach to building business logic with Entity Framework.

I am now playing with Entity Framework (EF) Core and some sample code made me think – could I better isolate the EF part of my Business Logic? The answer is yes, and this article describes the new and improved approach, with a lot more examples.

UPDATE 2018: BIG CHANGE

I have swapped to DDD-styled entity classes, which changes how my business logic is written. This is a big change to the original article. I decided to change this article rather than write a new one, as my new design replaces the previous design. I do describe the DDD-styled entity classes in this article, but see the article “Creating Domain-Driven Design entity classes with Entity Framework Core” , for a more detailed coverage of that topic.

I have also changed code to run that runs the business logic over to my open-source library, call GenericBizRunner. That makes takes away some of the hassle of running business logic.

People who have bought my book, “Entity Framework Core in Action” should note that I have updated chapter 10 to cover this approach to business logic. The update won’t be out until the book has finished its production cycle.

NOTE: You can apply the ideas in this article in EF6.x, but you can’t totally stop access to collection navigational properties in the same we you can in EF Core. This doesn’t stop you using this approach, it just means a developer can bypass the DDD style update if they really want to.

Domain-Driven Design and business logic

I am a big fan of Domain-Driven Design (DDD) and Eric Evans’ seminal book on DDD, Domain-Driven Design. Eric Evans’ book talks about how the business problem we are trying to solve, called the “Domain Model”, should be “the heart of the Software” (see Eric Evans book, page 4). Eric goes on to say “When the domain is complex, this is a difficult task, calling for the concentrated effort of talented ad skilled people”. Therefore, I try to make sure that the Business Logics data structures are defined by, and solely focused on, the business problem. How that data is stored and how that data is viewed are secondary issues.

What is Business Logic?

If you are new to idea of Business Logic, then I suggest you read the section near the top called ‘What is the Business Layer’ in my original article as it gives a good description. However, if you are in a hurry here is the short version.

Business Logic is what we call pieces of code that carry out operations that are specific to the job that the application is carrying out. For instance, on an e-commerce site like Amazon you would have Business Logic that handles a customer’s purchase: checking on availability and delivery, taking payments, sending an email confirmation etc. Business logic is definitely a step up on complexity over CRUD (Create, Read, Update and Delete) operations.

While Business Logic can be spread throughout an application and the database, it is accepted best practice to try and isolate the Business Logic. In my new, 2018 design I have some business logic in the entity classes, and some in a separate project/assembly, which am calling the Business Layer.

Aside: While having all the business rules in the Business Layer and entity classes is something we should always strive for I find that in practice some issues go outside these places for good reasons. For instance, validation of data often flows up to the Presentation Layer so that the user gets early feedback. It may also flow down to the database, which checks data written to the database, to ensure database integrity.

Other business logic appears in the Presentation Layer, like not allowing users to buy unless they provide a credit card, but the status should be controlled by the Business Layer. Part of the skill is to decide whether what you are doing is justifiable or will come back and bite you later! I often get that wrong, but hopefully I learn from my mistakes.

The (improved) architecture of my Business Layer

Let me start with a diagram of the new, improved structure of the Business Logic within an application – in this case an ASP.NET web application but the same approach would work in many other application types.

Next I will explain each of the main layers, with example code, by building some business logic that will process an order for some books that my example application “sells”.

NOTE: You can access the code used in this article, including a runnable example ASP.NET Core application which works, by cloning the GitHub repository GenericBizRunner. You need .NET Core installed, but example application uses an in-memory SQLite database, so you don’t need a database server installed (thanks to Henrik Blick for that suggestion).

1. The DataLayer (updated 2018)

The Data Layer is where the entity classes are defined, along with the EF setup and DbContext. In the new 2018 design the entity classes are written in a DDD-styled approach. For an in-depth look at my DDD pattern for entity classes see my article, Creating Domain-Driven Design entity classes with Entity Framework Core.

My DDD-styled entity classes contain business logic for creating and updating the entity class and any of its aggregates (aggregates is a DDD term that refer to entities that don’t make any sense outside a root entity – see the LineItem entity class in the example below).

My example is taking an order for some books. The order is held in a Order entity class, and the order has a one-to-many relationship to a LineItem entity class, which holds each item in the order. The code below shows you the DDD-styled Order class – the focus is on the Order’s static factory to create a properly constructed Order instance.

public class Order
{
    private HashSet<LineItem> _lineItems;

    public int OrderId { get; private set; }
    public DateTime DateOrderedUtc { get; private set; }
    public DateTime ExpectedDeliveryDate { get; private set; }
    public bool HasBeenDelivered { get; private set; }
    public string CustomerName { get; private set; }

    // relationships

    public IEnumerable<LineItem> LineItems => _lineItems?.ToList();

    public string OrderNumber => $"SO{OrderId:D6}";

    private Order() { }

    public static IStatusGeneric<Order> CreateOrderFactory(
        string customerName, DateTime expectedDeliveryDate,
        IEnumerable<OrderBooksDto> bookOrders)
    {
        var status = new StatusGenericHandler<Order>();
        var order = new Order
        {
            CustomerName = customerName,
            ExpectedDeliveryDate = expectedDeliveryDate,
            DateOrderedUtc = DateTime.UtcNow,
            HasBeenDelivered = expectedDeliveryDate < DateTime.Today
        };

        byte lineNum = 1;
        order._lineItems = new HashSet<LineItem>(bookOrders
            .Select(x => new LineItem(x.numBooks, x.ChosenBook, lineNum++)));
        if (!order._lineItems.Any())
            status.AddError("No items in your basket.");

        status.Result = order;
        return status;
    }

    //... other access methods left out
}

Things to note from this code are:

  • Lines 3 to 7. Note that all the properties have private setters. That means that the developer has to go through the public constructor or any access methods to change anything in the entity (see this link about the DDD-sytled entity and access methods).
  • Line 9,10: The one-to-many list of LineItems is implemented as a EF Core backing field. This means the navigational collection is held in a private field, and the developer can only read it as an IEnumerbale<LineItem> property, which means they cannot add, remove or clear the collection. This means only the Order entity class can alter this collection.
  • Line 17: EF Core needs a parameterless constructor so that it can create instances of the entity class when it reads in data. But I can make that a private constuctor, which means a developer outside this class cannot create a default (i.e. empty, and therefore invalid) instance.
  • Lines 19 to 21: Because the creation of the Order might include a business rule error, which is fixable by the user, then I use a static factory method for creating the Order. This means I can return a result of type of IStatusGeneric<Order>, which a status and, if no errors, the created Order instance. If there were no possibilities of fixeable errors I would have used a public constructor with parameters.
  • Lines 32 to 34: This builds a list of LineItem entities. The LineItem entity class has an internal access constructor which creates a valid LineItem. Because the LineItem is a aggregate of the Order class, then only the Order should create/change the LineItems.

2. The BizLogic Layer

I have written numerous applications, all of which has some quite complex Business Logic. Over the years I have tried different ways of organising these applications and I have come up with one key philosophy – that the “The Business Logic is King“, i.e. its design and needs drives everything else.

My rules for the Business Logic are:

a. The Business Logic data classes should not be affected by other higher layers

Business Logic can be hard to write. Therefore, I want to stay focused on business issue and not worry about how other layers above might need to reformat/adapt it. So, all data in or out of the Business Logic is either defined inside the BizLogic layer, or it is a Data Layer class. It is Service Layer’s job to act as an Adapter, i.e. Service Layer converts any data to/from what the Business Logic needs.

In new/modern databases the entity classes should be a very good fit to the Business Logic needs, with a few extra properties needed to set up the database relationships, e.g. Foreign Keys. Therefore, the Business Logic can use the EF entity classes directly without any adaption.

However, one of my reader, , pointed out that for for old/legacy databases the fit between what the database and what the Business Logic may not be a good match.

b. The Business Logic works on a set of in-memory data

I don’t want the Business Logic worrying about how to load and save data in the database. It is much simpler to design the Business Logic code to work on simple in-memory set of data. The changes described in this article improve this significantly over the previous article.

In the previous approach I used to have a specific part of the Business Logic, normally at the start, which loaded all of the data. However, the new approach has what I call a DbAccess class which handles all the reading and writing (see later for why this is better). The DbAccess is class is a Facade pattern.

Example Code

Here is an example of Business Logic which is creating the Order. It is fairly simple, beacause the Order.CreateOrderFactory does quite a bit of it, with the PlaceOrderAction and the PlaceOrderDbAccess (see later) finding the Book entities.

public interface IPlaceOrderAction :
    IGenericActionWriteDb<PlaceOrderInDto, Order> { }

public class PlaceOrderAction : BizActionStatus, IPlaceOrderAction
{
    private readonly IPlaceOrderDbAccess _dbAccess;

    public PlaceOrderAction(IPlaceOrderDbAccess dbAccess)
    {
        _dbAccess = dbAccess;
    }

    public Order BizAction(PlaceOrderInDto dto) 
    {
        if (!dto.AcceptTAndCs)                    
        {                                         
            AddError("You must accept the T&Cs to place an order.");   
            return null;                          
        }                                         

        var bookOrders = 
            dto.CheckoutLineItems.Select(  
                x => _dbAccess.BuildBooksDto(x.BookId, x.NumBooks)); 
        var orderStatus = Order.CreateOrderFactory(
            dto.UserId, DateTime.Today.AddDays(5),
            bookOrders);
        CombineErrors(orderStatus);

        if (!HasErrors)
            _dbAccess.Add(orderStatus.Result);

        return HasErrors ? null : orderStatus.Result;
    }
}

A few things to point out about the above.

  • Line 1 and 2: The IPlaceOrderAction interface uses a GeneriBizRunner interface that tells the BizRunner that it has an input (PlaceOrderInDto), an output (Order) and that SaveChanges should be called if the method finishes without errors.
  • Line 4: The PlaceOrderAction class inherits the BizActionStatus class, which is provided by the GenericBizRunner. This provides error handling, with methods such as AddError and a list of Errors. This is communcated back to the caller via the GeneriBizRunner service instance.
  • Lines 6 to 11: The PlaceOrderAction class uses a class that implements the IPlaceOrderDbAccess interface. This is provided via dependency injection.
  • Lines 23 and 30: The IPlaceOrderDbAccess class contains a methods that the bsuiness logic can use to a) find the books the customer wants and b) adds the new order to the database.

This design splits the load between this class in the Business Layer and the Order entity class. In my design I deem that an entity class can work on itself (called the root entity) and any of its aggregates, i.e. navigational links that it the root entity handles. This means that the Order entity class doesn’t handle any Book accesses, which is why that is done in this PlaceOrderAction class.

2. The BizDbAccess Layer (new 2016)

The BizDbAccess layer contains a corresponding class for each BizLogic class that accesses the database. It is a very thin Facade over the EF calls. I should stress, I am not trying to hide the EF calls, or make a repository. I am just trying to Isolate the calls. Let me explain.

The problem with my previous approach, where the Business Logic called EF directly, wasn’t that it didn’t work, but that it was hard to remember where all the EF calls were. When it came to refactoring, especially when doing performance improvements, then I had to hunt around to check I had got every EF call. By simply moving all the EF calls into another class I have all of the EF commands in one place.

The main approach I will describe assumes that the database is of a new/modern design and there is a good fit between the data held in the database and the Business Logic requirements. The code below is a PlaceOrderDbAccess class, which goes with the PlaceOrderAction Business Logic described above:

public class PlaceOrderDbAccess : IPlaceOrderDbAccess
{
    private readonly EfCoreContext _context;

    public PlaceOrderDbAccess(EfCoreContext context)
    {
        _context = context;
    }

    public OrderBooksDto BuildBooksDto(int bookId, byte numBooks)
    {
        return new OrderBooksDto(bookId, 
           _context.Find<Book>(bookId), numBooks);
    }

    public void Add(Order newOrder)                 
    {                                               
        _context.Orders.Add(newOrder);              
    }                                               
}

Please note that I use a method called Add, to add the order to the EF Orders DbSet. As I said, I am not trying to hide what is going on, but just isolate the commands, so using the same terminology is helpful.

solution-view-bizlayer-bizdbaccessNOTE: To help with finding the relevant DbAccess I call the Business Logic class <name>Action and the DbAccess calls <name>DbAccess. They also have the same top-level directory name. See the attached picture of the solution directories on the right.

NOTE: When it comes to unit testing business logic it can be useful to be able to mock the BizDbAccess methods, especially for complex business logic where you want to force errors. For more simple business logic I find EF Core’s in-memory databases make unit testing quite easy. See my article “Using in-memory databases for unit testing EF Core applications” for more on that.

Handling old/legacy databases

One of my readers, , has been using a similar approach to the one describe in this article for some time. He pointed out in one of his comments that he finds this approach useful when the database tables are not a good fit for the Business Logic.

At that point, in addition to isolating the EF commands, the BizDbAccess class can carry out any adapting/ reformatting of the data between the database and the Business Logic.

For example, I have come across database which does not mark its relationship keys and Foreign Keys. This stops EF from doing relational fixup, i.e. linking the various entities loaded. In that case you would add code to the DbAccess class to link the loaded entities so that the Business Logic can work on a linked, in-memory set of classes.

The Service Layer

As I said earlier in my applications the Service Layer is very important layer that links everything together. The Service Layer provides a Command pattern and the Adapter pattern between the presentation layer and all the layers below.

Note: You can read more about my thoughts on the business layer in the Service Layer part of my “Six ways to build better Entity Framework (Core and EF6) applications” article.

In this case I have abstracted all the code, other than any DTOs (Data Tranfer Objects – simple classes to pass data around), into my library GenericBizRunner. This provides both the contains the Command pattern and the Adapter patterns.

  • Adapter pattern: The GenericBizRunner can handle UI-centric DTOs, with things like dropdownlists etc, and then map just the data that the business logic needs to do its job. That insulates the business logic from any need to think about, or deal with, UI data.
  • Command pattern: It is the job the the GenericBizRunner to run the business logic and call SaveChanges (with data validation if required) if the business logic was successful. It also passes back the status of the whole process (biz logic status and any validation errors from SaveChangesWithValidation) to the front-end system.

Note: The discarding of data by not calling ‘SaveChanges’ only works in situation where each call has its own DbContext. This is the case in a web application as each HTTP request gets a new DbContext. However, in Windows Applications etc. where the DbContext can be kept alive for longer you need to be careful about the lifetime/disposal of the DbContext.

The GenericBizRunner saves you from writing the code to run and check the business logic.  You can find out more about the library in my article “A library to run your business logic when using Entity Framework Core“. In the next section you will see how I call the business logic via the GenericBizRunner.

Presentation layer/API

At the top level you want to run your business logic. That could be in a human-focused screen or an API talking to another system – it doesn’t really matter. The point is your business logic should be isolated from the front-end. But at the same time the front-end may need features/requirements to make the application to work in its environment, like showing the user what is in their e-commerce basket.

In my example case I am using an ASP.NET Core application for my example, showing HTML pages via razor. I have used a cookie to hold the customers basket, and when the order is successfully placed I need to clear that cookie. Also, I want to show a confirmation page showing that the order was successfully placed, and when it might arrive.

In this case I have handled the cookie side of things inside the ASP.NET Core action method. I try to keep the ASP.NET Core action methods to have a standard format, but in this case the cookie part meant I needed some more code. NOTE: If I had put the basket in the database then the PlaceOrderAction could have cleared the basket as part of the business logic.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult PlaceOrder(PlaceOrderInDto dto, 
    [FromServices]IActionService<IPlaceOrderAction> service)
{    
    if (!ModelState.IsValid)
    {
        return View("Index", FormCheckoutDtoFromCookie(HttpContext));
    }

    var order = service.RunBizAction<Order>(dto);

    if (!service.Status.HasErrors)
    {
        ClearCheckoutCookie(HttpContext);
        return RedirectToAction("ConfirmOrder", "Orders", 
            new { order.OrderId, Message = "Your order is confirmed" });
    }

    //Otherwise errors, so I need to redisplay the basket from the cookie
    var checkoutDto = FormCheckoutDtoFromCookie(HttpContext);      
    service.Status.CopyErrorsToModelState(ModelState, checkoutDto);
    return View("Index", checkoutDto);
}

The parts of the code I want to point out are:

  • Line 4. I am a great fan of dependency injection (DI) and ASP.NET Core provides a way to inject something into the parameters of an action method. The IActionService<IPlaceOrderAction> returns an instance of the GenericBizRunner, which in turn has an instance of the PlaceOrderAction class  (again via DI). This makes accessing the BizRunner with its business logic very easy (and it works in razor pages too).
  • Lines 11 to 18: The BizRunner instance runs the business logic. In this case the business takes an input (PlaceOrderInDto) and returns a result (Order). On success my code has to clear the cookie and redirects to the ConfirmOrder action to show the successful order details.
  • Lines 21 to 23: When there are errors we want to redislay the customer’s basket, and show what was wrong. The CopyErrorsToModelState extension method is one I have written that will converts the GenericBizRunner’s IStatusGeneric errors into ModelState errors to show to the customer.

Quick aside – handling errors needs thought.

This article is already quite long, so I won’t dwell on this subject, but providing good error feedback to the user is not trivial. Many libraries use Exceptions to report errors – some of these errors are user friendly, like Validation Errors. However, many Exceptions, such as EF’s DbUpdateException, are very unfriendly and sometimes produce messages that relate to the inner working for the application, and hence should not be shown for security reasons.

The GenericBizRunner library have implemented a status feedback mechanisum, and I am in the process of extending it to other uses (you already saw the IStatusGeneric<T> return on the Order’s static CreateOrderFactory, which includes a return value too). The main point on on errors that the user/system can correct is to return all the errors in one go, especially in human-facing system. That way the user won’t get frustrated by having to fixed each error before another error is shown.

Conclusion

I recently reviewed an e-commerce application I developed which used the older approach to writing Business Logic, i.e. EF called in the Business Logic. The Business Logic was quite complex, especially around pricing and delivery. My conclusion was that the Business Logic work fine, but it was sometimes difficult to find and understand the database accesses when refactoring and performance tuning the Business Logic.

I tried out the new approach described in this article by refactoring some of the Business Logic in this e-commerce application over to the new approach. Overall it did make a difference by bring all the EF access code into one clear group per Business Logic case.

In additions I have been working with EF Core for over a year, and I have (finally) found a pattern for DDD-styled entity classes. I think this improved the business logic again, with even better separation of concerns (and for CRUD access too – see my article on DDD-styled entities).

The only down side to this new approach is that does need one more class than the previous approach. However, that class does provide a much better separation of concerns. Therefore, I think this new approach is a positive improvement to my previous approach to building Business Logic and is worth the effort extra effort of another class.

I hope this article gives you some ideas on how to better design and build complex Business Logic in your applications.

0 0 votes
Article Rating
Subscribe
Notify of
guest
38 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Dmitry
Dmitry
1 year ago

Hello Jon. I read your article and see in your first code example such construction order._lineItems =new HashSet<LineItem>… (line #33). `
Is it not a mistake cause _lineItems is a private field of the Order class? Perhaps you meant order.LineItems?

Last edited 1 year ago by Dmitry
Mohamed Aarab
Mohamed Aarab
2 years ago

I have an API and an Authentication server with Identity Server 4 installed, but I’m not entirely sure where to place some of these files. Because my login takes place on the auth server and deals out tokens and then there are the controller routes on the API that needs protecting, both projects need access to most of the classes and enums. For example Permissions.cs is needed in both the API and the Auth Server. How would you go on about this? Is it okay to have duplicate Permissions.cs?

Last edited 2 years ago by Mohamed Aarab
Mohamed Aarab
Mohamed Aarab
2 years ago
Reply to  Mohamed Aarab

Sorry, this comment was meant for another blog post, whoopsie.

Markand Bhatt
Markand Bhatt
3 years ago

The “Add” method of PlaceOrderDbAccess just attaches new order to context. Where is the actual save happening. I mean _context.SaveChanges(); Which layer?

Jon P Smith
3 years ago
Reply to  Markand Bhatt

Hi Markand,

I call the SaveChanges method in my service layer – see the Service Layer section in this article (link -> https://www.thereformedprogrammer.net/architecture-of-business-layer-working-with-entity-framework-core-and-v6-revisited/#the-service-layer ). There are a number of reasons for not calling SaveChanges outside the business logic:
1. It keeps the business logic ignorant of the database save.

2. The ServiceLayer is in a better position to handle any errors on saving the data, as it knows about the presentation layer (which the business logic doesn’t) – i.e. WebAPI might need a different error response than a human-focused UI.

3. I can call a series of business logic inside a transaction (see https://www.thereformedprogrammer.net/architecture-of-business-layer-calling-multiple-business-methods-in-one-http-request/ ) and the ServiceLayer manages the transaction rollback/commit.

Petr B.
Petr B.
3 years ago

Why do you have business logic/entities in datalayer? I think if there is something what implements business rules it is contained in business layer. Problem of your architecture is that all layers above datalayer are depend on datalayer is not it? I would like to ask you. If you use EF directly in business layer, how you can test your query? How did you divide business layer and datalayer to mock datalayer if you have EF (which is datalayer) inside business layer?

Jon P Smith
3 years ago
Reply to  Petr B.

Hi Petr,

I originally had all of the business logic in the business layer, and that is quite valid if you want to go that way. However I found that building domain-driven design (DDD) styled classes allowed me to move some of the more data/structural issues into the EF entity classes in the data layer. The plus is that a DDD-styled entity class “locks down” the class such that the ONLY way to create/update it is via its access methods. That means no part of the code can incorrectly create/update something in the database – in the example in this article its a customer order with it line items.

So the next question is what business logic should go in the data layer, and what business logic should go in the business layer? This took me a few months before I was happy with my decisions, and even now I am still thinking about it. Here are my thoughts on the split of responsibilities between the two layers:
1. The DDD-styled entity classes (data layer) should only consider issues around the correct formation of the data. In this example it returns an error if there are no line items in the order, as that is not a valid data format for an order.
2. The business rules/business actions belong in the business layer. These are things like checking that the business rules have been followed (in this example, that the T&Cs box was ticked) and triggering any other business logic, like sending an email to the customer.

On your questions about unit testing. EF Core is much better set up than EF6.x for unit testing. I use in-memory databases for testing the DDD-styled entity classes – they are quick to create and quick to run. For the business logic I do have the BizDbAccess layer, which I can mock.

Manshadi
Manshadi
3 years ago

Hi, What about enterprise apps ? Is it suitable for large scale projects?

Jon P Smith
3 years ago
Reply to  Manshadi

Hi Manshadi,

I would say so. Business logic ranges from setting a status in the database up to very complex business logic (I worked with my wife on healthcare location modelling – the business logic was massive). I would wouldn’t apply this approach for just setting a status value in a class/table, but once it gets beyond a simple update then this approach is worth the considering.

aranm
aranm
3 years ago

Why do you need to use method injection? Is it because your Controller violates SRP?

Jon P Smith
3 years ago
Reply to  aranm

Controllers tend to break the SRP (Single Responsibility Principal) because they do lots of things (list-many, list-one, create, edit, delete) but for one thing. Razor Pages are better at SRP as each view/class does one thing.

My libraries like GenericBizRunner and GenericServices provide services that take a lot of code out of the Controller method. I quite like using parameter injection as the system only has to create the specific service it needs.

Geoconnect
3 years ago

Why are you using static method CreateOrderFactory in order class line 19

Jon P Smith
3 years ago
Reply to  Geoconnect

Hi Geoconnect,

As I explain in the article (see Notes on lines 19 to 21) I use a static factory because the creating of an order can return an error to the user. By using a static factory I can return a status, plus the Order instance.

In cases where the create cannot generate an error then I would use a constructor.

Ridley Knarrenheinz
Ridley Knarrenheinz
3 years ago

I may be stressing something obvious, but although this approach highly suggests the DDD, I have a feeling that this is, globally, more of a database-driven design, as the database is, at the end, the one in the middle of everything, not the domain model. This is also confirmed with the implementation of the layer with business rules (the Bussines Layer) that comes more as a wrapper (or more correctly, a ‘layer above’) for a Data Access Layer.

My confusion mostly comes from the fact that the ‘entities’, which are basically designed as domain models (guess that makes them domain entities…) are set in Data Access Layer (not that the reasons for that decision aren’t presented). I personally never saw those as the true ‘models’, having business logic or not, but something highly dependent on the database/ORM, something that must be mapped to the real domain models (which reside in Business Layer) – basically, database tables.

Furthermore, those classes, which are used all around the application (perhaps not so much in GenericBizRunner project as in the one that comes with EF Core in Action e.g. for Review creation, we see Review entity even in View) are directly used in EF context class. This kinda gives them the persistence feature, which is something domain models shouldn’t have (by DDD). And for the case when (domain) models are not something that directly resembles the database table, I can’t just put them in context’s DbSet<>, so the mapping is inevitable.

Jon P Smith
3 years ago

Hi Ridley, and thanks for taking the time to describe what the differences between your approach and my approach as described in this article.

I realise that there are approaches that decouple the domain classes from the database, but I’m not convinced that Eric Evans would say that is the only way. In his book he has a section in the repository part called Working within Your Framework where he says on page 157

In general, don’t fight your framework. Seek ways to keep the fundamentals of the domain-driven design and let go of the specifics when the framework is antagonistic. Look for affinities between the concepts of domain-drive design and the concepts in the framework.

You also have to understand that the book was written in 2003 and the database frameworks were far simpler than today and repositories where the recommended approach. I have tried to find an approach that follows the approach Eric Evans proposed, but takes advantage of newer frameworks. That way I can write applications quickly and still benefit from the DDD approach.

Abdellah GRIB
Abdellah GRIB
3 years ago

Hello and thank you for this article,

Don’t you think there will be a duplication (free complexity) of code by prohibiting the service layer from using DbAccesses ?

I understand the need to isolate everything related to business, but why not having common base logic to access to db that will be used by both service layer and bizdbaccess ?

Jon P Smith
3 years ago
Reply to  Abdellah GRIB

Hi Abdellah,

Some people do have common code, say in a repository, that the biz logic and the front-end can use. I don’t do that because I am applying the Single Responsibility Principal (SRP). That means I will write database access code specifically for my business logic. That might mean there is more class/code in my approach, but that code is only used to do what my business logic needs. That means its really easy to refactor my business logic as I can change the database access code knowing it won’t effect any other code.

I agree with Neil Ford’s comment from his book Building evolutionary architectures:

The more reusable the code is, the less usable it is.

I have found over the years that making lots of small, simple classes used by one/few other classes beats making a few, large classes that lots of classes use. That’s why I don’t use a general repository pattern.

Abozar Nozari
Abozar Nozari
3 years ago

Hi Jon,
Thank you for your perfect articles and book about EF Core.
I am implementing the architecture you explained here with collaboration of EfCore.GenericBizRunner library to run business logic, and DDD styled entity class for my domain model entity classes and theoretical guidance of chapters 4 and 10 of “EF Core in Action” book.
Actually I have some ambiguity or maybe misunderstand about business logic location. I read @Peter B. comment and your replay. You mentioned that

“… So the next question is what business logic should go in the data layer, and what business logic should go in the business layer? …”

and explain the separation of concerns of business logic between BizLogic layer and DDD styled classes in data access layer.
If we put some portion of business logic in BizLogic layer and rest of it in data access layer, actually the integrity or atomic spirit of one business logic unit may break. For instance, in the “place an order” sample code you check acceptance of “T&C” in PlaceOrderAction class in BizLogic layer, and create Order as a business object in CreateOrderFactory method in data access layer. There is a trap here to bypass PlaceOrderAction class in upper layers and create order without check “T&C”.
In this article you wrote that:

“… This design splits the load between this class in the Business Layer and the Order entity class. In my design I deem that an entity class can work on itself (called the root entity) and any of its aggregates, i.e. navigational links that it the root entity handles. This means that the Order entity class doesn’t handle any Book accesses, which is why that is done in this PlaceOrderAction class …”

and Dino Esposito in his book “Microsoft .NET-Architecting Application for the Enterprise” defined Domain Services like this:

“Domain services are classes whose methods implement the domain logic that doesn’t belong to a particular aggregate and most likely span multiple entities. Domain services coordinate the activity of aggregates and repositories with the purpose of implementing a business action. In some cases, domain services might consume services from the infrastructure, such as when sending an email or a text message is necessary …”

.
My interpretation from 2 clauses mentioned above is that you design action classes in BizLogic layer for implementing domain services which does not belongs to only one entity class, is it right?

Jon P Smith
3 years ago
Reply to  Abozar Nozari

Hi Abozar,

That is a very detailed look at the whole problem of how to handle the different types of business logic. There isn’t one way to do it because business logic ranges from a simple validation up to some massive AI code. But to answer your question – yes, I am building what Dino calls “domain services” (One of Dino’s book helped me a LOT when I came back to programming – great stuff).

I build things in a certain way, but its not the only way. You might like to read this article that looks at different approaches, see https://www.thereformedprogrammer.net/three-approaches-to-domain-driven-design-with-entity-framework-core/

Also, I have slightly altered how I handle CRUD with DDD classes when I build my EfCore.GenericServices library once I had finsihed the book – see https://www.thereformedprogrammer.net/genericservices-a-library-to-provide-crud-front-end-services-from-a-ef-core-database/ . I have some new ideas on how to improve GenericServices, but that has to wait until I have some spare time (!!)

Behzad Bahmanyar
Behzad Bahmanyar
6 years ago

Hi, Nice article, But I believe there two main issue with your architecture:
1) The DTOs in your solution are spread in two layers(BizLogic and ServiceLayer)
2) Service layer is not only Adapter, It actually behave in two ways
ServiceLayer -> BizLogic -> BizDbAccess -> DataLayer -> uses the context to CRUD data
ServiceLayer -> uses the context to CRUD data
which I believe is one the reasons you have to have DTOs in both layers.

Jon P Smith
6 years ago

Hi Behzad,

I agree – the service layer uses both an Adapter and Command pattern. When handling CRUD accesses its mainly acting as a Adapter, but with the BizLogic its acting as a Command pattern (and sometimes an Adapter pattern). I think that is OK, but your right that its not so clear-cut.

On there being DTOs in the business layer then this is mainly because of the linking of the layers. Because the Business Layer doesn’t know about the ServiceLayer I defined the DTOs in the Business Layer. I could have just used an interface in the Business Layer and defined the DTOs in the ServiceLayer, but that is more code for little gain. I also think the DTOs in the Business Layer are different to the DTOs used for CRUD.
– The DTOs in the Business Layer are true Data Transfer Objects – they are there to transfer data between layers.
– The DTOs used for CRUD are implementing an Adapter pattern – they are picking out, and transforming, data on the way through.

I don’t think I brought that distinction out enough in the article, so thanks for pointing that out. I hope my comments help.

Jonathan
Jonathan
3 years ago
Reply to  Jon P Smith

Thank you for your article, It is very interesting.
About the DTO in the business layer:
Wouldn’t it be better that the ServiceLayer maps the front-end DTO into a domain object (or another DTO) and uses it to communicate with the BusinessLayer? (This domain object doesn’t necessarily need to be persisted in the DB), in this way the front end DTO wouldn’t go that deep.

In your case if the DTO changes in the Service Layer (due to a change in the Front end), then the business layer would be affected, isn’t it?
Do you see the Business Layer as a deeper layer than the Service Layer?
What do you think?

Thanks!

Tim Schmidt
Tim Schmidt
6 years ago

Awesome article @JonPSmith:disqus! I started with your “Is the Repository pattern useful with Entity Framework?”, then made my way here, and will continue on with others, as well as your book. I really appreciate the content and, especially, that you’ve updated it. I look forward to the finished book!

Jon P Smith
6 years ago
Reply to  Tim Schmidt

Hi Tim,

Thanks for the very positive review! I’m glad you like it. I have been writing articles for some years – its hard work, but I like doing it. In fact it is through these articles that Manning found me and asked if I would like to write the book Entity Framework Core in Action. The book is MUCH harder that writing articles! – we are expecting it to by over 400 pages. Not so many articles at the moment because the book is so full on.

PS. You can buy it on early access – you get the chapters as I write them, and the whole book when its finished.

Tim Schmidt
Tim Schmidt
6 years ago
Reply to  Jon P Smith

haha already got it and am digging into it. Also, I love that you chose to actually keep the SoC in your examples~ (and the rare occurrences where you didn’t, you call it out). So frustrating when people don’t do that. I end up asking myself “They didn’t do things this way…is that on purpose?! If so, why? Am I missing something? Are they just lazy?” (usually ends up being the last).

Good luck with the book! Writing is hard stuff.

Lucretia Rinker
Lucretia Rinker
7 years ago

My colleagues wanted a form earlier this week and were made aware of an online service that hosts a huge forms library . If people need it too , here’s https://goo.gl/HjL6Ly

Jon Smith
7 years ago

Hi Veillet Alexandr,

I hope this comment gets to you. I have updated the article to reflect what I believe you were saying in your comment. Do let me know if this makes sense. Also, it you have your own blog site I am happy to update the links in the article to point to that.

Thanks for your input.

Veillet Alexandre
Veillet Alexandre
7 years ago
Reply to  Jon Smith

Hi Jon, sorry i answered to you in the previous article. Like i said, i have read the article last month and saw the last update today. the article is really nice and go in a really nice direction with pretty good advices.
I would have gladly give to you my blog but i don’t write one because i don’t have much time, although i am trying to write some kind of book about basic architecture concept to advanced concept, in a short but meaningful way since i hate most book which are too lengthy so i decided to do my own which cover many aspects in a short and cocnise way but that also take a lot fo time to do and it won’t probably ready for a very long time

Steve Abel
Steve Abel
7 years ago

Great article – will you be releasing the full code of the example app you built?

Jon Smith
7 years ago
Reply to  Steve Abel

Hi Steve,

Not at the moment. I’m still playing with things and its not right yet. Also I have a contract job on so not much time at the moment.

Ramón Gutiérrez
Ramón Gutiérrez
7 years ago
Reply to  Jon Smith

Hi Jon. Excellent article.
Hope to see your full code for this demo.

Don Kitchen
Don Kitchen
6 years ago

Any progress on the code for this? I like the approach you’re using and it seems like a good fit for a new project I’m starting with EF Core as well.

Jon P Smith
6 years ago
Reply to  Don Kitchen

Hi Don,

I have been commissioned to write a book on EF Core for Manning Publications, and an early access of this should be out in a couple of weeks with the first three chapters.

I’m working on chapter 4, which is all about this topic, and I should have a Git repo with some code in it that might be useful.

I’ll let you know when its out, and I may well add another article with a link to the code.

Don Kitchen
Don Kitchen
6 years ago
Reply to  Jon P Smith

First off, congratulations on the book!

I look forward to reading it and seeing the code once you get to that point.

Take care and good luck!

Jon P Smith
6 years ago
Reply to  Don Kitchen

Hi Don,

The early access version of the book is out. Go to Entity Framework Core in Action and click the “Source code on GitHub” link to get to the code. The BizRunners are in the Chapter04 branch – look for BizRunners folder in the ServiceLayer.

Chapter 4, which deal with business logic hasn’t been released to MEAP yet – I have written it and its being checked as we speak. Should be added to the early access eBook in 2 to 3 weeks.

Ian Worthington
Ian Worthington
7 years ago

Why not just have bizlogic in the services layer, then make BizDbAccess a repository layer which can handle all kinds of requests from basic CRUD, to bizlogic requests?

Jon Smith
7 years ago

Hi Ian,

I see where you are going with your question. My answer is a combination of:

1. My BizDbAccess’s aren’t trying to be a all-singing, all dancing repository layer (see my article Is the Repository pattern useful with Entity Framework?). It is simply isolating the EF code used in the BizLogic into one place. This makes jobs like refactoring the EF code, performance tuning etc. easier.

2. I have built an open-source library, GenericServices which I use for CRUD accesses.