Skip to content

Reflection on API design with symfony #49842

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

Closed
soyuka opened this issue Mar 28, 2023 · 11 comments
Closed

Reflection on API design with symfony #49842

soyuka opened this issue Mar 28, 2023 · 11 comments
Labels
RFC RFC = Request For Comments (proposals about features that you want to be discussed)

Comments

@soyuka
Copy link
Contributor

soyuka commented Mar 28, 2023

I've reworded and changed some bits to an article https://soyuka.me/http-state-story.

@nicolas-grekas
Copy link
Member

nicolas-grekas commented Mar 28, 2023

Just to clarify on the relationship between MVC and Symfony, the doc says that:

Many modern web frameworks advertise themselves as being MVC frameworks.
[...] the Symfony Components are able to create any type of frameworks, not just the ones that follow the MVC architecture.

When creating a framework, following the MVC pattern is not the right goal.

So the arguments that we should put in that discussion shouldn't be about following any architectural style.

Instead, we strive for providing useful and extensible features.

On this ground, I would like to understand what the issue is with #49138 in your opinion (the discussion should likely happen there.)

I understand that a controller is a piece of action, where a resource is a piece of state. I also agree that building a resource-oriented HTTP framework is aligned with the design of the web and is a nice thing. But in any case, servers need to first "do" something to then present a resource. As such, a controller has to be the entry point of any resource-oriented design. Even if implemented as a listener, something that "reacts" to a request is a conceptually a controller.

FormParameter, QueryParameter, PathParameter, CookieParameter, HeaderParameter

PathParameter already exists but is implicit. I'm not sure we need an explicit variant of it.
QueryParameter is #49134 as you linked, and it is needed to me because it finishes the routing part of the controller layer - by terminating the URL-to-"action" mapping.

The other ones, I don't think they're needed: using the $request object instead works just fine.

@soyuka
Copy link
Contributor Author

soyuka commented Mar 28, 2023

I definitely agree, Symfony can be used as an MVC framework, but it is not an MVC framework. Still, today a Symfony route has a controller and this concept alone is at the heart of the framework. I liked it better when we started to speak about "actions". My point is that for API designs, it is wrong to use a single Controller, or the MVC pattern.

On this ground, I would like to understand what the issue is with #49138 in your opinion (the discussion should likely happen there.)

It's built around a controller, which is wrong on an architectural point of view for creating API endpoints. Accepting this feature (also worth for #49518) is accepting that the controller is at the heart of the architecture of an API endpoint, and encourages users to implement them like this. It feels that we want to enforce that Symfony is an MVC framework (which it is not). This will lead to bad design as the research shows.

Even if implemented as a listener, something that "reacts" to a request is a conceptually a controller.

The implementation details matters here. With the controller as presented in #49518 (or #49138), it is absolutely not extensible and doesn't permit any sort of composition to "do something" but only to "present a resource" which is then responsibility of the View (the separation of concern is important). I've written my concerns on each of these issues. To me, this issue is a legit place to talk about general architecture when it comes to API designs using Symfony and how we could provide solutions for users that want these features.

The other ones, I don't think they're needed: using the $request object instead works just fine.

Agreed, so does the serialize and the validate methods but it looks like the community wants to get better shortcuts and I see no harm to them. They need to be correctly implemented though.

@soyuka soyuka changed the title Symfony HTTP Kernel and the state of MVC On to improving API design with Symfony Mar 28, 2023
@derrabus derrabus added the RFC RFC = Request For Comments (proposals about features that you want to be discussed) label Mar 28, 2023
@ro0NL
Copy link
Contributor

ro0NL commented Mar 28, 2023

It's built around a controller, which is wrong on an architectural point of view for creating API endpoints.

i tend to disagree here, to me this is the fastest route of getting things done; that is responding to a request

it's reasonable to expect this from a first class http framework IMHO

also these features are opt-in, one is still free to use a meta-framework like api-platform or fos-rest-bundle on top, one is also free to propose first class REST support in symfony

(personally i'd like to see REST burried :))

to clarify further, to me

POST /api/message-type {payload}
GET /api/message-type?payload

is an API

@soyuka
Copy link
Contributor Author

soyuka commented Mar 29, 2023

#49138 MapRequestBody is actually really close from the old FrameworkExtraBundle ParamConverter. This lead to bad design IMO. It lead developers to do the following within each controllers:

  • validation
  • deserialization
  • content negotiation

Or you'd end up creating your own listeners, and actually re-create API Platform...

@ro0NL, you can do these things with API Platform pretty easily:

<?php

namespace App\ApiResource;

use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Post;

#[Get(uriTemplate: '/message-type', provider: [self::class, 'getMessageType'])]
#[Post(uriTemplate: '/message-type', processor: [self::class, 'processMessageType'])]
class MessageType
{
    public function __construct(public string $type) {}

    static public function getMessageType(Operation $operation, array $uriVariables = [], array $context = []) {
        return new self('test');
    }

    static public function processMessageType(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) {
        return $data;
    }
}

This supports validation, serialization, deserialization, content negotiation and even security. Providers/processors should be seen as your extension points and could be in their respective classes.

On the APIs you create with controllers, you never have to handle validation, deserialization, content-negotiation? How do you process currently? I'd love to see the architecture behind your APIs.

@ro0NL
Copy link
Contributor

ro0NL commented Mar 29, 2023

@ro0NL, you can do these things with API Platform pretty easily:

personally i find the example code in #49138 much more straightforward and sane

so i can do the things even more easily without need for external packages.

also i'm already moving away from such meta-frameworks that dont really help in terms of RAD (IMHO)

i fail to see to why we couldnt ship these features? who declared REST the holy grail? who says symfony must support either both flavors or none?

i fail to see what's blocking #49138 concretely, IMHO it's a good PR .. please merge it :)

TLDR: stop selling me ReSt

@soyuka
Copy link
Contributor Author

soyuka commented Mar 29, 2023

I'm not selling REST above, I just provide more bullet proof extension points to what the "controller". Also you didn't answer my question. How do you skip validating in the #49138 feature? How do you handle content negotiation? Yes it is good for Rapid Application Development, but when you scale it will lead to bad design.

@ro0NL
Copy link
Contributor

ro0NL commented Mar 29, 2023

How do you skip validating in the #49138 feature?

why would i want to skip validating user payloads? then don't define any validation contraints 🤷

How do you handle content negotiation?

we'd pass the preferred format to the serializer using $format = $request->getPreferredFormat(self::DEFAULT_FORMAT);, so you can register a normalizer to handle any format, if it's unsupported you get 415.

when you scale it will lead to bad design

why? what is "bad design" in GET /some/path responding with json?

@soyuka
Copy link
Contributor Author

soyuka commented Mar 29, 2023

But then you force users to use the Symfony Validator and the Symfony Serializer, we can not use the #[MapRequestBody] with something else then Symfony. This feature is broken by design.

I can't make all the arguments (and don't want to) I was hoping that by describing what's wrong with applying the "Controller" model to APIs, I'd manage to explain what's wrong with the #[MapRequestBody] or the #[Serialize] but it is not enough or maybe not the good approach.
That being said I don't want to pollute Symfony issues with that and will close this issue, with hope that the reading was interesting for some.

@soyuka soyuka closed this as not planned Won't fix, can't repro, duplicate, stale Mar 29, 2023
@dunglas
Copy link
Member

dunglas commented Mar 29, 2023

who declared REST the holy grail?

From the same page quoted by @nicolas-grekas:

The fundamental principles of the Symfony Components are focused on the HTTP specification. As such, the framework that you are going to create should be more accurately labelled as a HTTP framework or Request/Response framework.

HTTP is the main implementation of REST, so as an HTTP framework, Symfony is a REST framework:

Since 1994, the REST architectural style has been used to guide the design and development of the architecture for the modern Web. This chapter describes the experience and lessons learned from applying REST while authoring the Internet standards for the Hypertext Transfer Protocol (HTTP) and Uniform Resource Identifiers (URI), the two specifications that define the generic interface used by all component interactions on the Web, as well as from the deployment of these technologies in the form of the libwww-perl client library, the Apache HTTP Server Project, and other implementations of the protocol standards.

Roy Fielding, author of HTTP and of REST (https://www.ics.uci.edu/~fielding/pubs/dissertation/evaluation.htm)

@ro0NL
Copy link
Contributor

ro0NL commented Mar 30, 2023

so does/should this theory block #49138?

@B-Galati
Copy link
Contributor

To be more precise, HTTP is a protocol that enables REST architecture style but it's easy not to be REST compliant when using HTTP.

@soyuka soyuka changed the title On to improving API design with Symfony Reflection on API design with symfony Sep 26, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RFC RFC = Request For Comments (proposals about features that you want to be discussed)
Projects
None yet
Development

No branches or pull requests

6 participants