Should all business logic be in domain models or?

ASP.NET MVC4 - Basically, I had all my business logic in my controllers (which I am trying to include in domain models). However, I don’t quite know if I need to introduce ALL of my business logic in the domain model or if some of them should remain in the controllers?

For example, I got a controller action as shown below:

[HttpPost] public ActionResult Payout(PayoutViewModel model) { if (ModelState.IsValid) { UserProfile user = PublicUtility.GetAccount(User.Identity.Name); if (model.WithdrawAmount <= user.Balance) { user.Balance -= model.WithdrawAmount; db.Entry(user).State = EntityState.Modified; db.SaveChanges(); ViewBag.Message = "Successfully withdrew " + model.WithdrawAmount; model.Balance = user.Balance; model.WithdrawAmount = 0; return View(model); } else { ViewBag.Message = "Not enough funds on your account"; return View(model); } } else { return View(model); } } 

Now, should all the logic be introduced into the method in the domain model so that the action method looks like this?

 [HttpPost] public ActionResult Payout(PayoutViewModel model) { var model = GetModel(model); return View(model); } 

Or how would you do that?

+6
source share
5 answers

We put our application and business logic in separate layers (csproj file) for the domain level for business logic and the service level for application logic. This completely abstracts them from the MVC project. This has two big advantages for us. First, business logic is not tied to a template that can change. A few years ago, none of us would have seen the popularity of MVC today, and in a few years we don’t know if there will be anything new that will come and replace MVC, so the vast majority of your code will be “un-tied” for MVC someday want to give up MVC for something else.

The second advantage is that it is very easy to implement different levels of presentation. Therefore, if you want to present your business logic as a WCF service, you can do this very easily by creating a new WCF project and making this facade for your service level and domain. This simplifies maintenance because both your MVC project and your WCF service will use the same business logic classes.

Example The following is an example code of what I will do. This is quick and dirty, and there should be more to it than adding registrations if the user does not save back to the database, etc.

 public enum PayoutResult { UserNotFound, Success, FundsUnavailable, DBError } public class UserProfile { public float Balance { get; set; } public string Username { get; set; } // other properties and domain logic you may have public bool Withdraw(PayoutModel model) { if (this.Balance >= model.Amount) { this.Balance -= model.Amount; return true; } return false; } } public class PayoutService { IUserRepository userRepository; public PayoutService() { this.userRepository = new UserRepository(); } public PayoutResult Payout(string userName, PayoutModel model) { var user = this.userRepository.GetAll().SingleOrDefault(u => u.Username == userName); if (user == null) { return PayoutResult.UserNotFound; } // don't set the model properties until we're ok on the db bool hasWithdrawn = user.Withdraw(model); if (hasWithdrawn && this.userRepository.SaveUser(user)) { model.Balance = user.Balance; model.Amount = 0; return PayoutResult.Success; } else if (hasWithdrawn) { return PayoutResult.DBError; } return PayoutResult.FundsUnavailable; } } 

Your controller will now look like this:

 [HttpPost] public ActionResult Payout(PayoutModel model) { if (ModelState.IsValid) { var result = service.Payout(User.Identity.Name, model); // This part should only be in the MVC project since it deals with // how things should be presented to the user switch (result) { case PayoutResult.UserNotFound: ViewBag.Message = "User not found"; break; case PayoutResult.Success: ViewBag.Message = string.Format("Successfully withdraw {0:c}", model.Balance); break; case PayoutResult.FundsUnavailable: ViewBag.Message = "Insufficient funds"; break; default: break; } } return View(model); } 

And if you need to expose the payment in a web service (I work in a corporate environment, so this is very common for me). You do the following ...

 public class MyWCFService : IMyWCFService { private PayoutService service = new PayoutService(); public PayoutResult Payout(string username, PayoutModel model) { return this.service.Payout(username, model); } } 
+10
source

For me, sharing problems is the most important guiding principle for these solutions. Thus, it depends on how complex your domain is and what benefit you get from complicating the code.

In any case, as a rule, I tend to give the Controllers the following problems:

  • Create and display view models (if no significant display exists)
  • View Model Check

And, I tend to refer to a model (or service) for knowing a domain other than the application:

  • You can withdraw money.
  • Make a conclusion

So, here is how I would split the code:

  [HttpPost] public ActionResult Payout(PayoutViewModel model) { if (ModelState.IsValid) { var account = accountRepository.FindAccountFor(User.Identity.Name); if (account.CanWithdrawMoney(model.WithdrawAmount)) { account.MakeWithdrawal(model.WithdrawAmount); ViewBag.Message = "Successfully withdrew " + model.WithdrawAmount; model.Balance = account.Balance; model.WithdrawAmount = 0; return View(model); } ViewBag.Message = "Not enough funds on your account"; return View(model); } else { return View(model); } } 

Saving the state of the application, I usually end up in an interceptor. This way you can wrap the work transaction block around the entire request.

+5
source

I would put all the logic in the domain model and make two domain calls, one for verification, one for the use case.

So the object is as follows:

 public class User { public double Balance { get;set; } public ValidationMessageCollection ValidatePayout(double withdrawAmount) { var messages = new ValidationMessageCollection(); if (withdrawAmount > Balance) { messages.AddError("Not enough funds on your account"); } return messages; } public void Payout(double withdrawAmount) { balance -= withdrawAmount; } } 

And your controller will look like this:

 [HttpPost] public ActionResult Payout(PayoutViewModel model) { if (!ModelState.IsValid) { return View(model); } var user = PublicUtility.GetAccount(User.Identity.Name); var validationMessages = user.ValidatePayout(model.WithdrawAmount) if(validationMessages.Any()) { ViewBag.Message = validationMessages.ToSummary(); return View(model); } ViewBag.Message = "Successfully withdrew " + model.WithdrawAmount; model.Balance = user.Balance; model.WithdrawAmount = 0; return View(model); } 

There are other things that I would do, for example, insert an application / service layer, use viewModels and do all the Reset ViewModel in ViewModelBuilder / Mapper or simmilar, but this shows the main idea.

+2
source

The approach we follow required business cases enclosed in a ViewModel (your case: PayoutViewModel), and was exposed through a method, and this method will be consumed as part of the controller’s actions. In addition, we have a clear distinction between model and view model, where in the model model viewmodel is referenced.

0
source

It is recommended that you have thin code on the controllers, it is better to manage the business logic in other layers, such as the serviceLayer that I used, before which the presentation model returns that you wanted to return to your view / controller. Even define your ajax methods inside a service level class. This reduces code complexity and maintainability issues. Even more readable.

In the controller, you can use DI to introduce the serviceLayer class or set it as

  ServiceLayer test = new ServiceLayer() ; 

then in the controller

  test.registerCustomer(model); // or test.registerCutomer(CustomerViewModel); 
-2
source

Source: https://habr.com/ru/post/947439/


All Articles