在官方的Api平台网站上有一个通用设计考虑页面。
最后但同样重要的是,要创建基于事件源的系统,一种方便的方法是:
然后,您可以从API平台提供的内置Doctrine过滤器、排序、分页、自动加入等中受益。
因此,我尝试用一种简化来实现这种方法(使用一个DB,但读写分离)。
但失败了...有一个问题,我不知道如何解决,所以请你帮忙!
我创建了一个user
Doctrine实体和我想使用@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')"
因此,如上所示,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;
}
创建用户
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:
数据持有者
是的!我想要那个...但是如何在我的例子中实现它?
仅适用于 2.4 版本,但真的很有帮助。
只需为 CreateUserDTO 添加 output_class=false
,POST 就会一切正常|噗|补丁
output_class设置为false允许您绕过获取项目操作。你可以在API platform \ Core \ event listener # L68中看到。
你需要在回答中发送“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"
}
附:抱歉我的英语:)
问题是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