Skip to content

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

Merged
merged 2 commits into from
Jul 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions core/data-persisters.md
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;
Copy link
Member

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.

Copy link
Member Author

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

}

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' ]
```
3 changes: 2 additions & 1 deletion core/data-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ final class BlogPostCollectionDataProvider implements CollectionDataProviderInte
}
```

Then declare a Symfony service, for example:
If you use the default configuration, the corresponding service will be automatically registered thanks to [autowiring](https://symfony.com/doc/current/service_container/autowiring.html).
To declare the service explicitly, or to set a custom priority, you can use the following snippet:

```yaml
# api/config/services.yaml
Expand Down
50 changes: 50 additions & 0 deletions core/design.md
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.
4 changes: 2 additions & 2 deletions core/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ Built-in event listeners are:
Name | Event | Pre & Post hooks | Priority | Description
------------------------------|--------------------|--------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------
`AddFormatListener` | `kernel.request` | None | 7 | guess the best response format ([content negotiation](content-negotiation.md))
`ReadListener` | `kernel.request` | `PRE_READ`, `POST_READ` | 4 | retrieve data from the persistence system using the [data providers](data-providers.md)
`ReadListener` | `kernel.request` | `PRE_READ`, `POST_READ` | 4 | retrieve data from the persistence system using the [data providers](data-providers.md) (`GET`, `PUT`, `DELETE`)
`DeserializeListener` | `kernel.request` | `PRE_DESERIALIZE`, `POST_DESERIALIZE`| 2 | deserialize data into a PHP entity (`GET`, `POST`, `DELETE`); update the entity retrieved using the data provider (`PUT`)
`ValidateListener` | `kernel.view` | `PRE_VALIDATE`, `POST_VALIDATE` | 64 | [validate data](validation.md) (`POST`, `PUT`)
`WriteListener` | `kernel.view` | `PRE_WRITE`, `POST_WRITE` | 32 | if using the Doctrine ORM, persist data (`POST`, `PUT`, `DELETE`)
`WriteListener` | `kernel.view` | `PRE_WRITE`, `POST_WRITE` | 32 | persist changes in the persistence system using the [data persisters](data-persisters.md) (`POST`, `PUT`, `DELETE`)
`SerializeListener` | `kernel.view` | `PRE_SERIALIZE`, `POST_SERIALIZE` | 16 | serialize the PHP entity in string [according to the request format](content-negotiation.md)
`RespondListener` | `kernel.view` | `PRE_RESPOND`, `POST_RESPOND` | 8 | transform serialized to a `Symfony\Component\HttpFoundation\Response` instance
`AddLinkHeaderListener` | `kernel.response` | None | 0 | add a `Link` HTTP header pointing to the Hydra documentation
Expand Down
54 changes: 28 additions & 26 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could put everything at 1 and don't bother with incrementing everything again, Markdown will do it for you 😉

Copy link
Member Author

Choose a reason for hiding this comment

The 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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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

Expand Down