如何减少领域/实体/数据传输对象的重复?

23

我正在重新设计我的项目,旨在使其更易于维护,并尽最大努力遵循良好的设计实践。目前,我的解决方案包括一个Silverlight组件,用于托管SL应用程序的ASP.Net主机,还包含一个WCF RIA服务,以及一个共享类库,这样SL和WCF服务就可以共享对象。业务逻辑分散在各个地方,在我的WCF服务中手动编写所有CRUD操作。因此,我正在为所有内容创建一个新结构,并将这个混乱的结构移植到新格式中。在此过程中,我发现当我不知道是否应该这样做时,我正在复制类。

我的新结构如下:

客户端:
Reporting.Silverlight = Silverlight应用程序。这将引用我的DTO类。
Reporting.Web = 包含我的SL应用程序,是人们进入应用程序的主要入口点。

业务:
Reporting.Services = 我的WCF服务位于此处。我的SL应用程序将调用此处的服务进行操作,而这些服务将返回DTO类。
Reporting.Services.Contracts = 包含我的WCF服务接口,并包含带有DataContract装饰器的DTO类。
Reporting.Domain = 包含我的领域对象和业务逻辑。

数据:
Reporting.Data.Contract = 包含我的存储库和工作单元接口。
Reporting.Data = 存储库/工作单元的具体实现。在此定义Entity Framework 5上下文。
Reporting.Data.Models = 包含所有我的实体对象,以便EF5可以使用SQL操作。

我有3个地方几乎具有完全相同的类,对我来说这很可疑。在Reporting.Services.Contracts中,我有一个DTO被传递给SL客户端。以下是其中一个示例:

[DataContract(Name = "ComputerDTO")]
public class ComputerDTO
{
    [DataMember(Name = "Hostname")]
    public string Hostname { get; set; }

    [DataMember(Name = "ServiceTag")]
    public string ServiceTag { get; set; }

  // ... lots more
}

我认为上面的DTO很好,因为它只是一组传递给SL客户端的属性。我的大部分DTO属性都与实体对象的属性1:1映射,除了ID字段。以下是与上述DTO对应的实体对象:

[Table("Inventory_Base")]
public class ComputerEntity
{
    // primary key
    [Key]
    public int AssetID { get; set; }

    // foreign keys
    public int? ClientID { get; set; }

    // these props are fine without custom mapping
    public string Hostname { get; set; }
    public string ServiceTag { get; set; }
    // ... plus a bunch more in addition to my navigation properties
}

我正在使用EF5的代码优先方法。 我仍处于重新编写的初始阶段,到目前为止,我的印象是业务逻辑不应该在EF实体内部。 DTO也不应该有业务逻辑。 那么这意味着它应该放进我的领域模型中,对吗?好吧,那就在Reporting.Domain中再创建一个几乎相同的第三个类。

public class Computer
{
   public string Hostname { get; set; }
   public string ServiceTag { get; set; }
   // ... lots more, pretty much mirrors the DTO

   public string Method1(string param1)
   {
       // lots of methods and logic go in here
   }
}
有3个几乎完全相同的类肯定不是正确的方法,对吗?我应该将所有业务逻辑放在EF实体内,然后将结果投影到传递到DTO中吗?如果将所有域/业务逻辑都塞进EF实体类中是一个好主意,那么结构上我应该将该程序集移动到我的业务层并放在数据层之外,即使这些对象是保存到数据库中的对象?理想情况下,我正在尝试将任何与Entity Framework有关的引用限制在我的数据项目内,并且在我的业务项目之外。我有大约200个类要迁移,包括我的领域,并且一旦完成重写,我期望这个东西会扩展到更多功能。如何组织这个东西并保持DRY,如果你有任何见解,将不胜感激。
如果有助于更好地定义我正在尝试做什么,请让我知道我正在遵循的存储库/单元工作方法论。

嗨Gert - 我很喜欢你在那个链接问题中的答案,它为我解决了很多问题。 我的理解是,如果Entity对象相同且实用,将其用作域对象是可以接受的。 但是,我预计稍后添加到我的域类将与我的实体对象不同。 在这种情况下,我是否仍需要在DTO / Domain / Entity-for-my-ORM之间复制我的类? - Bill Sambrone
DTO/Domain/Entity解决方案旨在为不同的关注点构建独立的模型。当持久化基础设施妨碍您构建精细的领域模型时,实体才有意义。如果您的领域不是那么复杂,您可以使用实体作为领域模型。同样,也适用于DTO。 - Yugang Zhou
@BillSambrone 对于领域的某些部分,可能需要创建专用领域类来编写简洁的业务逻辑,因为实体模型结构会妨碍编程。这些类在实体模型中没有直接对应物,因此不再是重复(或复制)。这里更适合使用“映射”一词。对于DTOs也是如此。当UI层需要一个在实体模型中没有匹配项的扁平/聚合数据结构时,我经常使用DTOs。如果要将表示层与数据/业务逻辑解耦,则还必须使用DTOs。 - Gert Arnold
1个回答

28
拥有三个几乎相同的类肯定不是正确的方式,对吧?
在我看来,它们并不是“三个几乎相同的类”,它们不具备相同的功能。它们是同一领域概念的多个方面,每一个都为特定层的需求量身定制。
这是模块化和关注点清晰分离所要付出的代价。你拥有的门(如六边形架构中的“端口和适配器”)越多,你就需要更多的方面,也就需要更多的映射。
这是一篇好文章:http://blog.ploeh.dk/2012/02/09/IsLayeringWorththeMapping/

这是一篇不错的文章。我的收获是,如果我想保持这个东西的分层结构,我确实需要有我的DTO类、Domain类和Entity类。它们看起来很相似,但都“负责”不同的事情。你有没有推荐一个好的映射框架,可以减少我需要手动映射的工作量? - Bill Sambrone
2
我只使用过AutoMapper(https://github.com/AutoMapper/AutoMapper),它对我来说非常好用。 - guillaume31

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