Over the past year, we've added comprehensive test coverage and ensured that JSON:API was both secure and cacheable (by auditing and correcting cache contexts/tags)
In this process, we learned that our normalizers were incompatible with the Symfony normalizer interface and we "cordoned off" the normalization system with the eventual intent of refactoring it and adding support for schema generation/validation.
This issue is meant to track and coordinate that process. Below, I've written out in broad strokes the steps, in order, that I think we can take to end up in a world where our normalizers aren't a black box to outsiders, where they're faster, where they're schema-complete and where there's a clear path forward for bringing Drupal core's normalizers into that fast/schema-complete world too (with minimal disruption and no BC-breaks).
I'd like to ensure the our many normalizer value objects really are just value objects. Over time, we've added many more value objects and learned that some of their inheritance hierarchies make them unwieldy (HttpExceptionNormalizerValueextendsFieldNormalizerValue, for example). Some of them implement a hefty bit of specialized normalization logic for what should really pure value object. I believe this first step will result in fewer value objects. I think we can consolidate many of them into a standardNormalizedValueobject which simply takes an array and aCacheableMetadataobject.I'd like to reduce the number of (or at least confirm the number is necessary) normalizer's that we actually have. I believe issue like #3015438: Wrap entity objects in a ResourceObject which carries a ResourceType can start to reduce the number of them. I think theRelationshipandRelationshipItemclasses (and their normalizers) can probably be factored away if we take advantage of the new value objects we have elsewhere (likeEntityCollectionandResourceIdentifier(Interface)).I'd like to come up with a pattern to extend the now cleaned up normalizers with a caching mechanism. Right now, I'm imagining a service tag that identifies the normalizer as "deterministic", meaning that it can be cached by its(Done in #3252872: Use CacheableSupportsMethodInterface for performance improvement in normalizers) Following up on that, I think we could use reflection to identify TypedData normalizers (IOW, non-JSON:API normalizers) that can also be cached and use that knowledge to alter their service definitions in order to add the "deterministic" tag.$supportedInterfaceOrClassvalue.At this point in the process, I think it would be safe to then begin adding the "deterministic" tag to core normalizers where possible and altering the Drupal core- No longer a need for deterministic tag.serializerservice to implement cacheing based on this tag as well. All normalizers probably won't be 100% cacheable, but I think we could get to that state for most of them.After the "deterministic" flag and concurrently with core's adoption of it,I'd like to add two new interfaces calledSchematicNormalizerInterfaceandSchematicDenormalizerInterface. These interfaces would have aget(De)NormalizationSchemamethod on them. JSON:API could enforce this interface for its own normalizers and begin adding it to core normalizers as well. With the addition of that interface, we could then validate input and output of those normalizers against their declared schema. The challenge here is that normalizers for things likeEntityInterfacecan't really provide a useful schema without knowledge of the entity type/bundle This is where @e0ipso'sResourceFieldInterfaceet al. come into play by allowing theResourceTypeto know about its field types. I think this could work and perhaps the new schema interface will have to take a queue from (or import) @e0ipso'sshaperlibrary with its concept of normalization "context" (this is different from Symfony's idea of context, I believe).- Paired with
the "deterministic" flag andthe schema interfaces, we'd then have enough knowledge to generate resource type-level schema's with some certainty. Contrib and core would have a BC-compliant way of opting in to this system byadding the "deterministic" tag andimplementing the additional interface(s). - With this concept proven, I think we could then require new normalizers to have this tag and interface on them and move those tags/interfaces into Drupal core. When normalizers are detected without either of them, we could throw deprecation notices and then require that they be implemented in Drupal 11.
- After (or maybe even before) these deprecations, I think that we could have a schema generation module
(probably schemata)that with a config/settings.php setting, we could require these tags/interfaces and if the normalizer does not have both, it could simply remove the service from the container. If the settings is not enabled, then the schema it would output would fall back to usingthat outputs a schema with schemas, if known, oranyfor non-deterministic scenarios.anyor some other safe default in other cases.
Obviously, the above is subject to lots of input and suggestions. I've been thinking about all this for a while but have never actually put it all down in one place.
Comments
Comment #2
gabesulliceComment #3
gabesulliceComment #4
gabesulliceComment #5
gabesulliceComment #6
gabesulliceComment #7
gabesulliceComment #8
wim leersThanks for putting all of this in one place. It sounds like a solid plan overall. I like how the first steps are very concrete, the last steps are less concrete, but already there is an outline for how it can and probably should work. 👌
Comment #9
gabesulliceAdding #3031214: Introduce "deterministic" normalizers to the IS
Comment #10
gabesulliceMoving to Drupal core
Comment #11
gabesullice#1 and #2 are done!
Comment #12
xjmThese would be minor-only changes. Since 8.9.x and 9.0.x are now in beta, I'm moving this to 9.1.x. Thanks!
Comment #16
wim leers#3031214: Introduce "deterministic" normalizers was closed in #3031214-19: Introduce "deterministic" normalizers because #3252872: Use CacheableSupportsMethodInterface for performance improvement in normalizers fixed it by using new upstream facilities! 🥳
Is #3014283: Use ResourceTypeFields to statically determine the field normalizer still relevant? 🤔
In any case, this issue summary needs an update for its step 3, because that is at least partially done now! :) And potentially also some of the later steps.
Comment #17
bradjones1Comment #21
bradjones1Comment #22
bradjones1Updated the IS. I _think_ the answer to #16 is no, it's no longer relevant, however I pinged @bbrala for his updated thinking since he was the last to comment on #3014283-47: Use ResourceTypeFields to statically determine the field normalizer.
Much of the IS update I made was to trim out all the extra work that would have been required to do our own implementation of the cacheable-supports logic that we got from Symfony upstream. The challenge with this issue is that it's been open so long, a lot of the underlying landscape has shifted a bit. Again, I _think_ this basically means we can start at what Gabe originally proposed as his #5, or #3031367: Generate JSON schema for content entity types. More specifically, we can tackle only the normalization side right now, since that's what the majority of schema generation is really focused on, and then perhaps tackle denormalization schema as a follow-on which would allow us to express different schemas for different HTTP verbs, e.g. in OpenAPI specs.