EntityFramework模型在领域驱动设计界定上下文中的应用模型在领域驱动设计界定上下文中的应用
在使用Entity Framework(以下简称EF)来定义模型(Model)时,开发人员往往喜欢把应用程序中的所有模型对象都一股脑
地塞进一个模型中。这种开发习惯估计是源于Database First的开发方式,在这种方式下,开发人员可以很方便地将数据库中
的表和视图直接拖拽到EF模型设计器中,于是一个模型也就包含了由这些表或视图所映射的所有对象。当然,不正确的Code
First的实践方式,同样也会造成这样的局面:在一个DbContext中为模型中的每一个实体都定义DbSet属性,甚至会不知不觉
地将与这些实体关联的所有类型全部包含进去。
当开发一个具有大型领域模型的超大规模的应用程序时,与设计一个单一的大领域模型相比,将大领域模型根据应用程序的业
务需要“切割”成一系列较小的模型是非常重要的,我们也往往能够从中获得更多的好处。在本文中我将向大家介绍领域驱动设
计(DDD)中的一个重要概念:界定上下文(Bounded Context),并向大家展示如何根据界定上下文来设计基于Entity
Framework Code First的模型。如果您是第一次接触DDD,那么本文将会为您提供一个很好的了解和学习DDD的机会;如果
您已经开始在项目中使用DDD,那么本文或许能够为您提供一些Entity Framework与DDD的实践启示。
领域驱动设计与界定上下文
DDD是一个相当广泛的话题,它囊括了软件设计的所有方面。作为Domain Language(DomainLanguage.com)中DDD
workshop的讲师,Paul Rayner是这样概括DDD的:
“DDD主张一种更为实用的、覆盖面更广的以及可持续的软件设计方式:通过与领域专家的沟通,将领域模型适配到软件系统
中,而正是领域模型帮助我们解决了那些重要的、复杂的业务问题”
DDD包括了很多软件设计模式,在这些众多的模式之中,界定上下文使我们能够很自然地在软件设计中使用Entity
Framework。界定上下文主张根据特定的业务领域设计和开发一些较小的模型。Eric Evans在他的《领域驱动设计:软件核心
复杂性应对之道》一书中,对界定上下文是这样描述的:“明确定义模型应用的上下文。根据团队组织、应用程序各个部分的
使用率、物理显现(如代码库和数据库方案)明确设置界限。在这些界限中要保持模型严格的一致性,但不要被外界问题干扰
和迷惑。”(选自《领域驱动设计:软件核心复杂性应对之道》一书,陈大峰、张泽鑫等译,2006年3月版)
更小的模型为我们的软件设计和开发带来了更多的好处,它使得团队能够根据自己的设计和开发职责确定更为明确的工作边
界。小的模型也为项目带来了更好的可维护性:由于上下文由边界确定,因此对其的修改也不会给整个模型的其它部分造成影
响。更进一步,就Entity Framework而言,相比大模型的读取和加载,小模型不仅加载速度快,而且内存占用也会相对较小,
在一定程度上提升了应用程序的性能。
由于我是使用EF的DbContext来设计和开发界定上下文,我原本打算使用“界定的DbContext”这一词语来描述我们的上下文。
然而,DbContext与界定上下文之间并不是完全等同的:DbContext是一个技术架构的类型实现,而界定上下文则是在描述一
个完整的软件设计过程中的一个更为广泛的概念。因此,使用“限定的”或者“专注的”来描述本文中出现的DbContext或许更为准
确。
经典的EF DbContext与界定上下文之间的对比
虽说DDD通常会被用在具有复杂领域业务的大型应用程序的开发之中,中小型应用程序的开发同样也能从DDD的理论和实践
中获益。下面,我将以一个具有特定领域业务的应用程序为例,向大家介绍Entity Framework在界定上下文中的应用。该应用
程序提供这样一种业务:它能为公司提供跟踪销售和市场信息的业务。通过分析不难发现,整个应用程序将包含多种对象,比
如:客户(Customers)、订单(Orders)、订单行项目(Line Items)、产品(Products)、市场(Marketing)、销售人员
(SalesPeoples),甚至还会包括公司雇员(Employees)。通常,我们都是在DbContext中定义包含了所有这些对象的
DbSet属性,就像下面的代码所示:
public class CompanyContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Employee> Employees { get; set; }
public DbSet<SalaryHistory> SalaryHistories { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<LineItem> LineItems { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Shipment> Shipments { get; set; }
public DbSet<Shipper> Shippers { get; set; }
public DbSet<ShippingAddress> ShippingAddresses { get; set; }
public DbSet<Payment> Payments { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Promotion> Promotions { get; set; }
public DbSet<Return> Returns { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Config specifies a 1:0..1 relationship between Customer and ShippingAddress
modelBuilder.Configurations.Add(new ShippingAddressMap());
}
}
想象一下,如果你所要开发的应用程序规模很大,包含了上百个类似Customer、Employee等这样的类型,那么在一个单独的
DbContext类型中针对每个类型定义一个DbSet的属性将是一个多么繁琐的工作,更何况你还需要通过Fluent Interface对其中
的某些类型进行配置。通常情况下,当应用程序的规模达到一定程度时,我们都会将其分割成多个子系统,项目中的每个团队