实体 VS 领域模型 VS 视图模型

56

关于这个主题已经有数百个类似的问题。但我仍然感到困惑,希望得到专家的建议。

我们正在使用ASP.NET MVC 4和EF5开发应用程序,采用的是DB first方法。

我们在一个单独的项目中拥有数据层,它是一个类库,并包含其中定义的所有实体。然后定义了业务层,其中包括所有存储库和域模型(这个术语使用是否正确)。接下来是表示层。

目前,我们没有定义任何视图模型,而是使用与BL相同的域模型作为视图模型。采用这种方法,可以只需要进行一次映射。

实体 <=> 域模型

但对我来说,这并不是一个好的设计。我更喜欢在我的表示层中定义视图模型,并使用域模型在表示层和业务层之间进行通信。在BL中,将域对象转换为数据实体并与DAL通信。使用这种方法,我需要进行两次映射。

视图模型 <=> 域模型 <=> 实体

我的域模型真的必要吗?我不能使用我的实体来与表现层通信吗?如果我在表示层中引用实体,是否会有任何影响?如果有,是什么影响?


你可以使用ViewModels将实体映射。 - Ehsan Sajjad
3
谢谢您的评论。当然我可以使用,但我想更多地了解在演示层直接使用引用实体的优缺点。 - Naresh
@GertArnold - 感谢您的评论。 - Naresh
3个回答

118

我认为你只是在定义每个层次的内容和在你的解决方案中所扮演的角色方面有一些问题。


数据层

您的数据层仅仅是指您的数据库/SharePoint列表/.csv文件/Excel表格等存储数据的地方,可以采用任何格式。因此请记住,数据层只不过是数据而已。

// ----------------------------
//    Data tier
//        - MySQL
//        - MS SQL
//        - SharePoint list
//        - Excel
//        - CSV
//        - NoSQL
// ----------------------------

数据访问层

该层抽象了您的数据源,并提供了一个API,使应用程序的其余部分可以与数据源交互。

假设我们的数据源是MS SQL数据库,并且我们正在使用Entity Framework访问数据。您将尝试抽象掉的是数据库和Entity Framework,并为每个实体创建一个数据仓库

例如...

我们在MS SQL数据库中有一个Customers表。客户表中的每个客户都是一个实体,并在您的C#代码中表示为这样。

通过使用存储库模式,我们可以抽象出数据访问代码的实现,因此,如果将来我们的数据源发生更改,则不会影响我们应用程序的其余部分。接下来,我们需要在数据访问层中添加一个CustomersRepository,其中包括AddRemoveFindById等方法,以抽象出任何数据访问代码。以下示例展示了如何实现此目标。

public interface IEntity
{
    int Id { get; set; }
}

public class Customer : IEntity
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime RegistrationDate { get; set; }
}

public interface IRepository<TEntity> where TEntity : class, IEntity
{
    TEntity FindById(int id);
    
    void Add(TEntity entity);
    
    void Remove(TEntity entity);
}

public class CustomerRepository : IRepository<Customer>
{
    public Customer FindById(int id)
    {
        // find the customer using their id
        return null;
    }
    
    public void Add(Customer customer)
    {
        // add the specified customer to the db
    }
    
    public void Remove(Customer customer)
    {
        // remove the specified customer from the db
    }
}

数据访问层位于数据层和业务逻辑之间。

// ----------------------------
//    Business logic
// ----------------------------

// ----------------------------
//    Data access layer
//        - Repository 
//        - Domain models / Business models / Entities
// ----------------------------

// ----------------------------
//    Data tier
//        - MySQL
//        - MS SQL
//        - SharePoint list
//        - Excel
//        - CSV
//        - NoSQL
// ----------------------------

业务层

业务层建立在数据访问层之上,不涉及任何数据访问问题,而是专门处理业务逻辑。如果其中一个业务要求是防止从英国以外的地方下订单,则业务逻辑层将处理此问题。


表现层

表现层仅呈现数据,但如果您不小心呈现数据并允许发布哪些数据,则会为自己带来很多麻烦,这就是为什么使用视图模型非常重要,因为视图模型是表现层关注的内容,表现层不需要了解您的域模型,它只需要知道视图模型的相关信息。

那么什么是视图模型呢?它们只是为每个视图量身定制的数据模型,例如注册表单将包括一个RegistrationViewModel,公开这些典型属性。

public class RegistrationViewModel
{
    public string Email { get; set; }
    public string Password { get; set; }
    public string ConfirmPassword { get; set; }
}

演示层也处理输入验证,例如验证键入的电子邮件地址是否具有正确的格式,或输入的密码是否匹配是演示层的问题,而不是业务问题,并且可以使用Data Annotations来处理。

public class RegistrationViewModel
{
    [Required]
    [DataType(DataType.EmailAddress)]
    public string Email { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [Compare("ConfirmPassword")
    public string Password { get; set; }

    [Required]
    [DataType(DataType.Password)]
    public string ConfirmPassword { get; set; }
}

使用View Models的原因在于Business Models属于业务层,其中包含应保持私有的数据。例如,如果您将Domain Model暴露在JSON响应中,它将会暴露用户的全部数据,包括他们的姓名和地址,因为您没有选择性地公开什么以及什么不公开,而是使用任何看起来有效的东西。


我还要指出,领域模型(domain models)和实体模型(entity models)之间存在区别。这里有一个更详细的答案:here


Cross cutting concerns

这里我简要介绍一下:

  • 异常管理
  • 将View Models映射到Domain Models。

5
我已经搜索了数小时,这就是我一直在寻找的解释。谢谢! - Kevin_
在实现上述存储库模式时,IRepository、IEntity、Customer和CustomerRepository应该放在数据访问层(DAL)还是业务层? - BeYourOwnGod
@VanceSmith 我应该更新上面的代码为这个,因为它更通用,你可以在任何/所有实体中使用它...回答你的问题:它将存在于数据层。 - Aydin
@AydinAdn:“横切关注点”部分非常简短。我担心在将持久对象映射到DTO时避免/最小化麻烦。我已经为此提出了单独的问题。https://dev59.com/eZzha4cB1Zd3GeqPIq_R - Amit Joshi
因此,在没有向客户端呈现任何数据的简单控制台应用程序中,就不需要视图模型,更不需要映射实体。我们只需从存储库将实体公开给业务层,在那里进行计算,然后再次传递打算持久化的数据即可。我是对的吗? - ccoutinho

12

我虽不是专家,但我会就这个主题分享我的意见。

视图模型

我想分享一下您对忽略视图模型的担忧。

使用视图模型,您可以:

  1. 从域模型中仅选择所需的数据
  2. 以正确的方式格式化所需的数据以供呈现(例如,将价格小数(100.00)格式化为字符串(€100.00))
  3. 您可以在视图模型上使用DataAnnotation。

因此,我认为这也是一个糟糕的设计,但其他人可能有不同的观点。

请记住,业务层不知道视图模型的任何信息,因此您应该在控制器中进行映射。

实体 vs 域模型

我建议先从一个可使用ORM或NoRM持久化的POCO作为域模型开始。对于大多数开发的软件,它不会对系统产生太多影响,而且很简单。

将来,如果您由于某种原因开始使用Web服务,则可能需要考虑使用DTO(数据传输对象)进行远程调用。那时,您可以拥有另一层,负责将域模型映射到所需的DTO。这个层仅在远程调用(Web服务)中使用,将视图模型保留供呈现使用。


1
谢谢您的回复。 - Naresh
感谢您的帖子。我了解到NoRM - 一种连接No SQL数据库的ORM。 - RBT

6
引入额外的类的主要好处是责任分离:
  • 表示层:显示信息和数据输入,包括为此进行的任何前处理和后处理,例如格式化。
  • 领域/业务/应用逻辑:在这里完成实际工作
  • 持久层:存储和检索数据
使用仅有的两个模型类来实现ViewModels和Domain Entities。不要使用与持久化实体类似的分离模型类来建模领域逻辑,而是将其实现在消耗领域实体的服务类中。领域实体最多应该具有处理其自身属性的逻辑(例如保持两个属性的组合值处于有效状态)。如果一个用例涉及到多个领域实体,请在服务类中建模此用例。如果将管理实体之间关系的逻辑直接放在实体类中,则代码会很快变得无法维护(相信我,我试过了)。
我不建议将可持续化实体用作ViewModels,因为这将混淆显示关注点(例如[DisplayName])和持久性关注点(例如[ForeignKey])。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接