From the "path to core" issue, we need to:

  1. Document architecture + what the PHP API is + what the HTTP API is
  2. Re-analyze codebase to make as much as possible @internal

We've already gotten a start on this with the addition of jsonapi.api.php by #2860350: Document why JSON API only supports @DataType-level normalizers.

However, we should take a closer, more holistic look at the module here (not just focused on normalizers) and very precisely document our API guarantees.

We then need to take a fine-toothed comb to every class, interface, hook and service to ensure that they are properly documented and marked as @internal.

Comments

gabesullice created an issue. See original summary.

gabesullice’s picture

Status: Active » Needs review
StatusFileSize
new8.46 KB

The attached patch further clarifies the intent of some of our design decisions and clarifies the scope of the module API.

@e0ispo, please pay special attention to the changes I made to the normalizer docs.

gabesullice’s picture

Files not marked as @internal (excluding test classes):

  • src/ResourceType/ResourceType.php
  • src/LinkManager/LinkManager.php
  • src/ResourceType/ResourceTypeRepositoryInterface.php
  • src/StackMiddleware/FormatSetter.php
  • src/EntityToJsonApi.php
  • src/Normalizer/RelationshipItemNormalizer.php
  • src/Normalizer/UnprocessableHttpEntityExceptionNormalizer.php
  • src/Normalizer/NormalizerBase.php
  • src/Normalizer/FieldItemNormalizer.php
  • src/Normalizer/EntityReferenceFieldNormalizer.php
  • src/Normalizer/HttpExceptionNormalizer.php
  • src/Normalizer/JsonApiDocumentTopLevelNormalizer.php
  • src/Normalizer/ConfigEntityNormalizer.php
  • src/Normalizer/RelationshipNormalizer.php
  • src/Normalizer/ContentEntityNormalizer.php
  • src/Normalizer/EntityNormalizer.php
  • src/Normalizer/EntityAccessDeniedHttpExceptionNormalizer.php
  • src/Normalizer/FieldNormalizer.php
  • src/DependencyInjection/Compiler/RemoveJsonapiFormatCompilerPass.php
  • src/Exception/EntityAccessDeniedHttpException.php

ResourceType and LinkManager are both explicitly marked with @api. Do we want that?

From Slack:

gabesullice [1:06 PM]

@e0ipso why are ResourceTypes marked with `@api`?

e0ipso [1:08 PM]

@gabesullice one can override those with their implementation. In fact, that's the supported way to provide API public names that differ from the internal names.
@gabesullice check JSON API Extras for an example of that.

gabesullice [1:12 PM]

hm, okay. I think that changes the language of some of the docs we've written
We can't say that we have no PHP API then [corrected]
I figured that JSON API Extra was just replacing the resource type repository service and accepting the fact that "things could change"
so it was committed to staying in step with the module
regardless if the API changed underneath it

wim leers’s picture

  1. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + * specification (the spec). By its own definition, the JSON API spec is "is a
    

    "specification (the spec)" looks weird to my eyes?

  2. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + * While "Drupal-centric", the JSON API is committed to strict compliance with
    

    s/is committed/module is committed/

  3. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + * to Drupal. However, when "Drupalisms" cannot be reconciled with the spec,
    + * the module will always choose the implementation most faithful to the spec.
    

    Nice!

  4. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    - * bundle as a resource type and every entity reference field as a relationship.
    ...
    + * The JSON API module maps every entity type and bundle to a resource type.
    

    This can be misinterpreted, the current text is clearer.

  5. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + * Since the spec does not have a concept of resource type inheritance or
    + * composition, the JSON API module implements different bundles of the same
    + * entity type as *distinct* resource types.
    

    Excellent addition!

  6. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + * JSON API module only derives resources from (config and content) entities.
    

    "derives" is confusing in this context IMHO. "expose" would be clearer.

  7. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + * the JSON API module defines every entity type + bundle as a resource type and
    

    "entity type + bundle as a resource type" -> this is clearer, perhaps you can phrase it like this above as well?

  8. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + * normalizers at the "DataType" level. This is a level below "FieldType"
    + * plugins. Modules which do not implement normalizers at this level risk that
    

    "DataType" plugin level

    Also: it's not a risk, it's a certainty.

  9. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + * their normalizers wll not be used by the JSON API module.
    

    will

  10. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + * designed to be configuration free.
    

    Nit: I think "zero configuration" is clearer.

  11. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + *   "DataType" plugin level are supported (these are a level below fields).
    

    While we're clarifying this anyway: s/fields/field types/

  12. +++ b/jsonapi.api.php
    @@ -9,81 +9,113 @@
    + * The JSON API module does provide a PHP API to generate JSON API compliant
    + * serialization of entities:
    

    to generate a JSON API representation of an entity

wim leers’s picture

#3:

  • The EntityToJsonApi class should be marked @api.
  • All those normalizers should be marked @internal.
  • The compiler pass and middleware should be marked @internal.
  • The entity denied access exception should be marked @internal.
  • And most controversially, I agree with @gabesullice that ResourceType, LinkManager and ResourceTypeRepositoryInterface should all be @internal. Just because something is necessary for jsonapi_extras (which itself is highly in flux) doesn't mean that it should be a public API that the JSON API module commits to supporting, let alone that Drupal core will commit to supporting. Especially because there's only a single consumer. Like Gabe, I think that it should be up to the JSON API Extras module to stay in sync. This doesn't mean we'll make just any change — we should make sure that JSON API Extras doesn't break horribly, so that JSON API Extras can support the current core minor and the previous core minor. But making it an actual API not only is a maintainability risk, it also limits JSON API Extras in the future. Because, effectively, this is an "API" only to allow the JSON API Extras module to cleanly do its thing. But once JSON API is in core, other modules might start depending on it too, and then we won't be able to make changes just to accommodate JSON API Extras anymore. This strategy can work fine: I used it in https://www.drupal.org/project/big_pipe_sessionless too. To enable that module, I refactored core's BigPipe code to allow the BigPipe Sessionless module to reuse maximally, and inject extra logic in just the right places. But there is no official API that the BigPipe module maintains. The burden is on me, maintainer of https://www.drupal.org/project/big_pipe_sessionless, to keep it in sync. And that's totally doable :)
e0ipso’s picture

If we do that it means that no one but JSON API Extras can make use of what it's now marked as @api moving forward. I can accept that, but I'd like to see that reflected in the docs here.

Also, this will be a breaking change. Let's cut jsonapi-8.x-2.x.

wim leers’s picture

I can accept that, but I'd like to see that reflected in the docs here.

+1, this needs to be documented of course.

Also, this will be a breaking change.

What if we just mark those things that are currently @api but are becoming @internal as @deprecated? Then it's a deprecated API, and everything remains as-is. When JSON API moves into core, the deprecation takes effect. Result: existing users relying on this (likely VERY few) are not disrupted but are warned, and when it moves into core, it comes into effect. Thoughts?

e0ipso’s picture

I have no strong feelings about #7. We'd be making a new major version when moving into core then. Which is fine by me, but I think it keeps us more honest if we do it as soon as possible.

gabesullice’s picture

"specification (the spec)" looks weird to my eyes?

I don't like writing "the spec", this made me feel better about it. I think I'll just get rid of the shortening elsewhere.

Also: it's not a risk, it's a certainty.

Thanks, I was not sure about that.


The attached patch only has verbiage changes. It doesn't have any of the changes from #3 or #5.

gabesullice’s picture

StatusFileSize
new8.54 KB
new5.75 KB

Bah, forgot the patch.

e0ipso’s picture

The attached patch only has verbiage changes. It doesn't have any of the changes from #3 or #5.

Please make sure to also include the changes requested in #6.

gabesullice’s picture

Issue tags: +Needs release note
StatusFileSize
new35.54 KB
new20.5 KB

The attached marks all classes as internal, except for:

  • EntityToJsonApi, which was explicitly marked as @internal
  • ResourceType*, which were marked as @deprecated
  • LinkManager, which was marked as @deprecated

This calls for a special release note:

Drupal\jsonapi\ResourceType\ResourceType and Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface are deprecated. Modules which extend from ResourceType or implement ResourceTypeRepositoryInterface in order to modify the behavior of the JSON API module should be aware that future compatibility is no longer guaranteed. The JSON API Extras module is the recommended alternative to modify the behavior of the JSON API module for the foreseeable future.


I'm unclear why the LinkManager was ever marked as @api, so I'm not sure how to word a release note for that. What features would we be foregoing there?

e0ipso’s picture

@gabesullice as noted in #2857730-10: Add a link to get only published entities in collections I think that true HATEOAS needs to populate links from within the application (it's not possible to do it from the module itself).

The link manager is the service the actual sites will use to create those links properly.

That poses the question of: are we sure we want to remove it?

gabesullice’s picture

Thanks for the context @e0ipso. I wasn't sure what the use case would be. It makes sense that applications should be able to add/remove link relations... but we really aren't exposing that as an API here. Given the methods on this class, it looks like we're really only letting a hypothetical consumer of this API change the self and pagination links... which are very closely coupled to the rest of our implementation.

If we actually had an interface like JsonApiLinkProviderInterface::getResourceLinks(): LinkInterface and LinkInterface::getRel(): string and LinkInterface::getUrl(): Url then my opinion would be different.

So, given the very limited usefulness of this API and the high degree of coupling to our implementation, my preference would be to deprecate this and make a follow-up to implement a more HATEOS friendly API.

wim leers’s picture

#10: 👍


#12:

  1. +++ b/src/LinkManager/LinkManager.php
    @@ -13,7 +13,7 @@ use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
    - * @api
    + * @deprecated
    

    Hm, I think we should keep @api and add @deprecated?

  2. +++ b/tests/src/Kernel/Controller/EntityResourceTest.php
    @@ -32,6 +32,8 @@ use Symfony\Component\Routing\Route;
    + * @internal
    
    +++ b/tests/src/Kernel/Controller/EntryPointTest.php
    @@ -10,6 +10,8 @@ use Drupal\Tests\jsonapi\Kernel\JsonapiKernelTestBase;
    + * @internal
    
    +++ b/tests/src/Kernel/EntityToJsonApiTest.php
    @@ -21,6 +21,8 @@ use Prophecy\Argument;
    + * @internal
    

    Aren't tests always @internal? I guess it doesn't hurt to be very explicit.


#13 + #14: I sort of agree with both of you on that one :D I agree with @e0ipso that it should be possible to define additional links on resources. But at the same time, I agree with @gabesullice about LinkManager not really qualifying as an API. Apparently #2907123: Mark LinkGenerator and ResourceType as API classes marked LinkManager as @api. The justification there was If a module wants to generate the URI to the jsonapi representation of an entity, it must use the LinkGenerator class. — which is not quite correct. The correct way is to get the URL for the jsonapi.entityType--bundle.individual route. That's not very practical though. So the next best way to get that URL is to do something like \Drupal::service('jsonapi.entity.to_jsonapi')->normalize($entity)['data']['links']['self']. But the real solution would be #2878463: [PP-1] Define a JSON API link relation for entities.

Which is why I agree that LinkManager should be marked @deprecated: not only is using LinkManager::getEntityLink() a hard-to-find API to get a URL for an entity (for that we have $entity->toUrl('some-link-relation-type'), more importantly, LinkManager itself is not extensible: it's impossible to define additional links to be added, due to its design.

wim leers’s picture

Assigned: Unassigned » e0ipso
Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs documentation

IMHO this is ready. @e0ipso should be the one to do final review and commit this.

e0ipso’s picture

Assigned: e0ipso » Unassigned

Yeah, @gabesullice is right about LinkManager. I was speaking about what I hoped it had become through iterations and support requests that never happened. That indicates that people don't care about links or HATEOAS, which makes me a bit sad. But it's a reality I embrace by making it an internal API.

For the record, I think #2878463: [PP-1] Define a JSON API link relation for entities will be a step forward, but not enough. I may want to add a link to the collection of line items in stock inside of the JSON API representation of the shopping cart (aka a link to a collection with filters and all the fanfare)… but one step at a time.

I will be merging this soon as is. Thanks for all the work and discussions.

wim leers’s picture

I was speaking about what I hoped it had become through iterations and support requests that never happened. That indicates that people don't care about links or HATEOAS, which makes me a bit sad. But it's a reality I embrace by making it an internal API.

Yep (hope/aspirations), yep (sad reality) and yep (embracing sad reality).

I may want to add a link to the collection of line items in stock inside of the JSON API representation of the shopping cart (aka a link to a collection with filters and all the fanfare)… but one step at a time.

That's interesting :) It'll be hard to provide a simple/generic API for this. "Get the URL for the individual JSON API representation of this entity" is simple. "Get this particular set of entities meeting a particular set of conditions" is complex. That'll be an interesting challenge :) I suspect that it'll become clearer how we can/should achieve that as we gain more real-world experience and examples.

but one step at a time.

e0ipso’s picture

Status: Reviewed & tested by the community » Fixed

Committed!

  • e0ipso committed da9b386 on 8.x-1.x authored by gabesullice
    Issue #2933343 by gabesullice, Wim Leers, e0ipso: Define, document and...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.