Russell D (3) [Avatar] Offline
#1
Figure 5.1
[ 66 KB ]
Figure 4.2
[ 64 KB ]
Hi Jon,

A co-worker and I read thru much of your MEAP recently and we had a few questions. Things that might make sense to elaborate on in the book. (Or they might be there and we missed them.)

First off, your book is great in terms of EF Core details and maybe more importantly architecture best practices on how to isolate database / business logic so you don't end up with a .net Controller that is a mess of EF routines, business logic, etc. (I ended up buying your book after reading your "Architecture of Business Layer working with Entity Framework (Core and v6) – revisited" article...)


My questions are mainly around the choice of when to make a process "Business Logic" and go thru the BizLogic / BizDbAccess layers. Versus when to use the db context directly in the Service layers for CRUD operations.

We really like the stack that that is shown on Figure 4.2... But it was modified in Figure 5.1 to let the ServiceLayer talk directly to the Data Access Layer. (I attached pictures of the diagrams for reference.)

With that in mind… Here is what I'm curious about, and maybe your future readers might be curious about also:

What, for you, defines the difference between a CRUD operation and BizLogic?
  • For example, ListBooksService.SortFilterPage is directly accessing the context and performing DB specific and semi-complex operations to get the filtered list of books.

  • This is almost as complex as the BizLogic.Orders.Concrete.PlaceOrderAction that uses the IBizAction interface.

  • Why not isolate all CRUD operations also in a BizLogic / BizDbAccess type scheme?
  • This would isolate the database access completely and keep even simpler operations like getting a list of books (SortFilterPage) away from directly accessing the context.

  • Having one consistent path to get to the data, regardless of Biz or CRUD, seems nice.

  • This pattern gives you the benefits of being able create mocks and tests cleanly and to have the SaveChanges be only in one place when you have a transaction.

  • The benefit of "Isolate the [business] database access code into a separate project" is minimized if only the business logic has this isolation. Same with the "EF Core's SaveChanges" benefit.

  • At a minimum all CRUD and BizLogic operations could use a shared DbAccess type layer, even if you didn't create BizLogic for everything

  • It seems like beyond chapter 4 all the business logic is in the ServiceLayer instead of in the BizLogic
  • Just curious on your thought process on why you changed paths… Maybe creating this extra layer wasn't worth the trouble?

  • We were contemplating using a BizDbAccess type layer for everything in our project (i.e. Always following Figure 4.2) until we saw you didn't extend that after chapter 4.

  • Semi-related. Code organization-wise, what do the "Concrete" subfolders imply in BizLogic.Orders, ServiceLayer.BookServices, etc
  • I was thinking they were concrete classes or maybe just the Service classes only, but that doesn't always seem to be the case.

  • I.e. OrderLineItem, SortFilterPageOptions.cs are concrete. And there are non-service classes in the Concrete folder also.


  • To me it comes down to trying to pick a single pattern. Mixing in business Logic in the Services along with direct EF access VS the more complete isolation of EF from the ServiceLayer using BizDbAccess and/or BizLogic for all operations.

    Thanks in advance!
    -- Russell
    Jon P Smith (32) [Avatar] Offline
    #2
    Hi Russell,

    Wow, that is some question! Lots of good items, so I hope I can give you a good set of answers.

    What, for you, defines the difference between a CRUD operation and BizLogic?
    CRUD operations are ones that can be directly executed on the database. They don’t have any “rules” other than some validation and authorization. BizLogic has “business rules”, such that they can’t (easily) be described by validation checks alone.

    Clearly there is a grey area in the middle. For instance, I show changing the publication date of the book as a CRUD (update), but there may be some rules about changing the publication date, like you can’t move it backwards. In that case it would need to be done as a business rule.

    Why not isolate all CRUD operations also in a BizLogic / BizDbAccess type scheme?
    It’s all performance and quick development – CRUD operations typically occur a lot more than business logic in an application. You want then to run fast, and develop then quickly. The BizLogic/BizDbAccess is going to have slightly slower performance (not much), but it WILL take a lot more time to develop. Query Objects are great.

    Note: I have built a library called GenericServices (see https://github.com/JonPSmith/GenericServices), which automates the CRUD even more. It’s only available for EF6.x. I do plan to rewrite it for EF Core, but its not going to happen for a while.

    Isolation of BizLogic from EF Core
    The BizLogic assembly/project does not include the EF Core libraries, which means it can’t access the database. In the applications I have built up till now I actually separate the entity classes into a separate assembly, which then means that the BizLogic can’t even see the EF Core parts. I didn't do that in the book as it would a) make it even harder to understand and b) its stops the possibility of building DDD repos (chapter 10).

    It seems like beyond chapter 4 all the business logic is in the ServiceLayer instead of in the BizLogic
    All the services in the ServiceLayer are there because they need to deal with classes that are presentation-focused. They are there to isolate the bizLogic from anything presentation-focused. I could have put that code in the ASP.NET Core layer, but I have learnt that keeping the ASP.NET Controllers etc. as simple as possible is the way to go.

    I am currently building a GenericBizRunner which automates much of the BizRunner pattern I showed in chapter 4 and moves most (but not all) of the service code into special DTOs that live in the ServiceLayer. Watch out on my twitter feed https://twitter.com/thereformedprog, for a tweet when its ready.

    Semi-related. Code organization-wise, what do the "Concrete" subfolders imply in BizLogic.Orders, ServiceLayer.BookServices, etc
    The idea is that anything in a folder called Concrete is a class that will be created by dependency injection. Therefore, if I see a using that ends in with .Concrete in classes other than in classes in the same folder then I know it’s not right.

    Anything that should be referred to directly by other layers I keep at only one level down. That makes the using statements short. Classes one level down are normally DTOs or interfaces. So, your example of OrderLineItem.cs, is a DTO that is referred to by the ServiceLayer etc.

    Now, in the book I don’t exactly follow that, because I only turn some services into ones that use dependency injection in chapter 5. In my own projects that holds true (mostlysmilie ).


    Jon P Smith (32) [Avatar] Offline
    #3
    Jon P Smith wrote:Hi Russell,

    Wow, that is some question! Lots of good items, so I hope I can give you a good set of answers.

    What, for you, defines the difference between a CRUD operation and BizLogic?
    CRUD operations are ones that can be directly executed on the database. They don’t have any “rules” other than some validation and authorization. BizLogic has “business rules”, such that they can’t (easily) be described by validation checks alone.

    Clearly there is a grey area in the middle. For instance, I show changing the publication date of the book as a CRUD (update), but there may be some rules about changing the publication date, like you can’t move it backwards. In that case it would need to be done as a business rule.

    The other difference is that CRUD is often very presentation-focused. Something that I try to stop in my BizLogic.

    Why not isolate all CRUD operations also in a BizLogic / BizDbAccess type scheme?
    It’s all performance and quick development – CRUD operations typically occur a lot more than business logic in an application. You want then to run fast, and develop then quickly. The BizLogic/BizDbAccess is going to have slightly slower performance (not much), but it WILL take a lot more time to develop. Query Objects are great.

    Note: I have built a library called GenericServices (see https://github.com/JonPSmith/GenericServices), which automates the CRUD even more. It’s only available for EF6.x. I do plan to rewrite it for EF Core, but its not going to happen for a while.

    Isolation of BizLogic from EF Core
    The BizLogic assembly/project does not include the EF Core libraries, which means it can’t access the database. In the applications I have built up till now I actually separate the entity classes into a separate assembly, which then means that the BizLogic can’t even see the EF Core parts. I didn't do that in the book as it would a) make it even harder to understand and b) its stops the possibility of building DDD repos (chapter 10).

    It seems like beyond chapter 4 all the business logic is in the ServiceLayer instead of in the BizLogic
    All the services in the ServiceLayer are there because they need to deal with classes that are presentation-focused. They are there to isolate the bizLogic from anything presentation-focused. I could have put that code in the ASP.NET Core layer, but I have learnt that keeping the ASP.NET Controllers etc. as simple as possible is the way to go.

    I am currently building a GenericBizRunner which automates much of the BizRunner pattern I showed in chapter 4 and moves most (but not all) of the service code into special DTOs that live in the ServiceLayer. Watch out on my twitter feed https://twitter.com/thereformedprog, for a tweet when its ready.

    Semi-related. Code organization-wise, what do the "Concrete" subfolders imply in BizLogic.Orders, ServiceLayer.BookServices, etc
    The idea is that anything in a folder called Concrete is a class that contains real code. e.g. not DTOs or interfaces, and is often created by dependency injection. Therefore, if I see a using that ends in with .Concrete in classes other than in classes in the same folder then I know it’s not right.

    Anything that should be referred to directly by other layers I keep at only one level down. That makes the using statements short. Classes one level down are normally DTOs or interfaces. So, your example of OrderLineItem.cs, is a DTO that is referred to by the ServiceLayer etc.

    Now, in the book I don’t exactly follow that, because I only turn some services into ones that use dependency injection in chapter 5. In my own projects that holds true (mostlysmilie ).


    Russell D (3) [Avatar] Offline
    #4
    Fabulous info, Thanks! So good, I don't have any followup questions. smilie


    Related thoughts...

    Our most recent internal debate, before reading your response, was: It seems odd to have two patterns for accessing data in a solution... BizDbLayer for the BizLogic vs the ServiceLayer which gets direct access to the db context for its CRUD functions. (Your answer helps me see why.)

    I know it would be extra work, but we might go down a path that uses a [Biz]DbAccess layer for ALL database access. We could use DbAccess in both the BizLogic and the ServiceLayer. (It would isolate the EF specific stuff and let us send mock data to both layers easier. This might be overkill, but in general I've had much better luck testing against a fully mocked DbAccess layer vs seeding test data in a database. Maybe there is a way to Mock the data that is accessed thru EF though?)

    Also Pondering... It seems like there might be a pattern needed to create these BizRunners so they are fed everything they need and don't have any side effects until complete. i.e. Don't write to the database until after the full set of BizActions are completed. Some of this might be tricky due to concurrency (i.e. I want to prevent multiple debits for multiple devices that would make my balance negative), but it makes for cleaner / more testable code if you could lock things properly. These BizRunners would act almost like static methods / LINQ then...


    I'll keep an eye out for your GenericBizRunner. I could see how one could make the Runner execute a list of IBizActions to make it generic...

    I know this isn't the main focus of your book, but I think coming up with a pattern / best practice for PresentationLayer -> ServiceLayer / BusinessLogicLayer -> DatabaseLayer like you have done is needed. I've seen too much ugly code without a ServiceLayer that should be banished...
    Jon P Smith (32) [Avatar] Offline
    #5
    Hi Russell,

    That sounds like a plan. I would be interested in how it goes.

    Here are a few minor thoughts

    - Mocking: With EF Core's in-memory databases unit testing with actual databases is very easy - see my article http://www.thereformedprogrammer.net/using-in-memory-databases-for-unit-testing-ef-core-applications/.
    - Architecture : I started writing an article about how to implement different architectures in .NET, but it is going to take a lot of work, so I sidelined it for now. There are a number of other variants of the layered architecture, like the hexagonal architecture (see http://alistair.cockburn.us/Hexagonal+architecture) which has the service layer all around the edge, and the module architecture (see http://www.codingthearchitecture.com/presentations/sa2015-modular-monoliths).
    - Chaining BizLogic: I have build transactional BizRunners (see section 4.6.2 of my book).

    Hope that is helpful.
    Russell D (3) [Avatar] Offline
    #6
    Very helpful thanks!

    The article on EF mocking was exactly what I was looking for.

    Btw. We figured, worst case, that if the DbAccess abstraction for the ServiceLayer caused issues it could be easily refactored out. (We would have to change test cases, but the service layer code would be easy to refactor.). Thus minimal harm in giving it a try.

    Thanks again!
    Jon P Smith (32) [Avatar] Offline
    #7
    Hi Russell,

    I just wanted to say that I have just released my EfCore.GenericBizRunner library, available on Nuget at https://www.nuget.org/packages/EfCore.GenericBizRunner/ . I have also written a log article called A library to run your business logic when using Entity Framework Core, plus the GitHub project has good docs and example code.

    If you have any suggestions either leave them as comments on the article, or if its more of a discussion send me something via the contact me page on my site and we can start an email discussion.