Skip to content

Commit e8ca537

Browse files
committed
Docs for the Messenger integration
1 parent a1bf55a commit e8ca537

File tree

4 files changed

+118
-19
lines changed

4 files changed

+118
-19
lines changed

core/deprecations.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ Thanks to the `sunset` attribute, API Platform makes it easy to set this header
107107
<?php
108108
// api/src/Entity/Parchment.php
109109

110+
namespace App\Entity;
111+
110112
/**
111113
* @ApiResource(
112114
* deprecationReason="Create a Book instead",
@@ -128,6 +130,8 @@ It's also possible to set the `Sunset` header only for a specific [operation](op
128130
<?php
129131
// api/src/Entity/Parchment.php
130132

133+
namespace App\Entity;
134+
131135
/**
132136
* @ApiResource(itemOperations={
133137
* "get"={

core/design.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ To do so, you have to write a plain old PHP object representing the input and ou
88
that is [marked with the `@ApiResource` annotation](../distribution/index.md).
99
This class **doesn't have** to be mapped with Doctrine ORM, or any other persistence system. It must be simple (it's usually
1010
just a data structure with no or minimal behaviors) and will be automatically converted to [Hydra](extending-jsonld-context.md),
11-
[OpenAPI / Swagger](swagger.md) and [GraphQL](graphql.md) documentations or schemas by API Platform (there is a 1-1 mapping
11+
[OpenAPI](swagger.md) and [GraphQL](graphql.md) documentations or schemas by API Platform (there is a 1-1 mapping
1212
between this class and those docs).
1313

1414
Then, it's up to the developer to feed API Platform with an hydrated instance of this API resource object by implementing
@@ -37,13 +37,15 @@ be able to query, filter, paginate and persist data automatically.
3737
This approach is super-convenient and efficient, but is probably **not a good idea** for non-[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete)
3838
and/or large systems.
3939
Again, it's up to the developers to use, or to not use these built-in data providers/persisters depending on the business logic
40-
they are dealing with. API Platform makes it easy to create custom data providers and persisters, and to implement appropriate
41-
patterns such as [CQS](https://www.martinfowler.com/bliki/CommandQuerySeparation.html) or [CQRS](https://martinfowler.com/bliki/CQRS.html).
40+
they are dealing with.
41+
API Platform makes it easy to create custom data providers and persisters.
42+
It also makes it east to implement patterns such as [CQS](https://www.martinfowler.com/bliki/CommandQuerySeparation.html)
43+
or [CQRS](https://martinfowler.com/bliki/CQRS.html) thanks to [the Messenger Component integration](messenger.md) and the [DTO support](dto.md).
4244

4345
Last but not least, to create [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html)-based systems, a convenient
4446
approach is:
4547

46-
* to persist data in an event store using a custom [data persister](data-persisters.md)
48+
* to persist data in an event store using a Messenger handler or a custom [data persister](data-persisters.md)
4749
* to create projections in standard RDBMS (Postgres, MariaDB...) tables or views
4850
* to map those projections with read-only Doctrine entity classes **and** to mark those classes with `@ApiResource`
4951

core/dto.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ So let's create a basic DTO for this request:
5151

5252
```php
5353
<?php
54-
// api/src/Api/Dto/ForgotPasswordRequest.php
54+
// api/src/Entity/ResetPasswordRequest.php
5555

56-
namespace App\Entity\Dto;
56+
namespace App\Entity;
5757

5858
use ApiPlatform\Core\Annotation\ApiResource;
5959
use Symfony\Component\Validator\Constraints as Assert;
@@ -62,44 +62,44 @@ use Symfony\Component\Validator\Constraints as Assert;
6262
* @ApiResource(
6363
* collectionOperations={
6464
* "post"={
65-
* "path"="/users/forgot-password-request",
66-
* "status"=202
65+
* "path"="/users/forgot-password-request"
6766
* },
6867
* },
6968
* itemOperations={},
7069
* outputClass=false
7170
* )
7271
*/
73-
final class ForgotPasswordRequest
72+
final class ResetPasswordRequest
7473
{
7574
/**
75+
* @var string
76+
*
7677
* @Assert\NotBlank
77-
* @Assert\Email
7878
*/
79-
public $email;
79+
public $username;
8080
}
8181
```
8282

8383
In this case, we disable all operations except `POST`. We also set the `output_class` attribute to `false` to hint API Platform that no data will be returned by this endpoint.
84-
Finally, we use the `status` attribute to configure API Platform to return a [202 Accepted HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202).
85-
It indicates that the request has been received and will be treated without giving an immediate return to the client.
8684

8785
Then, thanks to [a custom data persister](data-persisters.md), it's possible to trigger some custom logic when the request is received.
8886

8987
Create the data persister:
9088

9189
```php
92-
// api/src/DataPersister/ForgotPasswordRequestDataPersister.php
90+
<?php
91+
// api/src/DataPersister/ResetPasswordRequestDataPersister.php
92+
****
9393
namespace App\DataPersister;
9494

9595
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
96-
use App\Entity\ForgotPasswordRequest;
96+
use App\Entity\ResetPasswordRequest;
9797

98-
final class ForgotPasswordRequestDataPersister implements DataPersisterInterface
98+
final class ResetPasswordRequestDataPersister implements DataPersisterInterface
9999
{
100100
public function supports($data): bool
101101
{
102-
return $data instanceof ForgotPasswordRequest;
102+
return $data instanceof ResetPasswordRequest;
103103
}
104104

105105
public function persist($data)
@@ -121,9 +121,9 @@ And register it:
121121
# api/config/services.yaml
122122
services:
123123
# ...
124-
'App\DataPersister\ForgotPasswordRequestDataPersister': ~
124+
'App\DataPersister\ResetPasswordRequestDataPersister': ~
125125
# Uncomment only if autoconfiguration is disabled
126126
#tags: [ 'api_platform.data_persister' ]
127127
```
128128

129-
Instead of a custom data persister, you'll probably want to leverage the Symfony Messenger Component integration.
129+
Instead of a custom data persister, you'll probably want to leverage [the Symfony Messenger Component integration](messenger.md).

core/messenger.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Symfony Messenger Integration: CQRS and Async Message Processing
2+
3+
API Platform provides an integration with the [Symfony Messenger Component](https://symfony.com/doc/current/messenger.html).
4+
5+
This feature allows to implement the [Command Query Responsibility Segregation (CQRS)](https://martinfowler.com/bliki/CQRS.html) pattern in a convenient way.
6+
It also makes it easy to send messages through the web API that will be consumed asynchronously.
7+
8+
Many transports are supported to dispatch messages to async consumers, including RabbitMQ, Apache Kafka, Amazon SQS and Google Pub/Sub.
9+
10+
## Installing Symfony Messenger
11+
12+
To enable the support of Messenger, install the library:
13+
14+
docker-compose exec php composer require messenger
15+
16+
## Dispatching a Resource through the Message Bus
17+
18+
Set the `messenger` attribute to `true`, and API Platform will automatically dispatch the API Resource instance as a message using the message bus provided by the Messenger Component:
19+
20+
```php
21+
<?php
22+
23+
// api/src/Entity/ResetPasswordRequest.php
24+
25+
namespace App\Entity;
26+
27+
use ApiPlatform\Core\Annotation\ApiResource;
28+
use Symfony\Component\Validator\Constraints as Assert;
29+
30+
/**
31+
* @ApiResource(
32+
* messenger=true,
33+
* collectionOperations={
34+
* "post"={"status"=202}
35+
* },
36+
* itemOperations={},
37+
* outputClass=false
38+
* )
39+
*/
40+
final class ResetPasswordRequest
41+
{
42+
/**
43+
* @var string
44+
*
45+
* @Assert\NotBlank
46+
*/
47+
public $username;
48+
}
49+
```
50+
51+
Because the `messenger` attribute is true, when a `POST` will be handled by API Platform, the corresponding instance of the `ResetPasswordRequest` will be dispatched.
52+
53+
For this example, only the `POST` operation is enabled.
54+
We use the `status` attribute to configure API Platform to return a [202 Accepted HTTP status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202).
55+
It indicates that the request has been received and will be treated later, without giving an immediate return to the client.
56+
Finally, the `output_class` attribute is set to `false`, so the HTTP response that will be generated by API Platform will be empty, and the [serialization process](serialization.md) will be skipped.
57+
58+
## Registering a Message Handler
59+
60+
To process the message that will be dispatched, [a handler](https://symfony.com/doc/current/messenger.html#registering-handlers) must be created:
61+
62+
```php
63+
<?php
64+
65+
// api/src/Handler/ResetPasswordRequestHandler.php
66+
67+
namespace App\Handler;
68+
69+
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
70+
71+
final class ResetPasswordRequestHandler implements MessageHandlerInterface
72+
{
73+
public function __invoke(ResetPasswordRequest $forgotPassword)
74+
{
75+
// do something with the resource
76+
}
77+
}
78+
```
79+
80+
That's all!
81+
82+
By default, the handler will process your message synchronously.
83+
If you want it to be consumed asynchronously (e.g. by on a worker machine), [configure a transport and the consumer](https://symfony.com/doc/current/messenger.html#transports).
84+
85+
## Accessing to the Data Returned by the Handler
86+
87+
API Platform automatically uses the `Symfony\Component\Messenger\Stamp\HandledStamp` when set.
88+
It means that if you use a synchronous handler, the data returned by the `__invoke` method replaces the original data.
89+
90+
## Detecting Removals
91+
92+
When a `DELETE` operation occurs, API Platform automatically adds a `ApiPlatform\Core\Bridge\Symfony\Messenger\RemoveStamp` ["stamp"](https://symfony.com/doc/current/components/messenger.html#adding-metadata-to-messages-envelopes) instance to the "envelope".
93+
To differentiate typical persists calls (create and update) and removal calls, check for the presence of this stamp using a custom "middleware".

0 commit comments

Comments
 (0)