feat(graphql): added support for graphql subscriptions to work for actions#6904
feat(graphql): added support for graphql subscriptions to work for actions#6904psihius wants to merge 11 commits intoapi-platform:mainfrom
Conversation
soyuka
left a comment
There was a problem hiding this comment.
- Private fields config option probably should also use expression language same way topics do
Not sure to understand this functionality, did I miss something?
- Caching item of the subscriptionCollection is going to become quite sizeable when you have a lot of segmentation based on owners of the data and I don't have a good idea how to segment that
I don't think we should send collection updates, only items inside a collection should be sufficient.
When a data is deleted you can still send an update on its IRI (as mercure topic) with a null value? I'm not sure why data should be part of a cache key, instead we should use IRIs just as in REST? Or is it that you want to send a mercure update with only the fields selected in the GraphQl Query?
- Mercure payload format probably needs updating - with it now containing all 3 types of operations, I think it needs a field that contains what type of operation it was - create, update or deleted so mercure consumers can decide what to do
IMO, we should always consider a mercure update as a modified element, it's a new one if you don't have it in your referential, or else it's updated. You can detect it's deleted when the value is null.
- Deleted stdObject contains too little information, kind'a want ability to retain some of the data in there, like for example the parent relation field that points to the parent ID the record was deleted for.
Not sure why we would need this. It makes things really complicated when we look for data associations, when do you stop looking for relations?
https://api-platform.com/docs/core/mercure/#dispatching-restrictive-updates-security-mode - this, but for the graphql subscription. It's a way to restrict data pushing to specific user/resource/whatever way you want to restrict access based on resource field values. I use it to restrict access between accounts in SaaS service.
As you can see here, it first tries to get subscriptions from full IRI. If it's an update operation - the item is already in cache, if not - it goes to collection cache (which would happen only for create operation).
See that second foreach there? It filters out based on field combo subscriptions that are not addressed to the specific combo of fields. The _private_field$field item makes sure with it's value that you send updates to a subscription id that is restricted to the same field value. IF you use There's the corresponding change in SubscriptionManager::retrieveSubscriptionId which gets called in SubscriptionProcessor which is where the subscription id is created for a given IRI, that subscription id is created from fields that were selected as return data for the subscription. In there I also inject those private field and their data from the resource in question, which affects the value of generated subscription id. The subscription id always is generated as a resource wide subscription, it's the same id regardless what IRI you pass as long as it's the same resource type. The cache just was stored per resource IRI (aka per item), so I just added also the collection level cache item so you can push updates for "create" operation, since that adds a new item and you need to know what subscriptions you need to push that update into.
I did modify the output for the "delete" operation to contain
|
|
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
|
I definitely need time to review this I think it's still something that we should integrate. |
Feel free to reach out on Symfony Slack into my DM's or tag me on #api-platform channel about this whole thing. I will be able to do changes/fixes to this whole thing next week cause I think we found a small bug to fix, so I will be touching the code :) |
ead72e1 to
c9cefd8
Compare
|
I found some flaws in my previous implementation, split out completely the handling of collection updates and so that collection subscriptions generate their own subscription ID instead of piggybacking on individual item subscriptions. I've also added relationship handling for mercure updates, because those have never been handled so you could not any -to-many relationship data on an update, even if you asked for it in the GraphQL query. Nothing complicated in there, but it works and will allow people to have simple related payloads to be included with the mercure update payload. There is more I would do, but it would require some major amount of time and effort that I simply just don't have: I would split out mercure update pipeline completely from graphql processing pipeline because part of GraphQL library work is happening at the response time once it;s out of ApiPlatform's graphql internals and as you can see from my latest commit, there are some things that have been missing because mercure updates happen as a side channel as a doctrine event listener. |
decfc5b to
9e61e92
Compare
|
don't hesitate to add functional tests to help me understand the needs behind that, there are tests inside |
Yeah, I've been slowly working on it. In other news, my last commit is more of a temporary fix for our production system, but the cache pool that's used there right now needs to be replaced. The system cache pool does not have locking and protection from parallel writing, which under 20+ users, started to overwrite each other's changes. I've also added private field data to deleted object data in the doctrine listener, probably same needs to happen in ODM listener. I needed to read deleted item fields and pass them on to the subscription manager. |
ef602bf to
cea37ca
Compare
src/Symfony/Doctrine/EventListener/PublishMercureUpdatesListener.php
Outdated
Show resolved
Hide resolved
|
Thanks for the review, I will adjust to feedback. I do have a question about collections, if I implement CollectionOperationInterface for the collection subscription, I assume the system will allow operating on collections via the reference to the resource aka I would like some references what to read and what files to dig into to explore proper collection subscription implementation and what helpers and components do I need to look at, since I'm not very familiar with broader internals. Like I did not know and would not find IdentifiersExtractor without the suggestion for quite a while if I continued to dig on my own :) |
…o have smaller diff
…ld data if those are set.
…pdates to them all
1aaf997 to
14994de
Compare
0e1eb28 to
6d97b96
Compare
|
Performed a rework of the feature based on feedback and did some internal cache related changes that changed how this works quite a bit. Documentation PR: api-platform/docs#2267 The biggest change is that collection subscriptions are now modeled as their own GraphQL operation type via Current behavior
{
"type": "delete",
"payload": {
"id": "/books/1",
"iri": "https://example.com/books/1",
"type": "Book"
}
}I kept the delete payload explicit on purpose. For item subscriptions you could argue Internal changesThe old collection IRI derivation hack is gone. Collection subscription lookup is now based on resource metadata +
This is meant for cases where a client is authenticated and allowed to see multiple scopes, but subscriptions still need to be isolated by something like organization / workspace / inbox / whatever the app defines. This is now validated explicitly, the extraction logic was centralized, and it now uses Symfony property access instead of raw getter assumptions. Cache behavior changed quite a bit too.
This makes the internal cache model much more predictable and fixes a few edge cases from the earlier implementation, especially around private subscriptions and collection notifications. I also addressed the main review points around:
TestingTesting is much broader now too. The feature is covered in:
|
2d830af to
aec5007
Compare
Signed-off-by: psihius <arvids.godjuks@gmail.com>
aec5007 to
f42ceee
Compare
|
Small follow-up after the last force-pushes: I fixed two remaining issues in the subscription/cache path:
I re-ran the focused subscription surface locally after that:
All green. |
This PR makes create and delete operations issue mercure updates for GraphQL subscriptions.
It also adds the ability to configure "restrictive updates" - https://api-platform.com/docs/core/mercure/#dispatching-restrictive-updates-security-mode - implementation follows the same idea, just one that fits GraphQL subscriptions.
Some things that need feedback/work/ideas:
This is a WIP because I need feedback on the direction taken and suggestions for improving some aspects of it.
This code is being used in production, works for our use case, but it's obviously far from ideal and I want to improve it and make it standard part of ApiPlatform.
The goal for this is to make managing items on frontend easy purely via mercure updates via graphql subscriptions. In our case it's a chat like frontend that needs to not only receive updates, but also new data (new messages, new chats) and data removal messages. This puts GraphQL API on par with functionality of the REST API where you can have restrictive data updates AND you can subscribe to collections which include all 3 types of data events.