在 DDD 中在哪里实现聚合级别权限?[关闭]

Where to implement aggregate level permissions in DDD? [closed]

提问人:jimmy_terra 提问时间:8/9/2018 最后编辑:TylerHjimmy_terra 更新时间:11/7/2018 访问量:3128

问:


想改进这个问题吗?更新问题,以便可以通过编辑这篇文章用事实和引文来回答。

5年前关闭。

假设我有一个聚合类型 ,其中包含 。根据订单的状态和对订单进行操作的用户角色,可能允许或不允许操作。OrderOrderItems

例如,如果用户角色是 ,订单的状态是 ,则允许添加和删除项目。相反,如果订单的状态为“则不允许添加和删除项目”。但是,如果用户角色是,并且订单的状态是,则允许添加和删除项目。CustomerOpenProcessingManagerProcessing

我的问题是:

  • 该类型是否应该处理这些类型的权限?也就是说,它封装了关于哪些角色可以做什么的所有逻辑,因此依赖于用户角色。Order
  • 或者,这是否应该在权限服务中的类型之外进行处理,该服务将接受角色、操作和操作的主题(订单实例)并确定是否允许该操作?即,逻辑被外部化并假定在操作执行之前进行验证,该操作不了解用户角色的概念。OrderOrder

(注意:实际用例要复杂得多,需要大量角色、状态和操作。授权发生在外层,并且已经应用了 - 这个问题是关于实例特定的权限。换句话说,客户有权访问“AddItemToOrder”API 端点,但根据订单的具体状态,实际操作可能会被允许,也可能不允许。

模式 语言无关的 领域驱动设计

评论

0赞 plalx 8/11/2018
在应用程序服务中使用类似的东西似乎很简单吗?但是,如果沿着这条路走下去,大多数业务逻辑最终都会被淹没在授权规则中。我最有可能在这里对 2 操作进行建模,因为在处理订单后更改项目与在订单尚未提交时更改项目是不同的业务流程。@guillaume31答案是去IMO的方式。另一个想法可能是 和 .orderAuthorizationService.assertCanChangeItems(user, order)addItem(Customer customer, ...)addItem(Manager manager, ...)

答:

3赞 Robert Bräutigam 8/9/2018 #1

在我正在使用的当前系统中,我通过在我的域对象上创建覆盖层来解决这个问题。我的大多数领域对象都是从接口开始的,我用覆盖层构建不同的功能。

例如:

// Business-relevant features
public interface Order {
    ...
    void removeItem(Sku sku); // Or whatever
}

// Implementation  directly in the database, side-steps
// ORM problems, here off-topic, but belongs to example
public final class DatabaseOrder implements Order {
    ...
    public DatabaseOrder(Connection connection) {
        this.connection = connection;
    }

    @Override
    public void removeItem(Sku sku) {
        connection.update(...);
    }
}

// This is the "authorization"
public final class AuthorizingOrder implements Order {
    private final Order delegate;
    private final User user;

    public AuthorizingOrder(User user, Order delegate) {
        this.user = user;
        this.delegate = delegate;
    }
    ...
    @Override
    public void removeItem(Sku sku) {
        if (user.isManager()) {
            delegate.removeItem(sku);
        } else {
            ...
        }
    }
}

当然,这是一个简单的例子,但你明白了。在此解决方案中,没有外部的非域对象,如服务。一切都与无处不在的语言联系在一起。

有些工厂或建筑商可以将这些东西连接在一起,因此最终系统可以使用具有所有必要功能的对象,即使它们净地分开。Order

评论

0赞 jimmy_terra 8/11/2018
添加装饰器是一个非常干净的解决方案。从示例中可以看出,您没有使用存储库?您将如何在存储库中来回传递域对象?我很想只返回真正的聚合,然后在我需要确保访问控制的地方显式覆盖它,然后在执行任何操作后将委托传回存储库进行保存。
0赞 Robert Bräutigam 8/11/2018
我主要避免使用任何类型的“存储库”,因为它们纯粹是技术性的。我通常将它放在与业务相关的对象中。例如,没有 或 ,只有返回 .您以后无法装饰/覆盖对象,因为您以后没有必要的信息来执行此操作。你必须从你覆盖的对象中“获取”它,这将破坏拥有面向业务的界面的目的。getUser()loadUser()login(...)User
11赞 guillaume31 8/9/2018 #2

我更愿意将访问控制视为应用逻辑而不是域逻辑,因为并非所有域操作调用都来自人类用户并且需要访问控制。将权限放在单独的横切层或不同的边界上下文中也有助于关注点分离。

在您的示例中,我将尝试为客户和经理采取的操作提供不同的域方法名称。丰富无处不在的语言可能是一个很好的仲裁者,当你在为相似但略有不同的概念而苦苦挣扎时。

评论

0赞 jimmy_terra 8/11/2018
你对聚合上的不同操作模式提出了一个很好的观点,我同意访问控制应该放在外面。对我来说,一个令人烦恼的疑问是,是否允许一个操作部分取决于聚合的内部状态,而暴露该状态并在外部做出决策感觉就像是封装被破坏了。
0赞 guillaume31 8/11/2018
我的第二点恰恰是,没有有条件允许的“操作”,而是两个操作——例如,(在未结订单上)和(在处理订单上):)这样,您可以更精细地控制访问。AddItems()FixOrder()
1赞 jimmy_terra 8/11/2018
是的,我明白了,但是在给定状态和角色数量的情况下,如果存在大量可能的组合矩阵,这是行不通的。将其编码到方法本身中是不切实际的,也没有任何领域语言可以充分描述它们。
0赞 techagrammer 8/17/2018
我完全同意@guillaume31.此外,在企业应用程序中,我们还创建了单独的 API,并将这些 API 的权限分配给每个角色。我们还有一件事是创建一个具有特定权限的用户组,并在本次和最终的权限检查中分配用户,看看用户是否可以执行分配用户的 api 或任何用户组
2赞 Tengiz 8/17/2018 #3

我有点觉得你依赖于技术细节,而不是无处不在的语言。无处不在的语言已经告诉你该怎么做了。但我假设你还没有这样的语言,或者至少你还没有通过它来锤击用户权限。

这是我的看法,假设你在问问题时使用了无处不在的语言。让我们看看它会把我们带到哪里。

Given current User has Customer role
And Order status is Open and it has 1 Item
When User adds an Item to Order
Then Order should have 2 items

以下是我如何对这种语言进行建模(作为表现力的单元测试):

//given.
var user = new User(role: new CustomerRole());
var order = new Order(status: OrderStatus.Open, items: new [] { new OrderItem(name: "Item 1", price: 1.00m });

//when.
order.AddItem(byUser: user, item: new OrderItem(name: "Item 2", price: 2.00m));

//then.
Assert.Equal(2, order.Items.Count);

到目前为止,正如我们所看到的,答案就在方法内部的某个地方。最有可能的是,在它里面,你会有一个富有表现力的代码,如下所示:Order.AddItem

public void AddItem(User user, orderItem item)
{
    if (user.Role.CanAddOrderItem() && this.Status.IsEqualTo(OrderStatus.Open))
    {
        this.items.Add(item);
    }
}

现在我们可以看到它是由 和 决定的。同样,假设这就是你无处不在的语言听起来的样子。OrderRole

我希望这会有所帮助!

评论

0赞 jimmy_terra 9/5/2018
感谢您的回复。这是一个选项,我对使用它持谨慎态度的原因是因为 Order 类型现在依赖于 User,而且我们在添加项目时总是必须提供 User。情况可能并非总是如此。
0赞 Tengiz 9/5/2018
我理解你的担忧。
0赞 Tengiz 9/5/2018
但是,我更愿意将依赖关系视为关联。如果订单和用户之间存在关联,则此类代码也没有任何错误。此外,这可能不是用户,而是订单所依赖的特定类型的用户。我建议定义有界上下文,并了解用户最终是否与订单处于相同的有界上下文中。这应该会更加清晰。
0赞 kravemir 10/17/2019
对于测试,我也会涵盖不愉快的场景,并确保它不是任何其他副作用,这会使不愉快的场景失败。即。 和。。对于没有角色的方案也是如此,..或者,扮演不同的角色,等等,..given current user has customer role, and order is open; when user adds *valid item* to order; then order has got one more itemgiven current user has customer role, and order is closed; when user adds *valid item* to order; then exception is thrown