【问题标题】:Repository pattern, POCO, ORM and intermediate entities存储库模式、POCO、ORM 和中间实体
【发布时间】:2012-08-31 14:03:59
【问题描述】:

我正在想办法解决这个问题:

我有 3 个具有多对多关系的表。

Users *-* Roles *-* Permissions

我使用 ORM 从他们那里获取数据。

我的业务层的一个方法必须根据权限返回用户,所以我用这个类返回对象:

public class UsersPerPermission
{
    public User[] {get;set;}
    public Permission {get;set;}
}

但是这个类没有映射到存储库中的任何表,它是我从现有表中生成的。这个班级应该住在哪里?

换句话说:

  • 我应该有一个IRepository.GetUsersPerPermission() 吗?然后该类应该存在于存储库中。

  • 或者我应该有一个IBusinessLayer.GetUsersPerPermission()?然后我必须调用存储库中的 CRUD 方法?

只将它放在业务层是有意义的,因为存储库应该只向表公开 CRUD 操作......但是,为了从业务层执行此操作,我必须执行几个独立的查询来获取数据并创建“UserPerPermission”类。另一方面,如果我将它放在存储库中,我可以使用分组一次性获取该信息。

谢谢!

PS:这个中间对象叫什么名字? “转换”?

【问题讨论】:

  • 为什么“我的业务层的方法必须根据权限返回用户”?
  • 因为有一些关于用户和权限管理的画面

标签: orm domain-driven-design repository-pattern ddd-repositories


【解决方案1】:

在 DDD 中,大多数实体和值对象应对应于已识别的域概念,这些概念是您的 ubiquitous language 的一部分。我通常会尽量限制多对多关系和人为关联对象。 Eric Evans 在他的书中描述了一些允许这样做的技术。当我必须创建一个关联对象时,它必须有一个关于域的有意义的名称,基本上我从不将它命名为Class1ToClass2。 在您的场景中,自从您的对象以来,它更加人为:

  • 对原始模型中已经存在(间接)的关联进行冗余建模。
  • 有一个不反映任何特定业务概念的名称。

请注意,如果我们在表示层或应用程序层中,这种对象不会毫无用处,因为它可以派上用场,因为它可以有一个包含屏幕上显示内容的结构 (DTO)。但是我这里说的是domain层,应该没有这种复合对象。

所以我一开始就不会创建UsersPerPermission 类。如果您想要的是用户列表并且 User 是聚合根,只需在 UserRepository 中创建一个 GetUsersByPermission() 方法。这并不意味着您也不能在 application 服务中使用 GetUsersByPermission() 方法,如果它与您的应用程序的用例相匹配(显示一个权限的详细信息的屏幕和拥有该权限的用户列表)。

【讨论】:

  • 问题是应用程序有一个屏幕,您可以在其中看到按权限分组的用户。按照你的方法,这是合理的,我必须调用'GetUsersPermissions',并且对于每个'Permission'调用'GetUsersByPermission',这意味着对数据库的大量查询。另一方面,有了这个人工聚合,我可以在一个查询中获得所有信息。
  • 您可以拥有多少权限?这么多,能不能一开始就和所有用户一起显示在屏幕上?由于(假设的)性能问题来自 UI,如果您单击特定的 Permission ,首先仅显示 Permissions 然后显示关联的用户,难道不能在 UI 中解决它吗?或者只是前几​​个 Permissions+Users 并在滚动上延迟加载以下几个? ...
  • 另外,您可能想看看这里给出的精彩答案:stackoverflow.com/questions/5477506/… 和这里:stackoverflow.com/questions/2098112/…
  • 如果您觉得这更像是一个报告屏幕而不是常规屏幕,那么您不必将查询逻辑放在域层中。它可以与主要的 DDD 架构一起存在于专用的报告模块中。见stackoverflow.com/questions/7831763/ddd-reporting-scenarios
  • +1。我同意这是用户存储库提供的东西。您通过创建域对象来加速查询,让持久性机制影响您的域模型。如果您需要解决多查询问题,请在单独的问题中提出,但不要更改域模型以满足数据库或用户界面等技术实现。
【解决方案2】:

我同意 guillaume31 的观点,即无需引入域对象“UsersPerPermission”来支持单个用例。

有两种方法可以使用现有的域类“用户”、“角色”和“权限”来实现您的用例。


解决方案一:

假设你有:权限 --> 角色 --> 用户

箭头表示可导航性。权限与角色列表相关联,角色与用户列表相关联。

我会在 Permission 类中添加一个方法 GetPermittedUsers() : List<User>,这很容易实现。

UI 逻辑将调用 PermissionRepository 的 GetPermissions(),然后在每个 Permission 上调用 GetPermittedUsers()。

我假设您使用像 hibernate(Nhibernate) 这样的 ORM 框架并正确定义了多对多关系。如果您从权限定义角色和用户的预加载,ORM 将生成一个查询,将权限、角色和用户表连接在一起并一次性加载所有内容。如果您为角色和用户定义延迟加载,您将在调用 PermissionRepository 时在一个查询中加载权限列表,然后在另一个查询中加载所有关联的角色和用户。一切都从数据库加载,最多三个查询。这称为 1+n 问题,大多数 ORM 都能正确处理。


解决方案二:

假设你有:用户 --> 角色 --> 权限

箭头表示可导航性。用户有一个角色列表。一个角色有一个权限列表。

我会将getUsersByPermissions(List<long> permissionIds) : List<Users> 添加到 UserRepository,并将 getPermissions() : List<Permission> 添加到 User 类。

UserRepository 的实现需要在一个查询中将 User、Role 和 Permission 表连接在一起,并一次性加载所有内容。同样,大多数 ORM 都会正确处理它。

一旦你有了一个用户列表,你就可以很容易地创建一个方法来构建一个Map<Permission, List<User>>


说实话,我很喜欢解决方案之一。我避免编写一个复杂的方法来将用户列表转换为权限和用户的映射,因此我不需要担心将这个方法放在哪里。但是,如果您已经具有另一个方向的导航能力,则解决方案可能会在 User、Role 和 Permission 类之间创建循环关系。有些人不喜欢循环关系。如果您的用户案例需要,我认为循环关系是可以接受的,甚至是必要的。

【讨论】:

    【解决方案3】:

    在类似的上下文中,我在域服务中使用了一个查询方法,该方法返回类似

    的内容
    IEnumerable<KeyValuePair<PermissionName, IEnumerable<Username>>>
    

    通过使用 KeyValuePair,我避免了用人为的概念(如 UsersPerPermition)污染域模型。此外,这样的结构是不可变的。 我没有在存储库上使用查询方法,因为在我的上下文中,没有实体与另一个实体耦合。因此,任何存储库都无关紧要。

    但是,当且仅当您正确建模实体的标识符(在您的示例中权限和用户都是实体)时,此解决方案对您的 GUI 有用。 实际上,如果它们是属于您的用户理解的普遍语言的shared identifiers,那么它们就足够了,无需进一步描述。

    否则,您只是在为您的 GUI 构建一个有用的 DTO。它不属于域,因此您应该使用最简单的可行方法(ADO.NET 查询?更简单的东西?)。 事实上,在我自己的场景中,GUI 和域都使用了这样的服务(GUI 显示了详细说明的预览)。

    一般来说,领域模型必须反映领域专家的语言,捕获与有界上下文相关的知识。其他一切都必须在域之外(但大部分时间都可以用域的值对象来表示)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-21
      • 2010-12-11
      • 2011-04-02
      相关资源
      最近更新 更多