与 ramiramul 不同,我会避免引入太多抽象。
如果你使用 EF,你的 DAL 实际上是实体框架,不需要抽象它。很多人尝试这样做,但这只会使您的代码复杂化很多,没有任何好处。如果您正在执行 SQL 请求并直接调用存储过程,那么 DAL 会有所帮助,但在 EF 之上构建抽象(这是另一个抽象,或在 NHibernate 之上)是一个坏主意。
此外,作为抽象的纯 DTO 越来越不受欢迎,但如果您有中间件并且不直接访问数据库,则可以使用它们 - 例如,像NServiceBus这样的消息总线:在这种情况下,消息将被视为 DTO。
除非您执行非常简单和纯粹的 CRUD(在这种情况下,继续,将逻辑放在控制器中 - 没有理由为非常简单的业务增加复杂性),您肯定应该将业务逻辑移到控制器之外。为此,您有很多选择,但其中 2 个最流行的是:具有域驱动设计的丰富域模型或具有面向服务设计的丰富业务服务。它们有很多方法可以做到这一点,但这两种方法说明了非常不同的方法。
富域(每个聚合的控制器)
在第一种情况下,您的控制器将负责获取域对象、调用逻辑并返回视图模型。他们在 View 世界和 Model 世界之间架起了一座桥梁。如何获取域对象需要稍微抽象,通常简单的虚拟方法效果很好 - 保持简单。
聚合根:
public class Item
{
public string itemId { get; set; }
public string itemDescription { get; set; }
public float unitPrice { get; set; }
// more fields
public virtual ItemProductLine itemProductLine { get; set; }
// Example of logic, should always be in your aggregate and not in ItemProductLine for example
public void UpdatePrice(float newPrice)
{
// ... Implement logic
}
}
查看型号:
public class ItemViewModel
{
public int id { get; set; }
public string itemNumber { get; set; }
public String itemDescription { get; set; }
public Double unitPrice { get; set; }
public string productLine { get; set; }
}
控制器:
public class ItemController : Controller
{
[HttpGet]
public ActionResult Edit(int id)
{
var item = GetById(id);
// Some logic to map to the VM, maybe automapper, valueinjector, etc.
var model = item.MapTo<ItemViewModel>();
return View(model);
}
[HttpPost]
public ActionResult Update(int id, ItemViewModel model)
{
// Do some validation
if (!model.IsValid)
{
View("Edit", model); // return edit view
}
var item = GetById(model.id);
// Execute logic
item.UpdatePrice(model.unitPrice);
// ... maybe more logic calls
Save(item);
return RedirectToAction("Edit");
}
public virtual Item GetById(int id)
{
return dbContext.Items.Find(id);
}
public virtual bool Save(Item item)
{
// probably could/should be abstracted in a Unit of Work
dbContext.Items.Update(item);
dbContext.Save();
}
}
这非常适用于向下渗透并且非常特定于模型的逻辑。当您不使用 CRUD 并且非常基于操作时(例如,与可以更改所有项目值的编辑页面相比,仅更新价格的按钮)也很棒。它非常解耦并且关注点分离 - 您可以自己编辑和测试业务逻辑,您可以在没有后端的情况下测试控制器(通过覆盖虚拟功能),并且您没有建立在彼此之上的数百个抽象. 您可能会在存储库类中推出虚拟功能,但根据经验,您总是有非常具体的过滤器和依赖于控制器/视图的关注点,并且通常您最终每个聚合根都有一个控制器,因此控制器是他们的好地方(例如.GetAllItemsWithAPriceGreaterThan(10.0)
)
在这样的架构中,您必须小心边界。例如,您可能有一个产品控制器/聚合,并希望列出与该产品相关的所有项目,但它应该是只读的 - 您不能调用来自产品的项目的任何业务 - 您需要导航到项目控制器为了那个原因。最好的方法是自动映射到 ViewModel :
public class ProductController : Controller
{
// ...
public virtual IEnumerable<ItemViewModel> GetItemsByProductId(int id)
{
return dbContext.Items
.Where(x => ...)
.Select(x => x.MapTo<ItemViewModel>())
.ToList();
// No risks of editing Items
}
}
丰富的服务(每个服务的控制器)
借助丰富的服务,您可以构建更加面向服务的抽象。当业务逻辑产生多个边界和模型时,这非常有用。服务在 View 和 Model 之间扮演着桥梁的角色。他们不应该公开底层模型,只公开特定的 ViewModel(在这种情况下扮演 DTO 的角色)。例如,当您有一个 MVC 站点和一些 REST WebApi 在同一个数据集上工作时,这非常好,它们可以重用相同的服务。
模型:
public class Item
{
public string itemId { get; set; }
public string itemDescription { get; set; }
public float unitPrice { get; set; }
// more fields
public virtual ItemProductLine itemProductLine { get; set; }
}
查看型号:
public class ItemViewModel
{
public int id { get; set; }
public string itemNumber { get; set; }
public String itemDescription { get; set; }
public Double unitPrice { get; set; }
public string productLine { get; set; }
}
服务:
public class ItemService
{
public ItemViewModel Load(int id)
{
return dbContext.Items.Find(id).MapTo<ItemViewModel>();
}
public bool Update(ItemViewModel model)
{
var item = dbContext.Items.Find(model.id);
// update item with model and check rules/validate
// ...
if (valid)
{
dbContext.Items.Update(item);
dbContext.Save();
return true;
}
return false;
}
}
控制器:
public class ItemController : Controller
{
public ItemService Service { get; private set; }
public ItemController(ItemService service)
{
this.Service = service;
}
[HttpGet]
public ActionResult Edit(int id)
{
return View(Service.Load(id));
}
[HttpPost]
public ActionResult Update(int id, ItemViewModel model)
{
// Do some validation and update
if (!model.IsValid || !Service.Update(model))
{
View("Edit", model); // return edit view
}
return RedirectToAction("Edit");
}
}
控制器仅用于调用服务并为视图编写结果。与面向域的控制器相比,它们是“愚蠢的”,但如果您有很多复杂的视图(大量的组合视图、ajax、复杂的验证、json/xml 处理以及 html 等),这是首选方法。
此外,在这种情况下,服务不必只与一个模型相关。如果它们共享业务逻辑,相同的服务可以操纵多个模型类型。因此 OrderService 可以访问库存并在那里进行调整等。它们更多地基于流程而不是基于模型。