Должна ли вся бизнес-логика быть в моделях домена или?

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);
    }
}

для меня разделение является самым важным руководящим принципом для этих решений. Таким образом, это зависит от того, насколько сложен ваш домен и какую выгоду вы получаете от усложнения кода.

в любом случае, как правило, я склонен давать контроллерам следующие проблемы:

  1. создание экземпляров и отображение моделей представления (если нет значительного отображения)
  2. Посмотреть модель проверки

и, я склонен ссылаться на модель (или служба) для знаний домена, не относящегося к приложению:

  1. вывести деньги
  2. сделать вывод

Итак, вот как я бы разделил код:

    [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);