存储库模式,POCO,ORM和中间实体

时间:2012-08-31 14:03:59

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

我想弄清楚如何解决这个问题:

我有3张表与多对多关系。

Users *-* Roles *-* Permissions

我使用ORM从中获取数据。

我的业务层的方法必须按权限返回用户,因此我返回具有此类的对象:

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

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

换句话说:

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

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

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

谢谢!

PS:这个中间对象的名称是什么? '转变'?

3 个答案:

答案 0 :(得分:4)

在DDD中,大多数实体和值对象应与您ubiquitous language中已识别的域概念相对应。我通常尽可能地限制多对多关系和人工关联对象。 Eric Evans在他的书中描述了一些允许这种情况的技巧。当我必须创建一个关联对象时,它必须具有关于域的有意义的名称,基本上我永远不会命名它Class1ToClass2。 在你的场景中,它比你的对象更加人为:

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

请注意,如果我们在演示文稿或应用程序层中,这种对象将毫无用处,因为它可以派生出一个包含屏幕上显示的内容(DTO)的结构。但我在这里谈论层,它应该没有这样的复合对象。

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

答案 1 :(得分:0)

我同意guillaume31,不需要引入域对象“UsersPerPermission”来支持单个用例。

使用现有域类“User”,“Role”和“Permission”可以通过两种方式实现用例。


解决方案一:

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

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

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

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

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


解决方案二:

假设你有:用户 - &gt;角色 - &gt;许可

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

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

UserRepository的实现需要在单个查询中将用户,角色和权限表连接在一起,并一次性加载所有内容。同样,大多数ORM都会正确处理它。

获得用户列表后,您可以非常轻松地创建一种方法来构建Map<Permission, List<User>>


说实话,我很喜欢解决方案。我避免编写一个复杂的方法来将用户列表转换为权限和用户的映射,因此我不需要担心放置此方法的位置。但是,如果您已经在另一个方向上具有导航性,则解决方案可以在用户,角色和权限类之间创建循环关系。有些人不喜欢循环关系。我认为如果用户需要它,有时候循环关系是可以接受的。

答案 2 :(得分:0)

在类似的上下文中,我在域服务中使用了一种返回类似

的查询方法
IEnumerable<KeyValuePair<PermissionName, IEnumerable<Username>>>

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

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

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

通常,域模型必须反映域专家的语言,捕获与有界上下文相关的知识。其他所有内容都必须在域外(但大部分时间都可以用域的值对象表示)。