当前位置: 首页 > 知识库问答 >
问题:

事件源/ CQRS方法在api平台上的实现

年高洁
2023-03-14

在官方的Api平台网站上有一个通用设计考虑页面。

最后但同样重要的是,要创建基于事件源的系统,一种方便的方法是:

  • 使用自定义数据持久化器将数据持久化到事件存储区中
  • 在标准RDBMS(Postgres,MariaDB...)表或视图中创建投影
  • 用只读的 Doctrine 实体类映射这些投影,并用 @ApiResource 标记这些类

然后,您可以从API平台提供的内置Doctrine过滤器、排序、分页、自动加入等中受益。

因此,我尝试用一种简化来实现这种方法(使用一个DB,但读写分离)。

但失败了...有一个问题,我不知道如何解决,所以请你帮忙!

我创建了一个userDoctrine实体和我想使用@Seri照相机\组({"读取"})公开的注释字段。我将在这里省略它,因为它非常通用。

api平台的yaml格式的用户资源:

# config/api_platform/entities/user.yaml

App\Entity\User\User:
    attributes:
        normalization_context:
            groups: ["Read"]
    itemOperations:
        get: ~
    collectionOperations:
        get:
            access_control: "is_granted('ROLE_ADMIN')"

因此,如上所示,UserDoctrine实体是只读的,因为只定义了GET方法。

然后我创建了一个CreateUserDTO:

# src/Dto/User/CreateUser.php

namespace App\Dto\User;

use App\Validator as AppAssert;
use Symfony\Component\Validator\Constraints as Assert;

final class CreateUser
{
    /**
     * @var string
     * @Assert\NotBlank()
     * @Assert\Email()
     * @AppAssert\FakeEmailChecker()
     */
    public $email;
    /**
     * @var string
     * @Assert\NotBlank()
     * @AppAssert\PlainPassword()
     */
    public $plainPassword;
}

创建用户api平台的yaml格式资源:

# config/api_platform/dtos/create_user.yaml

App\Dto\User\CreateUser:
    itemOperations: {}
    collectionOperations:
        post:
            access_control: "is_anonymous()"
            path: "/users"
            swagger_context:
                tags: ["User"]
                summary: "Create new User resource"

因此,在这里您可以看到只定义了一个 POST 方法,正是为了创建新用户。

这里路由器显示:

$ bin/console debug:router
---------------------------------- -------- -------- ------ -----------------------
Name                               Method   Scheme   Host   Path
---------------------------------- -------- -------- ------ -----------------------
api_create_users_post_collection   POST     ANY      ANY    /users
api_users_get_collection           GET      ANY      ANY    /users.{_format}
api_users_get_item                 GET      ANY      ANY    /users/{id}.{_format}

我还添加了一个定制的< code>DataPersister来处理< code>POST到< code>/users的操作。在< code > createuserdatapopersister::persist 中,我使用了原则实体来写入数据,但对于这种情况,这并不重要,因为Api-platform不知道DataPersister将如何写入数据。因此,从概念上来说,这是读和写的分离。

读取由Api平台附带的Doctrine的DataProvider执行,写入由自定义DataPersister执行。

# src/DataPersister/CreateUserDataPersister.php

namespace App\DataPersister;

use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Dto\User\CreateUser;
use App\Entity\User\User;
use Doctrine\ORM\EntityManagerInterface;

class CreateUserDataPersister implements DataPersisterInterface
{
    private $manager;

    public function __construct(EntityManagerInterface $manager)
    {
        $this->manager = $manager;
    }

    public function supports($data): bool
    {
        return $data instanceof CreateUser;
    }

    public function persist($data)
    {
        $user = new User();
        $user
            ->setEmail($data->email)
            ->setPlainPassword($data->plainPassword);

        $this->manager->persist($user);
        $this->flush();

        return $user;
    }

    public function remove($data)
    {

    }
}

当我执行创建新用户的请求时:

POST https://{{host}}/users
Content-Type: application/json

{
  "email": "test@custom.domain",
  "plainPassword": "123qweQWE"
}

问题!我得到一个< code>400响应< code >..." hydra:description ":"没有与类型" App\Dto\User\CreateUser "关联的项目路由,"...

但是,数据库中添加了一条新记录,因此自定义DataPersie可以工作;)

根据一般设计注意事项,已实现写入和读取分离,但未按预期工作。

我很确定,我可能缺少一些要配置或实现的东西。所以,这就是它不起作用的原因。

很乐意得到任何帮助!

更新 1:

问题出在\Api平台\Core\Bridge\Symfony\Routing\RouteNameResolver::getRouteName()。在第48-59行,它遍历所有路由,试图为以下内容找到合适的路由:

  • $operationType = “项目”
  • $resourceClass = 'App\Dto\User\CreateUser'

但是$操作类型='项目'仅为$resourceclass='应用\实体\用户\用户'定义,因此它无法找到路由并引发异常。

更新2:

所以,这个问题听起来可能是这样的:

如何实现读写分离(CQS?)使用Doctrine实体进行读取,使用DTO进行写入,两者都位于同一路由上,但使用不同的方法?

更新3:

数据持有者

    < li >将数据存储到其他持久层(ElasticSearch、MongoDB、外部web服务...) < li >不要通过API公开暴露与数据库映射的内部模型 < li >通过实现CQRS等模式,对读取操作和更新使用单独的模型

是的!我想要那个...但是如何在我的例子中实现它?

共有3个答案

洪梓
2023-03-14

仅适用于 2.4 版本,但真的很有帮助。

只需为 CreateUserDTO 添加 output_class=false,POST 就会一切正常|噗|补丁

output_class设置为false允许您绕过获取项目操作。你可以在API platform \ Core \ event listener # L68中看到。

松博耘
2023-03-14

你需要在回答中发送“id”。

如果用户是原则实体,请使用:

/**
 * @ORM\Id()
 * @ORM\GeneratedValue()
 * @ORM\Column(type="integer")
 */
private $id;

如果用户不是Doctrine实体,请使用:

/**
 * @Assert\Type(type="integer")
 * @ApiProperty(identifier=true)
 */
private $id;

无论如何,你的答案是这样的:

{
  "id": 1, // Your unique id of User
  "email": "test@custom.domain",
  "plainPassword": "123qweQWE"
}

附:抱歉我的英语:)

谢奇略
2023-03-14

问题是Dto\User\CreateUser对象正在为响应序列化,而实际上,您确实希望返回并序列化Entity\User。

当API平台序列化一个资源时,它们会为该资源生成一个IRI。IRI一代是代码呕吐的地方。默认的IRI生成器使用Symfony路由器根据API平台创建的API路由来实际构建路由。

因此,为了在实体上生成IRI,它需要定义一个GET项操作,因为这是将成为资源IRI的路由。

在您的情况下,DTO 没有 GET 项操作(也不应该有),但是当 API 平台尝试序列化您的 DTO 时,它会引发该错误。

从您的代码示例来看,似乎正在返回User,但是,从错误中可以清楚地看出,User实体不是正在序列化的实体。

要做的一件事是安装调试包,使用bin/console-server:dump启动转储服务器,然后在API Platform WriteListener:ApiPlatform\Core\EventListener\WriteListener的第53行附近添加一些转储语句:

dump(["Controller Result: ", $controllerResult]);
$persistResult = $this->dataPersister->persist($controllerResult);
dump(["Persist Result: ", $persistResult]);

控制器结果应该是你的DTO的一个实例,持久结果应该是你的用户实体的一个实例,但是我猜它返回的是你的DTO。

如果它返回您的 DTO,您只需调试并找出为什么从数据中返回 DTO-

希望这有帮助!

 类似资料:
  • 我在CQRS/ES设计中有一个计时案例。为了便于讨论,让我们以Microsoft关于这个主题的示例会议管理为基础(https://msdn.microsoft.com/en-us/library/jj554200.aspx)。 假设在第1分钟创建会议(最大座位数为20)。 在第4分钟,事件到达order mgmt上下文,因此创建了一个座位可用性。 在第7分钟,用户下了一个订单(通过订单管理),购买

  • 很明显,基于这些模式的系统是易于扩展的。但我想问你,具体怎么做?关于可伸缩性,我没有什么问题: 如何缩放聚合体?如果我将创建

  • 我想创建一个CQRS和事件源架构,非常便宜,非常灵活,非常简单。 我想确保事件永远不会失败,至少到达发布者/事件存储,永远,因为这是业务所在。 天蓝 有了azure,我似乎不知道该用什么。 Azure服务总线 蔚蓝函数 Azure webjob(我想这可以用Azure函数代替) ??(还有什么我忘了或者不知道的?) null 你的经验说明了什么? 其他替代方案呢?(例如:)?

  • 我有一个在AWS Lambda上运行的基于微服务的应用程序。其中两个微服务,最关键的,使用事件源/cqrs。 背景:(这也是我整理思想的地方) 我正在使用这个库并将事件存储在DynamoDB中,并将预测存储在AWS S3中。 写入部分的工作方式很有魅力:每个命令调用都从DynamoDB加载聚合的当前状态(通过处理程序运行事件和/或加载缓存的聚合),它根据一些业务逻辑决定接受或拒绝命令,然后使用键条

  • 当我在读一些CQRS的资料时,有一个反复出现的问题我没有理解。例如,假设一个客户端发出一个命令。该命令由域集成,因此它可以刷新其域模型(DM)。另一方面,命令保存在事件存储中。这是最常见的情况。 1) 当我们说DM被刷新时,我假设数据被保存在底层数据库中(如果有的话)。我说得对吗?否则,我们将处理内存瞬态模型,我想这不是一件好事吗?(在客户端请求之外,状态不应该保留在服务器端的内存中)。 2)如果

  • 事件源和CQRS很棒,因为它让rids开发人员被一个预先建模的数据库所困,除非有一个大的数据迁移项目,否则开发人员必须在应用程序的生命周期内使用该数据库。CQRS和ES还有其他好处,比如扩展eventstore、审计日志等,这些都已经遍布互联网。 但是缺点是什么呢? null