Должна ли вся бизнес-логика быть в моделях домена или?
ASP.NET MVC4-в основном у меня была вся моя бизнес-логика в моих контроллерах (которую я пытаюсь поместить в модели домена). Однако я не совсем знаю, должна ли вся моя бизнес-логика быть помещена в модели домена или некоторые должны оставаться в контроллерах?
например, я получил действие контроллера, как показано ниже:
[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);
}
}
теперь вся логика должна быть помещена в метод в модели домена, чтобы метод действия выглядел так это?
[HttpPost]
public ActionResult Payout(PayoutViewModel model)
{
var model = GetModel(model);
return View(model);
}
или как бы вы это сделали?
5 ответов
мы помещаем наше приложение и бизнес-логику в отдельные слои (файл csproj) доменный слой для бизнес-логики и сервисный уровень для логики приложения. Это полностью абстрагирует их от проекта MVC. У этого есть два больших преимущества для нас. Во-первых, бизнес-логика не привязана к шаблону, который может измениться. Несколько лет назад никто из нас не мог представить себе популярность MVC сегодня, и через несколько лет мы не знаем, будет ли какая-то новая вещь, которая придет вместе и замените MVC, чтобы получить подавляющее большинство вашего кода, чтобы быть "не привязанным" к MVC, помогло бы, если вы когда-нибудь захотите отказаться от MVC для чего-то другого.
второе преимущество заключается в том, что наличие разных слоев презентации очень легко реализовать. Поэтому, если вы хотите представить свою бизнес-логику как службу WCF, вы можете сделать это очень легко, создав новый проект WCF и сделав его фасадом для своих слоев службы и домена. Это делает обслуживание очень легким, так как оба ваших Проект MVC и служба WCF будут использовать одни и те же классы бизнес-логики.
пример Ниже приведен пример кода, что я буду делать. Это быстро и грязно, и должно быть больше, как добавление журнала, если пользователь не сохраняет обратно в базу данных и т. д...
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;
}
}
ваш контроллер теперь будет выглядеть так
[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);
}
и если вам пришлось выставить выплату в веб-сервисе (я работаю в корпоративной среде, так что это бывает много для меня) Вы делаете следующее...
public class MyWCFService : IMyWCFService
{
private PayoutService service = new PayoutService();
public PayoutResult Payout(string username, PayoutModel model)
{
return this.service.Payout(username, model);
}
}
для меня разделение является самым важным руководящим принципом для этих решений. Таким образом, это зависит от того, насколько сложен ваш домен и какую выгоду вы получаете от усложнения кода.
в любом случае, как правило, я склонен давать контроллерам следующие проблемы:
- создание экземпляров и отображение моделей представления (если нет значительного отображения)
- Посмотреть модель проверки
и, я склонен ссылаться на модель (или служба) для знаний домена, не относящегося к приложению:
- вывести деньги
- сделать вывод
Итак, вот как я бы разделил код:
[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);
}
}
сохранение состояния приложения, я обычно заворачиваю в качестве перехватчика. Таким образом, вы можете обернуть единица работы сделки вокруг всего запроса.
Я бы поместил всю логику в модель домена и сделал два вызова домена, один для проверки, один для выполнения варианта использования.
таким образом, сущность выглядит так:
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;
}
}
и ваш контроллер будет выглядеть так:
[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);
}
есть и другие вещи, которые я бы сделал, например, вставить слой приложения / Службы, использовать viewModels и выполнить все сброс ViewModel в ViewModelBuilder / Mapper или simmilar, но это показывает основное идея.
подход, которому мы следуем, требует бизнес-кейсов, заключенных в ViewModel (ваш случай: PayoutViewModel) и выставленных через метод, и этот метод будет использоваться в действиях контроллера. Кроме того, у нас есть четкое разделение на модель и модель представления, где viewmodel ссылается на модель внутри нее.
рекомендуется , чтобы у вас был тонкий код на контроллерах, лучше обрабатывать бизнес-логику в других слоях, таких как serviceLayer, который я использовал, перед которым возвращает вам модель представления того, что вы хотели вернуть в свой вид/контроллер. Даже определите свои методы ajax внутри класса уровня обслуживания.Которые уменьшают сложность кода и проблемы ремонтопригодности.Даже это более читабельно ..
в контроллере вы можете использовать DI для ввода класса serviceLayer или instansiate это как
ServiceLayer test = new ServiceLayer() ;
тогда в вас контроллер
test.registerCustomer(model); // or
test.registerCutomer(CustomerViewModel);