处理依赖于用户的应用程序

时间:2017-07-25 14:18:20

标签: domain-driven-design

我目前正在编写的应用程序在很大程度上依赖于当前登录的用户,为了给出一个具体示例,我们假设我们有一个产品列表。

现在每个用户都拥有查看某些产品的“权利”,此产品的特定详细信息,以及编辑/删除更少的产品。

E.g:

  • 用户可以看到3/5产品
  • 用户可以从这3种产品中的2种中看到更多详细信息
  • ...

由于大多数应用程序的域都是这种情况,我倾向于在大多数方法中传递用户。这不时变得麻烦。因为我必须在某些方法中传递用户,只需将其传递给另一个需要它的人。

我的直觉告诉我,我错过了一些东西,但我不确定如何解决这个问题。

我对使用拥有该用户的类进行了一些思考,并在我需要的任何地方注入该类。或使用静态属性。

现在有时候方法中的用户很方便,我想我可以覆盖它:

public doSomething(User user = null)
{
    var u = user ?? this.authService.User;
    ...
}

还有其他方法可以解决这类问题吗?

2 个答案:

答案 0 :(得分:2)

这取决于您在项目中的进展情况。在某些情况下,您可能没有余地来改变这一点,但如果您有更多控制权或正在开始,那么您可以选择。

通常身份&访问控制本身就是一个有限的上下文。身份验证和授权不应位于您的核心域中。您的核心域(甚至是子域)有兴趣执行他们的操作如果您有权访问,但确定该访问权不是域的责任。

授权应在域外进行。如果您发现自己正在查询域名,那么事情可能需要更改,因为您需要一个可能会应用授权的专用查询层。任何受限制的命令都将在集成/应用程序层应用授权。我们是否想要限制用户注册新订单甚至某种类型的新订单都不应该对i.t.o真的重要。域,因为它只是更改的粒度。

您可能拥有一个子域,用于处理您的域特有的授权以及身份&访问控制更正交的通用子域。

但是,您可能处于数据元素授权(分类级别)与结构之间存在令人不安的高度耦合的情况。我认为流体分类应远离一种结构,因为分类变化的影响太大。

只是一些想法:)

答案 1 :(得分:1)

你的直觉是正确的,请继续听。

授权检查不应与核心域检查混合使用。例如,检查用户可能更新产品详细信息的if和检查产品详细信息足够长的if不应包含在同一个类甚至相同的有界上下文中。如果你有一个monolith,那么这两个检查应该包含在不同的命名空间/模块中。

现在我会告诉你我是怎么做到的。在我最新的单片项目中,我使用了很多CQRS,我喜欢命令和查询之间的分离。我将举一个命令验证的例子,但这可以扩展到查询验证甚至非CQRS架构。

对于每个命令,我注册零个或多个命令验证器,检查命令是否可以发送到aggregate。这些验证器最终是一致的。如果命令通过了所有验证器,那么命令将被发送到aggregate,在那里进一步检查它,但是以一致的方式。因此,我们讨论的是两种验证:聚合之外的验证和聚合内部的验证。属于其他有界上下文的检查可以使用聚合外部的命令验证器来实现,这就是我的工作方式。现在一些示例源代码,在PHP中:

<?php
namespace CoreDomain {
    class ProductAggregate
    {
        public function handle(ChangeProductDetails $command):void //no return value
        {
            //this check is strong consistent
            //the method yields zero or more events or exception in case of failure
            if (strlen($command->getProductDetails()) < 10) {
                throw new \Exception("Product details must be at least 10 characters long");
            }

            yield new ProductDetailsWereChanged($command->getProductId(), $command->getProductDetails());
        }
    }
}

namespace Authorization {
    class UserCanChangeProductDetailsValidator
    {
        private $authenticationReaderService;
        private $productsPermissionsService;

        public function validate(ChangeProductDetails $command): void //no return value, if all is good no exception are thrown
        {
            //this check is eventual consistent
            if (!$this->productsPermissionsService->canUserChangeProductDetails($this->authenticationReaderService->getAuthenticatedUserId(), $command->getProductId())) {
                throw new \Exception("User may not change product details");
            }
        }
    }
}

此示例使用将命令直接发送到聚合的样式,但您也应将此模式应用于其他样式。为简洁起见,不包括命令验证器注册的详细信息。