在官方 Api-Platform 网站上有一个通用设计注意事项页面。
最后但同样重要的是,要创建基于事件溯源的系统,一种方便的方法是:
- 使用自定义数据持久化器将数据持久化到事件存储中
- 在标准 RDBMS(Postgres、MariaDB...)表或视图中创建投影
- 用只读的 Doctrine 实体类映射这些投影,并用 @ApiResource 标记这些类
然后,您可以从 API 平台提供的内置 Doctrine 过滤器、排序、分页、自动连接等中受益。
因此,我尝试通过一种简化来实现这种方法(使用一个 DB,但读取和写入是分开的)。
但是失败了...有一个问题,我不知道如何解决,所以请您帮忙!
我创建了一个User
Doctrine 实体和我想用@Serializer\Groups({"Read"})
. 我将在此处省略它,因为它非常通用。
User
用于 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')"
因此,如上所示,User
Doctrine 实体是只读的,因为只GET
定义了方法。
然后我创建了一个CreateUser
DTO:
# 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;
}
CreateUser
用于 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}
我还添加了一个自定义来DataPersister
处理. 在我使用 Doctrine 实体来写入数据,但对于这种情况,这并不重要,因为 Api 平台不知道 DataPersister 将如何写入它。因此,从概念上讲 - 它是读取和写入的分离。POST
/users
CreateUserDataPersister::persist
读取由 DoctrineDataProvider
附带的 Api 平台执行,写入由 custom 执行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"
}
问题!
我收到400
回复... "hydra:description": "No item route associated with the type "App\Dto\User\CreateUser"." ...
但是,一条新记录被添加到数据库中,因此自定义 DataPersister 有效;)
根据一般设计考虑,实现了写入和读取的分离,但没有按预期工作。
我很确定,我可能会遗漏一些需要配置或实现的东西。所以,这就是它不起作用的原因。
很乐意得到任何帮助!
更新 1:
问题出在\ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolver::getRouteName()
. 在第 48-59 行,它遍历所有路线,试图找到合适的路线:
$operationType = 'item'
$resourceClass = 'App\Dto\User\CreateUser'
但是只$operationType = 'item'
为定义 $resourceClass = 'App\Entity\User\User'
,所以找不到路由并抛出异常。
更新 2:
所以,这个问题听起来像这样:
如何使用 Doctrine 实体进行读取和 DTO 进行写入来实现读取和写入的分离(CQS?),两者都驻留在同一条路线上,但使用不同的方法?
更新 3:
- 将数据存储到其他持久层(ElasticSearch、MongoDB、外部 Web 服务...)
- 不通过API公开暴露与数据库映射的内部模型
- 通过实现 CQRS 等模式,为读取操作和更新使用单独的模型
是的!我想要那个......但是如何在我的例子中实现它?