-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Document the general design (RAD, CQRS, CQS, event sourcing...) and data persisters #518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# Data Persisters | ||
|
||
To mutate the application states during `POST`, `PUT`, `PATCH` or `DELETE` [operations](operations.md), API Platform uses | ||
classes called **data persisters**. Data persisters receive an instance of the class marked as an API resource (usually using | ||
the `@ApiResource` annotation). This instance contains data submitted by the client during [the deserialization | ||
process](serialization.md). | ||
|
||
A data persister using [Doctrine ORM](http://www.doctrine-project.org/projects/orm.html) is included with the library and | ||
is enabled by default. It is able to persist and delete objects that are also mapped as [Doctrine entities](https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/basic-mapping.html). | ||
|
||
However, you may want to: | ||
|
||
* store data to other persistence layers (ElasticSearch, MongoDB, external web services...) | ||
* not publicly expose the internal model mapped with the database through the API | ||
* use a separate model for [read operations](data-providers.md) and for updates by implementing patterns such as [CQRS](https://martinfowler.com/bliki/CQRS.html) | ||
|
||
Custom data persisters can be used to do so. A project can include as many data persisters as it needs. The first able to | ||
persist data for a given resource will be used. | ||
|
||
## Creating a Custom Data Persister | ||
|
||
To create a data persister, you have to implement the [`DataPersisterInterface`](https://github.com/api-platform/core/blob/master/src/DataPersister/DataPersisterInterface.php). | ||
This interface defines only 3 methods: | ||
|
||
* `persist`: to create or update the given data | ||
* `delete`: to delete the given data | ||
* `support`: checks whether the given data is supported by this data persister | ||
|
||
Here is an implementation example: | ||
|
||
```php | ||
namespace App\DataPersister; | ||
|
||
use ApiPlatform\Core\DataPersister\DataPersisterInterface; | ||
use App\Entity\BlogPost; | ||
|
||
final class BlogPostDataPersister implements DataPersisterInterface | ||
{ | ||
public function supports($data): bool | ||
{ | ||
return $data instanceof BlogPost; | ||
} | ||
|
||
public function persist($data) | ||
{ | ||
// call your persistence layer to save $data | ||
return $data; | ||
} | ||
|
||
public function delete($data): void | ||
{ | ||
// call your persistence layer to delete $data | ||
} | ||
} | ||
``` | ||
|
||
If service autowiring and autoconfiguration are enabled (they are by default), you are done! | ||
|
||
Otherwise, if you use a custom dependency injection configuration, you need to register the corresponding service and add the | ||
`api_platform.data_persister` tag. The `priority` attribute can be used to order persisters. | ||
|
||
```yaml | ||
# api/config/services.yaml | ||
services: | ||
# ... | ||
'App\DataPersister\BlogPostDataPersister': ~ | ||
# Uncomment only if autoconfiguration is disabled | ||
#tags: [ 'api_platform.data_persister' ] | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# General Design Considerations | ||
|
||
Since you only need to describe the structure of the data to expose, API Platform is both [a "design-first" and "code-first"](https://swagger.io/blog/api-design/design-first-or-code-first-api-development/) | ||
API framework. However, the "design-first" methodology is strongly recommended: first you design the **public shape** of | ||
API endpoints. | ||
|
||
To do so, you have to write a plain old PHP object representing the input and output of your endpoint. This is the class | ||
that is [marked with the `@ApiResource` annotation](../distribution/index.md). | ||
This class **doesn't have** to be mapped with Doctrine ORM, or any other persistence system. It must be simple (it's usually | ||
just a data structure with no or minimal behaviors) and will be automatically converted to [Hydra](extending-jsonld-context.md), | ||
[OpenAPI / Swagger](swagger.md) and [GraphQL](graphql.md) documentations or schemas by API Platform (there is a 1-1 mapping | ||
between this class and those docs). | ||
|
||
Then, it's up to the developer to feed API Platform with an hydrated instance of this API resource object by implementing | ||
the [`DataProviderInterface`](data-providers.md). Basically, the data provider will query the persistence system (RDBMS, | ||
document or graph DB, external API...), and must hydrate and return the POPO that has been designed as mentioned above. | ||
|
||
When updating a state (`POST`, `PUT`, `PATCH`, `DELETE` HTTP methods), it's up to the developer to properly persist the | ||
data provided by API Platform's resource object [hydrated by the serializer](serialization.md). | ||
To do so, there is another interface to implement: [`DataPersisterInterface`](data-persisters.md). | ||
|
||
This class will read the API resource object (the one marked with `@ApiResource`) and: | ||
|
||
* persist it directly in the database; | ||
* or hydrate a DTO then trigger a command; | ||
* or populate an event store; | ||
* or persist the data in any other useful way. | ||
|
||
The logic of data persisters is the responsibility of application developers, and is **out of the API Platform's scope**. | ||
|
||
For [Rapid Application Development](https://en.wikipedia.org/wiki/Rapid_application_development), convenience and prototyping, | ||
**if and only if the class marked with `@ApiResource` is also a Doctrine entity**, the developer can use the Doctrine | ||
ORM's data provider and persister implementations shipped with API Platform. | ||
|
||
In this case, the public (`@ApiResource`) and internal (Doctrine entity) data model are shared. Then, API Platform will | ||
be able to query, filter, paginate and persist automatically data. | ||
This is 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) | ||
and/or large systems. | ||
Again, it's up to the developers to use, or to not use those built-in data providers/persisters depending of the business | ||
they are dealing with. API Platform makes it easy to create custom data providers and persisters, and to implement appropriate | ||
patterns such as [CQS](https://www.martinfowler.com/bliki/CommandQuerySeparation.html) or [CQRS](https://martinfowler.com/bliki/CQRS.html). | ||
|
||
Last but not least, to create [Event Sourcing](https://martinfowler.com/eaaDev/EventSourcing.html)-based systems, a convenient | ||
approach is: | ||
|
||
* to persist data in an event store using a custom [data persister](data-persisters.md) | ||
* to create projections in standard RDBMS (Postgres, MariaDB...) tables or views | ||
* to map those projections with read-only Doctrine entity classes **and** to mark those classes with `@ApiResource` | ||
|
||
You can then benefit from the built-in Doctrine filters, sorting, pagination, auto-joins, etc provided by API Platform. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,16 +12,17 @@ | |
1. [Installing API Platform Core](core/getting-started.md#installing-api-platform-core) | ||
2. [Before Reading this Documentation](core/getting-started.md#before-reading-this-documentation) | ||
3. [Mapping the Entities](core/getting-started.md#mapping-the-entities) | ||
3. [Configuration](core/configuration.md) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could put everything at There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes but it’s less readable when reading the text file directly. Anyway we’re working on a TOC generator. Dropping this file will make contributions lot easier and less error prone. |
||
4. [Operations](core/operations.md) | ||
3. [General Design Considerations](core/design.md) | ||
4. [Configuration](core/configuration.md) | ||
5. [Operations](core/operations.md) | ||
1. [Enabling and Disabling Operations](core/operations.md#enabling-and-disabling-operations) | ||
2. [Configuring Operations](core/operations.md#configuring-operations) | ||
3. [Subresources](core/operations.md#subresources) | ||
4. [Creating Custom Operations and Controllers](core/operations.md#creating-custom-operations-and-controllers) | ||
1. [Recommended Method](core/operations.md#recommended-method) | ||
2. [Alternative Method](core/operations.md#alternative-method) | ||
5. [Overriding Default Order](core/default-order.md) | ||
6. [Filters](core/filters.md) | ||
6. [Overriding Default Order](core/default-order.md) | ||
7. [Filters](core/filters.md) | ||
1. [Doctrine ORM Filters](core/filters.md#doctrine-orm-filters) | ||
1. [Search Filter](core/filters.md#search-filter) | ||
2. [Date Filter](core/filters.md#date-filter) | ||
|
@@ -42,7 +43,7 @@ | |
1. [Using Doctrine Filters](core/filters.md#using-doctrine-filters) | ||
2. [Overriding Extraction of Properties from the Request](core/filters.md#overriding-extraction-of-properties-from-the-request) | ||
4. [ApiFilter Annotation](core/filters.md#apifilter-annotation) | ||
7. [The Serialization Process](core/serialization.md) | ||
8. [The Serialization Process](core/serialization.md) | ||
1. [Overall Process](core/serialization.md#overall-process) | ||
2. [Available Serializers](core/serialization.md#available-serializers) | ||
3. [The Serialization Context, Groups and Relations](core/serialization.md#the-serialization-context-groups-and-relations) | ||
|
@@ -58,34 +59,35 @@ | |
9. [Decorating a Serializer and Add Extra Data](core/serialization.md#decorating-a-serializer-and-add-extra-data) | ||
10. [Entity Identifier Case](core/serialization.md#entity-identifier-case) | ||
11. [Embedding the JSON-LD Context](core/serialization.md#embedding-the-json-ld-context) | ||
8. [Validation](core/validation.md) | ||
9. [Validation](core/validation.md) | ||
1. [Using Validation Groups](core/validation.md#using-validation-groups) | ||
2. [Using Validation Groups on operations](core/validation.md#using-validation-groups-on-operations) | ||
3. [Dynamic Validation Groups](core/validation.md#dynamic-validation-groups) | ||
4. [Error Levels and Payload Serialization](core/validation.md#error-levels-and-payload-serialization) | ||
9. [Error Handling](core/errors.md) | ||
10. [Error Handling](core/errors.md) | ||
1. [Converting PHP Exceptions to HTTP Errors](core/errors.md#converting-php-exceptions-to-http-errors) | ||
10. [Pagination](core/pagination.md) | ||
11. [Pagination](core/pagination.md) | ||
1. [Disabling the Pagination](core/pagination.md#disabling-the-pagination) | ||
2. [Changing the Number of Items per Page](core/pagination.md#changing-the-number-of-items-per-page) | ||
3. [Partial Pagination](core/pagination.md#partial-pagination) | ||
11. [The Event System](core/events.md) | ||
12. [Content Negotiation](core/content-negotiation.md) | ||
12. [The Event System](core/events.md) | ||
13. [Content Negotiation](core/content-negotiation.md) | ||
1. [Enabling Several Formats](core/content-negotiation.md#enabling-several-formats) | ||
2. [Registering a Custom Serializer](core/content-negotiation.md#registering-a-custom-serializer) | ||
3. [Creating a Responder](core/content-negotiation.md#creating-a-responder) | ||
4. [Writing a Custom Normalizer](core/content-negotiation.md#writing-a-custom-normalizer) | ||
13. [Using External JSON-LD Vocabularies](core/external-vocabularies.md) | ||
14. [Extending JSON-LD context](core/extending-jsonld-context.md) | ||
15. [Data Providers](core/data-providers.md) | ||
14. [Using External JSON-LD Vocabularies](core/external-vocabularies.md) | ||
15. [Extending JSON-LD context](core/extending-jsonld-context.md) | ||
16. [Data Providers](core/data-providers.md) | ||
1. [Custom Collection Data Provider](core/data-providers.md#custom-collection-data-provider) | ||
2. [Custom Item Data Provider](core/data-providers.md#custom-item-data-provider) | ||
3. [Injecting the Serializer in an `ItemDataProvider`](core/data-providers.md#injecting-the-serializer-in-an-itemdataprovider) | ||
16. [Extensions](core/extensions.md) | ||
17. [Data Persisters](core/data-persisters.md) | ||
18. [Extensions](core/extensions.md) | ||
1. [Custom Extension](core/extensions.md#custom-extension) | ||
2. [Filter upon the current user](core/extensions.md#example) | ||
17. [Security](core/security.md) | ||
18. [Performance](core/performance.md) | ||
19. [Security](core/security.md) | ||
20. [Performance](core/performance.md) | ||
1. [Enabling the Built-in HTTP Cache Invalidation System](core/performance.md#enabling-the-builtin-http-cache-invalidation-system) | ||
2. [Enabling the Metadata Cache](core/performance.md#enabling-the-metadata-cache) | ||
3. [Using PPM (PHP-PM)](core/performance.md#using-ppm-php-pm) | ||
|
@@ -97,32 +99,32 @@ | |
3. [Override at Resource and Operation Level](core/performance.md#override-at-resource-and-operation-level) | ||
4. [Disable Eager Loading](core/performance.md#disable-eager-loading) | ||
3. [Partial Pagination](core/performance.md#partial-pagination) | ||
19. [Operation Path Naming](core/operation-path-naming.md) | ||
21. [Operation Path Naming](core/operation-path-naming.md) | ||
1. [Configuration](core/operation-path-naming.md#configuration) | ||
2. [Create a Custom Operation Path Naming](core/operation-path-naming.md#create-a-custom-operation-path-resolver) | ||
1. [Defining the Operation Path Naming](core/operation-path-naming.md#defining-the-operation-path-resolver) | ||
2. [Registering the Service](core/operation-path-naming.md#registering-the-service) | ||
3. [Configure it](core/operation-path-naming.md#configure-it) | ||
20. [Accept application/x-www-form-urlencoded Form Data](core/form-data.md) | ||
21. [FOSUserBundle Integration](core/fosuser-bundle.md) | ||
22. [Accept application/x-www-form-urlencoded Form Data](core/form-data.md) | ||
23. [FOSUserBundle Integration](core/fosuser-bundle.md) | ||
1. [Installing the Bundle](core/fosuser-bundle.md#installing-the-bundle) | ||
2. [Enabling the Bridge](core/fosuser-bundle.md#enabling-the-bridge) | ||
3. [Creating a `User` Entity with Serialization Groups](core/fosuser-bundle.md#creating-a-user-entity-with-serialization-groups) | ||
22. [Adding a JWT authentication using LexikJWTAuthenticationBundle](core/jwt.md) | ||
24. [Adding a JWT authentication using LexikJWTAuthenticationBundle](core/jwt.md) | ||
1. [Testing with Swagger](core/jwt.md#testing-with-swagger) | ||
2. [Testing with Behat](core/jwt.md#testing-with-behat) | ||
23. [NelmioApiDocBundle integration](core/nelmio-api-doc.md) | ||
24. [AngularJS Integration](core/angularjs-integration.md) | ||
25. [NelmioApiDocBundle integration](core/nelmio-api-doc.md) | ||
26. [AngularJS Integration](core/angularjs-integration.md) | ||
1. [Restangular](core/angularjs-integration.md#restangular) | ||
2. [ng-admin](core/angularjs-integration.md#ng-admin) | ||
25. [Swagger Support](core/swagger.md) | ||
27. [Swagger Support](core/swagger.md) | ||
1. [Override Swagger documentation](core/swagger.md#override-swagger-documentation) | ||
26. [GraphQL Support](core/graphql.md) | ||
28. [GraphQL Support](core/graphql.md) | ||
1. [Overall View](core/graphql.md#overall-view) | ||
2. [Enabling GraphQL](core/graphql.md#enabling-graphql) | ||
3. [GraphiQL](core/graphql.md#graphiql) | ||
27. [Handling Data Transfer Objects (DTOs)](core/dto.md) | ||
28. [Handling File Upload with VichUploaderBundle](core/file-upload.md) | ||
29. [Handling Data Transfer Objects (DTOs)](core/dto.md) | ||
30. [Handling File Upload with VichUploaderBundle](core/file-upload.md) | ||
|
||
## The Schema Generator Component: Generate Data Models from Open Vocabularies | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC the return type is
void
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It has been changed by api-platform/core#1967