jasontaylordev / CleanArchitecture

Clean Architecture Solution Template for ASP.NET Core
MIT License
16.58k stars 3.56k forks source link

Ideal place to put BusinessLogic #78

Closed ShreyasJejurkar closed 4 years ago

ShreyasJejurkar commented 4 years ago

So, I am using this template quite a while now. Right now the there are only CRUD application scenarios in this template, where you just request for the data from one domain and then via meditr the request is raised and you get the new data.

But I am looking for more complex scenarios. Consider a situation where we need to get or put data from/to multiple domain objects and then do some operations on both of the data and pass that data to view to display, then how this template gonna handle this situation. As we know we cannot call another query handler method from one query handler method, which is not good approach and it has been discussed in many of the issues in this repository and as well as in NWT repository.

And also we cannot put our business logic in our controller because that will make it's less reusable and will make our controllers fat which we have to avoid.

So while I was thinking about this issue, I do have a better approach to doing it. So we don't have any service layer in between the controllers so we can introduce it and can write the custom business logic there and in that, we can have meditr calls to CQRS objects for data. This will make separation of BL from other layer as well and also it will be reusable. So it is just my approach to doing it, I really wanna other people thoughts.

public class BusinessLogicService
    {
        public List<TodoUser> GetTodosAndUsersDetails(IMeditor m) 
        {

            var users = m.Send(new GetAllUsers());

            var todos = m.Send(new GetAllTodos());

            // Apply logic and return the result to the controller call
            return new List<TodoUser>();
        }
    }
GFoley83 commented 4 years ago

It really depends on what you really mean by business logic.

If you just mean orchestration, i.e. retrieving all Users and Todos from the Db and mapping the data to a view model, which will ultimately be passed to the presentation layer, then everything inside your GetTodosAndUsersDetails, all this can take place inside a single command handler. Your command handler is your service layer. A per-request service layer of sorts.

Business logic to me means modelling what a business actually does in the real-world, in code. This is what the domain layer is for. The domain layer is where all your relationships and behaviors go e.g. a manager having many employees, an employee having an address etc.

https://stackoverflow.com/a/12167206/654708

ShreyasJejurkar commented 4 years ago

But we cannot call another query handler in one query handler, let say I have a query like GetEmployeeByUserIdand in Employee, I have managerID, now I want to get the Manager name based upon the ID and return the employee details along with his manager name to the controller. Now I will get Employee details in the handler of GetEmployeeByUserIdquery, but to retrieve manager details, I cannot call GetManagerByIdhandler in GetEmployeeByUserIdhandler, as its not recommended approach of calling one handler in another handler. Then how you would handle this situation. (This is just simple scenario)

I know I can call two handlers separately in the controller and can do operations on the data and return the result to the view. But this is just about two domain models, in the real world there might be 10's and 20's then it will be a huge code in controller and ultimately it will become the fat controller.

So I need a place where I can write this kind of code and it will be clean and maintainable

GFoley83 commented 4 years ago

I think you misunderstood my comment. I'm saying don't use two handlers at all, put everything you're talking about with employee and manager in one handler.

It's perfectly fine to load data from multiple sources, from a single handler.

ShreyasJejurkar commented 4 years ago

So you mean inject employeeRepository and ManagerRepository in the same handler which is responsible GetEmployeeByUserId CQRS? is it a good practice?

GFoley83 commented 4 years ago

Yes, extractly. It's perfectly fine to to inject more than one repository into the handler. No need to over-engineer the solution when the use-case (sounds like some basic CRUD operations) is simple.

I don't use repositories with a solutions like CleanArchitecture, as I feel they are a redundant abstraction. Which I think is also why there are none in this solution also. Instead of injecting every repository you need, just inject the interface for your DbContext.

ShreyasJejurkar commented 4 years ago

Ok. Thanks for the explanation. So at last it's not bad to include some complex business logic in handler which needs data from multiple repositories.

mnsrulz commented 4 years ago

Even if you look at the template, the idea is to use one application db context instead of multiple repos... with this it’s easy to read manipulate the data as per the need and at the same time following single responsibility principle.

ShreyasJejurkar commented 4 years ago

But if I have a logic that modifies the multiple records in multiple tables based on some logic which depends upon input data, then I think the handler method get so fat which I want to avoid, so that's why I was asking is there any place where I can include complex logic without getting handler fat.

GFoley83 commented 4 years ago

As I said above, all complex business logic goes in the Domain layer, not the Application layer.

https://github.com/jasontaylordev/CleanArchitecture/issues/67#issuecomment-599144705

Saving and retrieving multiple things to the database is not complex business logic.

https://lostechies.com/jimmybogard/2008/08/21/services-in-domain-driven-design/

In any case, this is a standard software design problem and not an issue with this repo. The info above should help. Beyond that, questions like these belong in StackOverflow or Code Review Stack Exchange.

Good luck.

mnsrulz commented 4 years ago

I have another question related to validation. Say if I have a Query where I need to apply validation which are dependent upon database calls or some other network operation. What place should I place that? In query/command validator class or directly in the query/command handler?

e.g. in this project to restrict user from accessing other users todo items/list. If a user is trying to access a resource which is present in the database but should be restricted to current user. I wanted to return a 403 forbidden...

GFoley83 commented 4 years ago

Validation and authorisation are two different things and should be kept separate.

Given what you're asking has nothing to do with this project, If you'd like to post a question on StackOverflow, I'd be happy to answer there.

mnsrulz commented 4 years ago

Validation and authorisation are two different things and should be kept separate.

Given what you're asking has nothing to do with this project, If you'd like to post a question on StackOverflow, I'd be happy to answer there.

I asked it in SOF https://stackoverflow.com/questions/60794058/at-what-layer-should-we-validate-user-resource-permission?noredirect=1#comment107560182_60794058

jasontaylordev commented 4 years ago

Thanks, @GFoley83 & @manishrawat4u. You've covered it well so I'll close this issue.

achmstein commented 4 years ago

I have a background job that need to retrieve a list of products. Should I use 'query/query handler' to retrieve products? or should I directly query DbSet? By the way these products won't be returned to client side, they're needed to perform business logic only.

ShreyasJejurkar commented 4 years ago

@achmstein better to use query handler, then you can have those logging and other behaviors attached to it.