领域模型之间数据映射的模式

3

最近我需要做的一件常见事情是寻找任何常见模式,使其更容易一些。主要思路是我有一些数据模型,它们被建模以满足ORM并纯粹执行CRUD操作以获取对象。这些模型目前通过存储库/工厂进行公开(取决于是否为C或RUD)。然后我有一个视图模型,它更易读,并且具有UI问题,例如验证和在视图之间映射数据(这是一个ASP.MVC场景,但这种情况可以抽象到大多数情况中)。因此,假设我转到localhost/user/1,则应该从数据库中获取ID为1的用户,然后将其显示在UI上。最终,这必须从数据域中提取数据,然后将其映射到用于显示的UI模型。

以下是一个示例场景:

public class OrmUser
{
    public int Id {get;set;}
    public string Name {get;set;}
    public IList<Permission> Permissions {get;set;}
}

public class UiUser
{
    [Required]
    public int Id {get;set;}
    [Required]
    public string Name {get;set;}
    public bool IsUserAdmin {get;set;}
}

public class UserMapper : IMapper<UiUser>
{
    public UiUser Get(int id)
    {
        var ormUser = UserRepository.Get(id);
        var uiUser = new UiUser
        {
            Id = ormUser.Id,
            Name = ormUser.Name,
            IsUserAdmin = IsUserAdmin(ormUser.Permissions)
        }
    }

    private bool IsUserAdmin(IList<Permission> permissions)
    {
        return permissions.SomeLinq(ToFindIfTheyAreAnAdmin);
    }
}

这是一个简单的例子,展示了数据模型如何包含许多相同类型的信息,但在当前视图下您并不关心所有信息,只需要其中的一部分。使用映射器,您可以抽象出映射以及与数据域的通信,但是您需要为每种类型编写一个映射器类,并且上面的假设是单向映射,而不是双向映射,后者需要更多的代码。
那么你们都是如何进行这种映射的呢?目前我只编写了抽象映射器,基本上允许UI层运行查询并获取一个视图模型,抽象出了存储库和从一个模型复制数据到另一个模型的过程,但感觉应该有更好的方法来完成这个任务。

值得注意的是,尽管这是一个 .net 的例子,但同样的情况也可能发生在任何语言中。因此,任何与语言无关的模式都是理想的,尽管为特定平台提供便利的框架也很好。 - Grofit
考虑到Automapper可能是一个不错的选择。 - sll
@Arnis L. 逻辑上将类和模型分组,只涉及处理数据源,例如 Nhibernate、MongoDb 等。因为您不会使用相同的模型在 Nhibernate 中持久化实体,也不会将它们显示到 UI 上,因此需要进行数据/UI 领域区分。 - Grofit
@Grofit 是的,但如果我们同时谈论领域驱动设计,那就高度不协调了。然后只有一个领域 - 我们客户所谈论的东西。 - Arnis Lapsa
是的,你说得对,我应该使用“层”而不是“层级”,但你只是在纠结细节,很明显这里的问题是什么。 - Grofit
显示剩余2条评论
3个回答

5
在 .net 中,你可能需要看一下 Automapper。
它基本上允许你进行以下操作: https://github.com/AutoMapper/AutoMapper
CreateMap<Domain.Customer, ViewModel.Customer>()

然后你可以通过以下方式在两者之间进行映射:

var vmCustomer = Mapper.Map<Domain.Customer, ViewModel.Customer>(domainCustomer);

它可能会为您节省大量的样板代码。


2
是的。AutoMapper 是适合您的工具。有很多关于它的文章,但最好去看看源头 - Jimmy Bogard。他是AutoMapper的创始人。

看看这篇博客文章http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/

现在的 AutoMapper 支持 MVC,而且非常高效,您可以使用属性来指定映射应该如何进行。

/ 祝好 Magnus


在两侧添加属性会使事情变得更加混乱,我正在尝试为两侧提出清晰的抽象。虽然对于.NET而言,AutoMapper似乎是最佳选择,但我希望有更多无关语言的解决方案,不过我猜AutoMapper和ValueInjector等背后的原则可以应用于其他语言/平台。 - Grofit
1
我还没有使用Jimmys最新的MVC插件,所以你可以使用属性。是的,这会有点混乱。这也取决于您的领域模型与视图模型的相似程度。如果您需要帮助AutoMapper完成大量工作,最好跳过属性并像正常情况下一样使用AutoMapper(映射配置文件)。但对我来说,映射是横切关注点,因此我将映射移出到一个单独的项目中,并将其包装成Facade(IAssamble)。然后,您可以在需要它的地方注入该IAssemble服务接口。如果您想要替换AutoMapper-那就替换吧。但是IAssemble接口仍然可以保持不变。 - Magnus Backeus

1
在这种情况下,我可能会在我的演示层为用户编写一个扩展方法。
public static class UserPresentationExtensions{
 public bool IsAdmin(this User user){
  return permissions.SomeLinq(ToFindIfTheyAreAnAdmin);
 }
}

视图将会长成这个样子:

@model User
<fieldset>
 <legend>User: @Model.Name</legend>
 @if(user.IsAdmin()){
  User is an admin
 }
</fieldset>

为了避免重复导入命名空间,可以使用 web.config 文件:
<configuration>
 <system.web.webPages.razor>
  <pages pageBaseType="System.Web.Mvc.WebViewPage">
   <namespaces>
    <add namespace="MyProject.PresentationExtensions" />
   ...

你说得有道理,但是假设你想要为你的模型添加验证,所以你决定在你的名称属性上添加一个 [Required] 属性。然而,如果你使用共享模型,那么你的数据层就需要知道这些注释,如果你的数据层必须有一些属性,那么你的 UI 层也需要知道它们。在最简单的情况下,你是对的,有时候只需要一个模型就可以了,只是以不同的方式访问它,但是对于大多数复杂的项目,你会发现自己需要在两边都进行修改,试图节省几行代码。
我说的是 - 在这种特殊情况下。这种方法仅适用于映射不是很重要且只有单向通信(你只需要呈现它)的情况。
当涉及到接收帖子时,情况就有点不同了。最常见的“一刀切”方法是应用所谓的 Thunderdome principle。即:
所有控制器方法都接受一个 ViewModel 对象(或在某些情况下为零个对象),并返回一个单独的 ViewModel 对象(一个对象进入,一个对象离开)。

然而,我经常更喜欢使用扩展/HTML助手的方式,只需将相应的参数传递给操作,就像这样:

public void BatheCat(int id /* cat id */, int bathId, string shampoo){
 ...
}

如果参数计数失控(当它小于等于3时我不会烦恼),我只是将它们封装起来(这里有一个来自我的项目的例子)。

这种方式需要为几乎每个要映射的模型或字段编写扩展方法或自定义逻辑。虽然这是一个有效的解决方案,但我认为当您开始在不同领域拥有10个以上的模型时,它不会导致可管理的代码库。 - Grofit
@Grofit 有时候,这比映射与领域模型相同的视图模型要简单得多。这就是我的观点。 - Arnis Lapsa
你说得没错,但是假设你想要为你的模型添加验证,所以你决定在你的Name属性上添加一个[Required]属性。然而,如果你使用了共享模型,那么你的数据层就需要知道这些注释,如果你的数据层必须有一些属性,那么你的UI层也需要知道它们。在最简单的情况下,你是正确的,有时候只需要一个模型就可以了,不同的方式访问它,但对于大多数复杂的项目,你会发现自己需要修改两边的代码来尝试节省几行代码。 - Grofit

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