4

这是我从这里提出的问题的后续行动。

首先,我没有在我的项目中使用DDD。

我有一个 3 层的 WCF 服务:

  • 服务层(仅持有操作和调用 BL 方法)
  • 包含所有业务逻辑类和方法的业务逻辑层
  • 保存 DbContext (LINQ-TO-EF) 和 POCO 实体的数据访问层

WCF 服务需要返回 DTO 对象,我无法确定放置将我的 POCO 实体转换为 DTO 的“转换器”类的最佳位置。

我有两个选择:

.

.

方法一

让业务逻辑方法将实体返回到服务层,并且服务层中有一个转换器类,用于将实体转换为 DTO。

优点:

  • 业务逻辑层做它必须做的事情——验证和 CRUD 操作
  • 业务逻辑层根本不需要了解 DTO

缺点:

  • 服务层现在必须包含对“数据访问层”程序集的引用,因为它从业务逻辑层接收实体。这似乎打破了 3 层的概念,即服务层只需要引用 BL 层,而 BL 层只需要引用 DAL。
  • 这是最糟糕的问题:翻译类需要从实体对象创建 DTO。因为它在处理 DbContext 后从 BL 接收实体对象,所以它无法访问未使用“包含”扩展加载的任何内容。这意味着 BL 方法需要将实体返回到服务层,以及翻译器创建 DTO 所需的一切。这是一个问题,因为它要求 BL 知道翻译者需要什么,其次 - 它会从数据库中获取大量不必要的数据!(也许翻译者需要返回一个“UserDto”对象,其中一个字段是“订单总数” - 为什么我想从数据库中获取所有订单只是为了制作一个“Count()”

.

.

方法B

将“转换器”类从实体对象转换为放置在“业务逻辑层”本身的 DTO。在这种机制中 - BL 方法已经返回 DTO。

优点:

  • BL 方法执行 BL 代码,然后调用“translate_to_dto”适当的消息将结果转换为返回的 DTO。这一切都在“DbContext”内部完成,这意味着当调用翻译器类翻译实体时,它仍然可以访问子对象,不需要调用“包含”。这意味着仅从数据库中获取创建 DTO 所需的数据。

缺点:

  • 现在“业务逻辑层”中的“翻译器”类是需要了解 DTO 的人,尽管服务层只负责了解它们!
  • BL 中的每个方法现在都执行纯 BL(有效性检查、CRUD 操作等),此外还调用翻译器方法以返回 DTO。这打破了“单一责任规则”,即方法(在 BL 中)应该只做一件特定的事情。

.

谁能告诉我执行“Entity ==> DTO”转换的正确位置在哪里?

.

[更新 - 添加示例]

业务逻辑层有一个名为UserManager的管理器类,它有一个像这样的 BL 方法:

public UserTasksDto     GetUserInfoWithTasks(Guid userId)
{
    if (userId == Guid.Empty)
        throw new ArgumentException("User ID cannot be empty");

    using (IMyDBEntities entities = _contextFactory.GetContext())
    {
        // Get POCO Object from DbContext
        User user = entities.Users.Find(userId);
        if (user == null)
            throw new EntityNotFoundException("User was not found in the database");

        if (user.Tasks.Count() == 0)
            throw new Exception("User does not have any tasks !");

        // Call 'Translator' static method to translate POCO to DTO
        Translator.TranslateUserToUserTasksDto(user);
    }
}

正如您在上面看到的 - BL 方法调用“翻译器”方法将 POCO 转换为 DTO。这是“实体”上下文中完成的,因此翻译者仍然可以访问用户的“任务”子项。

这是“翻译器”方法的样子:

class Translator
{
    public static UserTasksDto   TranslateUserToUserTasksDto ( User userPoco )
    {
        UserTasksDto dto = new UserTasksDto
        {
            UserId        = userPoco.Id,
            Username      = userPoco.Username,
            CreationDate  = userPoco.CreationDate,

            // Accessing a related entity, this is why this 'translate' method 
            // needs to be called inside the DbContext, otherwise it will except
            // (or we load all related entities using 'Include' just for the 'Count' purpose)
            Supervisor    = userPoco.Supervisor.Username,    
            NumOfTasks    = userPoco.Tasks.Count(),
            FirstTaskDate = userPoco.Tasks.OrderBy(task => task.Date).Take(1),
        }

        return dto;
    }
}

正如您在上面看到的 - 'Translate' 方法从 'User' POCO 对象'builds' 'UserTasksDto'。这是通过将“用户”对象中的一些字段及其相关实体映射到 DTO 来完成的。如果此方法不在BL 方法的 ObjectContext- 我会收到一个异常,说我正在尝试访问没有上下文的实体。

我希望我现在的问题更清楚...

4

2 回答 2

2

虽然有疑问,

如果您需要您的服务层独立于 BL/DAL 数据实体,那么在我看来,您必须为独立模型提供一个新的抽象层(dll 程序集)。

您的 BL 和/或 DAL 现在不会返回实体,而是来自这个新程序集的模型对象。

您的服务层不需要对实体的 DAL 的引用,而是对新模型程序集的引用。

这是与视图模型类似的模式。

现在服务层的工作是将模型转换为 DTO(如果它选择这样做)。

优点:将释放您对 BL/DAL 的依赖

缺点:模型抽象层看起来好像是多余的


编辑:

哦,我不是说要从业务层返回 DTO。我的意思是从业务逻辑中返回独立的模型,以便它们真正独立于所有层,并且之后与它们一起工作的任何层都可以使用它们或将它们转换为所需的任何形式。

例如,服务层可以包括模型到 DTO 翻译器,表示层可以将它们转换为视图模型,存储库可以选择将它们转换为 XML ......等等。现在每一层都有自己的模型到 X 转换器,BL 将只有单一职责,所有层都将独立于 DAL 实体。

注意:有些层可能会选择直接使用它们,我相信这是您的担忧,是的,如果它们这样做,它们将表现得好像 BL 返回了 DTO/VM,但这不是我的意图。

希望这现在可以解决问题...


更新:

想想你是一个开发人员,除了 BL 之外不会编写任何层。

你会返回 DAL 的实体吗?不。

您是否对如何使用您的库以及哪些层将使用它有任何概念。不。

您将在 DAL 实体之上创建一个新的抽象,并将它们返回到将使用您的库的 WHICHEVER 层。

因此,如果开发人员 X 出现并使用您的库创建 ACME WCF 服务,我很确定 X 不会使用您的模型对象作为他的 DTO,而是 X 将使用您的模型作为开始创建 DTO。

开发人员 Y 出现并使用您的库创建 ACME ASP.NET MVC 3 应用程序,我很确定 Y 不会将您的模型对象用作他的 VM(查看模型),而是 Y 将使用您的模型作为开始创建 VM。

于 2012-06-20T12:43:50.013 回答
0

可能没有合适的地方,但最合乎逻辑的地方是您的业务逻辑。

于 2012-06-20T12:16:03.623 回答