Skip to content
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

refactor: JSON:API #3971

Open
wants to merge 50 commits into
base: 2.x
Choose a base branch
from
Open

refactor: JSON:API #3971

wants to merge 50 commits into from

Conversation

SychO9
Copy link
Member

@SychO9 SychO9 commented Mar 8, 2024

Closes #3964

Changes proposed in this pull request:
This is a difficult review, I couldn't really find a way to split this since it can only be all done together, I would recommend looking at some specific resources, for example, comparing the main branch Discussion model JSON API with this one. The controllers and serializer class and command handlers vs the DiscussionResource class.

Reading the issue itself will help you a lot in getting context about the changes here. Other than that, some key things to think about:

  • Do we want to add out fork of the library into the monorepo? I'm leaning towards yes.
  • Any specific concerns about the new API? everything that an extension dev could do before should be able to do now as well, only differently. Once we start updating FoF extensions we will be able to see and make any necessary changes.

Some notable changes

  • The validation.yml file has been updated.
  • The discussions sortmap is no longer bound to the container, instead the SortColumn on the DiscussionResource can receive an ascendingAlias and descendngAlias which can them easily be accessed in the content classes, this also means extensions can add to the sortmap by just the sorts they add themselves in the same manner.

Todo

@SychO9 SychO9 marked this pull request as draft March 8, 2024 15:35
@SychO9 SychO9 marked this pull request as ready for review March 8, 2024 21:08
Copy link
Member

@luceos luceos left a comment

Choose a reason for hiding this comment

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

This is based off an initial reading from core changes, no extensions or implementation has been done.

For my understanding:

  • serializers have been replaced with resources, and resources contain not just endpoint instruction, but also field instructions; I really like that
  • many of the controllers have been simplified or removed entirely because of this refactor, which is also nice. I do think it will need some getting used to, to easily find where what is declared when looking things up.
  • Can we somehow get rid of all the Command/Handlers and move them to the respective controllers where possible or would that better be done in a follow up PR?

Overall this looks good, I will start doing some implementation attempts for the federation extension so that I can fully understand how things work etc.

Comment on lines +50 to +58
public function getId(object $model, \Tobyz\JsonApiServer\Context $context): string
{
return '1';
}

public function id(\Tobyz\JsonApiServer\Context $context): ?string
{
return '1';
}
Copy link
Member

Choose a reason for hiding this comment

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

For multi tenant communities, it would be helpful if this value could actually be set. So that if any json is visible to the world, it provides a human readable identifier. An option here would be to take that ID from the config.php if set, or fallback to 1. Unless this is extensible, it feels like a hardcoded value that might byte us in the future..

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good idea, but not really tied to this PR. It was hardcoded as 1 before this aswell in the ForumSerializer.

Let's do that in a separate issue to not go out of scope 👍🏼

@SychO9
Copy link
Member Author

SychO9 commented Mar 13, 2024

serializers have been replaced with resources, and resources contain not just endpoint instruction, but also field instructions

Yes, and columns. Filters have been disabled since we have our own driver-based filter implementation (which we can always improve in terms of boilerplate as well).

many of the controllers have been simplified or removed entirely because of this refactor, which is also nice. I do think it will need some getting used to, to easily find where what is declared when looking things up.

Keep in mind, you can still create a separate controller the old fashion way, only there are non of the previous parent serializer classes. So the result of it cannot be the serialization of a resource. You can also just use a custom endpoint (see user resource delete avatar).

Can we somehow get rid of all the Command/Handlers and move them to the respective controllers where possible or would that better be done in a follow up PR?

They are still useful reusable domain logic, for example check the delete avatar custom endpoint in the user resource class. Now that most controllers have been removed, command handlers aren't actually bad to have in terms of boilerplate. An additional improvement to them would be to make them one class rather than requiring two classes imo.

@SychO9
Copy link
Member Author

SychO9 commented Mar 14, 2024

documentation added: flarum/docs#472

@luceos
Copy link
Member

luceos commented Mar 20, 2024

There's two things I'd love to be able to do:

  • allow custom paths that aren't prefixed with /api, eg /.well-known/webfinger
  • allow a callback to be executed before payload is send to the user, so I can flatten the response or filter some values. I considered a middleware, but I'm not sure this feature could be applied for other purposes than mine (flattening the json payload by pushing attributes one level up). And yes this is a hack, but extensibility is about flexibility too.

Overall using this is very nice, it reminds me a lot of the filament resources logic. I have had no issue declaring the resources, their endpoints and their schema. So 👍

@SychO9
Copy link
Member Author

SychO9 commented Mar 20, 2024

allow custom paths that aren't prefixed with /api, eg /.well-known/webfinger

That would mean adding a route to the forum rather than the API, it's quite messy and would require quite a lot of changes.

This is an edge case where I would rather you use a normal controller. Does this endpoint require the api resourceat all? if it returns custom json structure that isn't actually of the JSON:API spec for resources, all the more reason to use a normal controller instead.

If it does return valid json for a resource, then checkout the ShowForumController controller and how you can call an api resource's endpoint.

allow a callback to be executed before payload is send to the user, so I can flatten the response or filter some values.

You can use the response method on the endpoint. Though again, if you are returning non json api spec json, what you might be needing to do instead is using custom controllers.

@SychO9 SychO9 requested a review from luceos March 20, 2024 15:58
Copy link
Sponsor Member

@askvortsov1 askvortsov1 left a comment

Choose a reason for hiding this comment

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

Overall this looks really nice! I've looked through all the extension changes, will try to do core next time.

extensions/approval/extend.php Outdated Show resolved Hide resolved
(new Extend\ApiController(ListPostsController::class))
->addInclude(['flags', 'flags.user']),
(new Extend\ApiResource(Resource\DiscussionResource::class))
->endpoint(Endpoint\Show::class, function (Endpoint\Show $endpoint) {
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Maybe in the docs it would be nice to have some guidelines for when to use default includes vs not.

Copy link
Member Author

Choose a reason for hiding this comment

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

docs actually recommend avoiding default includes whenever possible. Otherwise endpoints become unnecessarily bloated which easily harms performance. I have noticed for example some extensions adding default includes to endpoint A in order to display a relation in frontend page X, only for frontend page B to now unnecessarily have an api call that includes a relation it does not need (which often leads to more issues when you consider lack of eager loading).

With multiple extensions, it all builds up to a pretty bad effect.

Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Yeah this makes sense. I meant more like "default includes are almost always bad, here's why, here's what might qualify as an exception but seriously please don't"

extensions/flags/src/Api/ForumResourceFields.php Outdated Show resolved Hide resolved
return Flag::class;
}

public function query(Context $context): object
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

We need to override this to add the group by?

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, would be better to have something like this wouldn't it

Endpoint\Index::make()
    ->constrain(fn ($query) => $query->groupBy('post_id')),

extensions/subscriptions/src/Api/UserResourceFields.php Outdated Show resolved Hide resolved
public function __invoke(): array
{
return [
Schema\Boolean::make('canTag')
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

I wonder if it would make sense to have a Schema\Permission

Copy link
Member Author

Choose a reason for hiding this comment

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

Why not, the best part of this library is how much we can add on top that adds to the developer experience.

framework/core/locale/validation.yml Outdated Show resolved Hide resolved
@SychO9 SychO9 requested a review from askvortsov1 March 28, 2024 19:25
Copy link
Sponsor Member

@askvortsov1 askvortsov1 left a comment

Choose a reason for hiding this comment

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

Amazing job, this new API is such a great transformation!

Left some more comments. Some of these are clarifications / stuff I've gotten a bit rusty one, and some more might be better noted and saved for a future feature.

Thanks for putting this together, it's really exciting to see!

framework/core/src/Api/ApiServiceProvider.php Show resolved Hide resolved
framework/core/src/Api/Context.php Show resolved Hide resolved
framework/core/src/Api/Context.php Show resolved Hide resolved
}),

Schema\DateTime::make('markedAllAsReadAt')
->visible(fn (User $user, Context $context) => ($context->collection instanceof self || $context->collection instanceof ForumResource) && $context->getActor()->id === $user->id)
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Can we factor this logic out into a helper function?

framework/core/src/Api/Sort/SortColumn.php Show resolved Hide resolved

class PopulateWithActor implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

This (and iirc translation locales) are one of the few global bits of per-request state that make using octane difficult. Maybe in another pr, it would make sense to store all them in some centralized place so it's easier to keep track of and eventually eliminate?

framework/core/src/Http/RequestUtil.php Show resolved Hide resolved
Copy link
Sponsor Member

@askvortsov1 askvortsov1 left a comment

Choose a reason for hiding this comment

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

SychO9 and others added 6 commits May 9, 2024 13:19
# Conflicts:
#	extensions/likes/tests/integration/api/ListPostsTest.php
#	extensions/mentions/tests/integration/api/ListPostsTest.php
#	framework/core/src/Api/routes.php
#	framework/core/src/Discussion/UserState.php
#	framework/core/src/Extend/Settings.php
#	framework/core/tests/integration/extenders/ApiControllerTest.php
#	framework/core/tests/integration/extenders/ModelTest.php
#	framework/core/tests/integration/policy/DiscussionPolicyTest.php
@SychO9
Copy link
Member Author

SychO9 commented May 11, 2024

Will merge after the DB PRs are merged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

JSON:API Layer Refactor
4 participants