• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

vasilakisfil/Introspected-REST: An alternative to REST and GraphQL

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

vasilakisfil/Introspected-REST

开源软件地址(OpenSource Url):

https://github.com/vasilakisfil/Introspected-REST

开源编程语言(OpenSource Language):


开源软件介绍(OpenSource Introduction):

Introspected REST: An alternative to REST and GraphQL

In this manifesto, we will give a specific definition of what REST is, according to Roy, and see the majority of APIs and API specs (JSONAPI, HAL etc) fail to follow this model. We will see what problems a RESTful API brings and why API designers have been constantly avoiding using it but instead come up with half-way solutions or retreat to alternative models like RPC-over-HTTP or, lately, GraphQL. Then, we will propose a new model, Introspected REST, that solves the issues that REST creates and allows the design of progressively evolvable APIs, in a much simpler way than conventional REST. As part of this manifesto we will also present the concept of MicroTypes, small reusable modules that compose a Media Type and facilitate the evolvability and extensibility of our new model.

For the implementation of our new model in HTTP, we will have to go back in time, dig deep in existing RFCs and uncover forgotten concepts, like reactive content negotiation and Media Type parameters, in order to bend the existing Internet infrastructure, which has been mostly influenced by REST concepts, and successfully apply our new model.

1. Definitions

First some definitions, that we will use through the text:

  • REST, RESTful: The model that Roy defined in his thesis (along with his blog post REST APIs must be hypertext-driven).
  • RESTly: APIs that follows all parts REST model, except HATEOAS in which they support mostly links (specs like JSONAPI, HAL etc)
  • RESTless: APIs that have a plain JSON API without any links (follows REST model other than HATEOAS)
  • Introspected REST: APIs that follow the definition of the model we provide in this manifesto

We will use the term APIs and networked APIs interchangeably.

2. Introduction

REST defined by Roy was a magnificent piece of work, much ahead of its time which took us many years to understand what its capabilities are. However, now, almost 20 years later REST model shows its age. It's inflexible, difficult to implement, difficult to test, with performance and implementation issues. But most importantly, any implementation of REST model is very complex. Now, one could say that, most APIs are not build with mind to last for decades and maybe that's the reason that this model hasn't seen much adoption. The former is true but even if the latter is also true could it mean that this model is not suitable for short-term APIs?

We firmly believe that REST is much better than any API that does not follow REST principles (like RESTly APIs), even for short-term APIs. Networked services have very peculiar characteristics which, until now, only REST and GraphQL have fully addressed them. Being able to evolve our API without breaking the clients is critical.

Imagine the following scenario: we have built an Online Social Network and an iOS app that talks to the API on our backend. Now imagine that, after a company meeting, our CEO needs us to make tiny yet important change in the signup page: require the user to fill in her age, a field in the signup form we didn't have before. Essentially, this means, in API terms, add an extra field in the accepted object and require it from the client to be filled in by the user before sending it over.

If our API is RESTly and not REST, this means that we need to fix the code in the iOS side, test it and send a new iOS app to Apple store. It takes roughly 1 week for Apple to review our app and if our app doesn't get rejection during the review process for some reason, our tiny change will take action at least a week later after requested. If our API was REST, that would mean a simple change on the server's response, denoting which fields are required to submit the form. We would have the change deployed 10 minutes later.

Roy notes in his thesis:

A system intending to be as long-lived as the Web must be prepared for change

--- Roy Fielding

In the world of startups and money-driven companies, the following will sound more appealing:

If you want to move fast, you should build a change-first API.

An API that can change the state of the client without needing the latter to change.

Given that, how can we have a simpler model than REST, yet derive the same, if not more, functionality of REST? As we will show, Introspected REST is an API architectural style that solves that. An architectural style is not an implementation and it's not a spec either. As Roy notes:

An architectural style is a coordinated set of architectural constraints that restricts the roles/features of architectural elements and the allowed relationships among those elements within any architecture that conforms to that style.

--- Roy Fielding

Introspected REST is based on Roy's initial model but removes the need for runtime HATEOAS. Instead, the client derives the state on demand, using introspection, by retrieving the necessary metadata that are of interest. Eventually this brings the same advantages as Roy's model while being it's much simpler, much more flexible and backwards compatible with any RESTly or RESTless API.

But first let's discuss about Networked Services.

3. Networked Services and APIs

Nowadays JSON has become so popular that developers almost forget that there is whole bunch of protocols below it. Developers also forget that JSON is just a specification in the message level, like XML. It's not the only one and definitely it's not the best we could use. Nevertheless it's simple and simplicity is a virtue.

While OSI model is the conceptual model that we use to describe computer networks, with TCP/IP following 5 out of 7 OSI's abstraction layers, in our case, we will make a more API-specific description. When we want to request a resource from a networked hypermedia-based API, we roughly have the following levels:

3.1. Application level

In the application level, the client starts content negotiation (or content selection), usually asking for only one Media Type. A Media Type provides information about the structure of the content and the message format used in the data it describes, as described by RFC 2046.

In the HTTP the content negotiation is achieved by a client using the Accept header which denotes the Media Types that it can understand, in a preference order. Then the server responds with a Media Type proposed by the client in Content-Type header.

application/json is a Media Type that denotes that the data format of the requested representation is in JSON data format. Specifically the type of this Media Type is application while the subtype is json. JSON itself is not a Media Type but a message format.

Media Types can be a bit more complex as well: application/vnd.api+json, the media type of JSONAPI spec, (roughly) means that

  • the main type is application
  • the subtype is vnd.api which roughly denotes the Media Type name
  • the underlying structure follows JSON semantics

In theory, JSONAPI spec semantics could also be applied using XML as the data format (like in the case of HAL), or even YAML, however in practice we tend to forget that and we treat all Media Types as single and not composite.

However, it should also be noted that the Media Types and the content negotiation in general, are not restricted to HTTP only. Although HTTP is one of the most popular application network protocols today, the same logic could be applied in other (mostly text-based) protocols like SIP, CoAP, QUIC etc.

To sum up, the application level semantics are defined by the Media Type requested and should not be tightly coupled to the semantics of the message level (like JSON) or the underlying protocol level (like HTTP).

3.2. Message level

In the message level we find the format that is used for the actual representation. Nowadays we have almost mixed the message level with JSON but in practice other formats could successfully be used: XML, YAML, TOML to name a few.

Usually the message format that is used is described by the Media Type's suffix.

3.3. Protocol level

In the protocol level, the requests are usually sent using the HTTP. After all, nowadays most of the development happens around the Web and HTTP is the only protocol that browsers officially support.

Nonetheless, there are other similar stateless protocols as well. QUIC is a HTTP alternative protocol that is targeted for low latency and uses UDP underneath. CoAP is targeted in the IoT and also uses UDP underneath (full TCP/IP stack is quite heavy for constraint devices). SIP is also a text-based protocol with the same semantics as HTTP and is used in VoIP.

3.4. Network level

Finally, in the network level, the browser (or any other non-browser client) sends the networked request in one of the TCP, UDP, etc.

The actual protocol depends on the protocol used in the protocol level.

4. Roy's REST model

Roy came up with the REST model in order to solve issues that were arising by the unique properties of networked services during the infancy of Internet. When we develop an application that will be deployed in a networked environment and is expected to be accessed by other networked services, we need to think about its evolvability: if we need to add, remove or change functionality of that application we cannot expect services on the other end that talk with our application to be updated by humans. Such problems that arise from the peculiarities of networks, like discovery and evolvability must be solved using machine-to-machine communication.

REST model that solves such issues is an architectural style which is not tied to any spec, protocol or format of the aforementioned levels.

a RESTful API is just a website for users with a limited vocabulary (machine to machine interaction)

--- Roy Fielding

When Roy talks about REST, he mentions 5 crucial properties of REST model:

4.1. Access methods have the same semantics for all resources

induces visible, scalable, available through layered system, cacheable, and shared caches

Failure to provide consistency on access would imply that we don't provide a generic interface but instead we have resource-specific or even object-specific interfaces.

Actually a common interface is one of the most crucial parts of REST: without a common uniform interface it would be impossible to derive REST.

The central feature that distinguishes the REST architectural style from other network-based styles is its emphasis on a uniform interface between components. By applying the software engineering principle of generality to the component interface, the overall system architecture is simplified and the visibility of interactions is improved. Implementations are decoupled from the services they provide, which encourages independent evolvability. The trade-off, though, is that a uniform interface degrades efficiency, since information is transferred in a standardized form rather than one which is specific to an application's needs. The REST interface is designed to be efficient for large-grain hypermedia data transfer, optimizing for the common case of the Web, but resulting in an interface that is not optimal for other forms of architectural interaction.

In order to obtain a uniform interface, multiple architectural constraints are needed to guide the behavior of components. REST is defined by four interface constraints: identification of resources; manipulation of resources through representations; self-descriptive messages; and, hypermedia as the engine of application state.

--- Roy Fielding

Subsequently, the next 4 constraints, the core of REST, is a result of the effort to obtain a uniform interface between different components.

4.2. All important resources are identified by one resource identifier mechanism

induces simple, visible, reusable, stateless communication

Roy explains that very well in his thesis:

A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time.

More precisely, a resource R is a temporally varying membership function MR(t), which for time t maps to a set of entities, or values, which are equivalent. The values in the set may be resource representations and/or resource identifiers. [...] Some resources are static in the sense that, when examined at any time after their creation, they always correspond to the same value set. Others have a high degree of variance in their value over time. The only thing that is required to be static for a resource is the semantics of the mapping, since the semantics is what distinguishes one resource from another.

--- Roy Fielding

4.3. Resources are manipulated through the exchange of representations

induces simple, visible, reusable, cacheable, and evolvable (information hiding)

The representation that we expose from our public API could be totally different from our implementation internally or how the data are stored in our database. It could also be the same. Nevertheless the client expects and is expected to manipulate any resource using the representation we expose.

4.4. Representations are exchanged via self-descriptive messages

induces visible, scalable, available through layered system, cacheable, and shared caches induces evolvable via extensible communication

This would mean that the data of the response should follow the Media Type that the client requested and understands. Given that the client negotiated for that Media Type, it should be able to parse and understand any part of the response.

Interaction is stateless between requests, standard methods and Media Types are used to indicate semantics and exchange information, and responses explicitly indicate cacheability.

--- Roy Fielding

If our Media Type is very weak (like application/json) and we need functionality that the Media Type does not describe then we need to define another Media Type which will describe the new semantics and wait until client(s) incorporate the new Media Type changes.

Breaking our Media Type's semantics, or just extending them with new functionality, will have exactly the same result for the client: not self-descriptive messages that will require out-of-band information, like documentation.

4.5. Hypertext as the engine of application state (HATEOAS)

induces simple, visible, reusable, and cacheable through data-oriented integration induces evolvable (loose coupling) via late binding of application transitions

This is one of the most misunderstood parts of Roy's REST model. The idea here is that, once the client and server have reached a consensus on the Media Type after the negotiation, the server's response should strictly provide all the available options for the client to manipulate the resource and navigate to other resources, using semantics defined by the Media Type agreed.

As Roy notes:

A REST API should be entered with no prior knowledge beyond the initial URI (bookmark) and set of standardized Media Types that are appropriate for the intended audience (i.e., expected to be understood by any client that might use the API).

From that point on, all application state transitions must be driven by client selection of server-provided choices that are present in the received representations or implied by the user’s manipulation of those representations.

The transitions may be determined (or limited by) the client’s knowledge of media types and resource communication mechanisms, both of which may be improved on-the-fly (e.g., code-on-demand).

--- Roy Fielding

However, one of the requirements for HATEOAS to work is that the Media Type itself must allow to its vocabulary hypermedia. For instance, with application/json Media Type this wouldn't work as JSON itself (application/json Media Type is nothing more than a JSON) does not provide any of those mechanisms.

Instead, the server and client must agree on a format that provide such mechanisms.

Unfortunately though, the common practice is to put application/json in our Content-Type header denoting that the response type follows that Media Type and then inside the response we add semantics regarding hypermedia. Then we hand off out-of-band information to the client, like documentation, and demand to check them before identifying parsing and using the hypermedia semantics of our API.

5. API Clients and Applications

5.1. Client and Application responsibilities

Client and Application responsibilities some times are mixed together.

A client is responsible for understanding, interacting with the API and manipulating any API's resources, based on the Media Type's semantics and runtime HATEOAS. The client is responsible for providing in the application the list of resources that are available in the API, their fields, their capabilities, available actions and any hypermedia available.

The application responsibility on the other hand should not include API specific details. Instead, using the client, it should fetch whatever is needed by the application domain, within the API's capabilities.

Think about the traditional home telephone devices. The phone wire and its signals is the API. The device used to encode/decode the wire signals is the API client. On top of a device we can run our application. The PSTN, ISDN, (A)DSL etc are all different Media Types for the same API (wire signals). For each one, we need a client (device/modem) that will understand (encode/decode) the wire signals of that Media Type. Using that client we can built any type of application, in the feasible space of the Media Type. The application does not deal with the API's semantics, but instead it uses the Client to perform its tasks.

5.2. The Human factor principle

There are 2 types of human involvement when building an API client:

  • 1-fold: Programming the client only once to understand the Media Type correctly and let the client work for any API that follows that Media Type even when APIs evolve, given that it adhere in the Media Type specs. The only thing that the client requires is the initial URI of the API.
  • multi-fold: Programming the client once to understand the Media Type. Then modify the client to parse and understand the API correctly using some offline contract (i.e. documentation for available resources, fields, pagination etc) and then every time the API evolves (like adding a resource or a field), reprogram the client accordingly. The extend of human involvement during that phase depends on the weakness of the Media Type.

An API that follows the REST model should be evolvable without the need of human involvement in the client side, given that the client understands the Media Type. A side effect of such evolvability and 1-fold clients is that the versioning should not take place in the URL but in the Media Type itself.

Versioning an interface is just a polite way to kill deployed applications

The reason to make a real REST API is to get evolvability … a "v1" is a middle finger to your API customers, indicating RPC/HTTP (not REST)

Roy Fielding

6. REST applied in a modern API

When engineering a REST API, there are 2 approaches:

  • design a specialized, usually UI-driven, API: the resources and their browsability is tightly coupled with the specific application that was built for
  • design a generic, usually data-driven, API: the resources are more generic and the API's capabilities allow a plethora of transformations.

Specialized APIs could be more efficient, or have crucial advantageous characteristics for the domain that were built for since they are optimized only for that specific case. However, they pose difficulties when they need to be reused by any other application which does not share the same UI. As a result, such APIs are very special and a bit rare.

On the other hand, the data-driven APIs, are more generic and facilitate any application to request the data optimized (in the framework of the API's capabilities) for its use case. Being able to specify our application's needs when requesting data from an API is crucial, especially if our business depends on the adoptability of our API.

For the following subsections, we will mostly focus in the generic data APIs, however most of the things mentioned here can also be applied in a specialized or UI-driven API.

6.1. Requirements from a modern REST API

REST model is built for machine-to-machine communication, of any type. However, as this form of communication is getting more and more common, clients are expecting more options (capabilities) from the server for their responses. It's not enough to just request and get the resource but we should be able to specify to the server what transformations should apply, according to our needs. Nowadays we have been using networked APIs so much that now we essentially have to provide an ORM to the client over the HTTP (or any other protocol).

We provide here a list of features (we call them capabilities) that we think should be built in a modern networked API, in 2017.

6.1.1. Sparse fields (collection/resource)

The client should be able to ask and get specific attributes (i.e. a subset) of the resource representation. Also related, we should note that a representation of a resource could have completely different set of attributes for different clients, usually depending on the client's permissions or user role that it represents.

6.1.2. Associations on demand (collection/resource)

The client should be able to ask related associations to the main initial resource, in the same request.

What differentiates an association from an attribute is that the former has a dedicated identification. What is more, if the API exposes the association as a dedicated resource, the id can be used as identification.

6.1.3. Sorting & pagination (collection only)

The client should be able to sort based on one or more attributes and paginate the collection based on the page, page size and possibly an offset.

6.1.4. Filtering collections (collection only)

The client should be able to run any sort of collection filtering, as long as it does not pose any security threat or slows down the API performance.

6.1.5. Aggregation queries (collection only)

The client should be able to run any sort of aggregation queries, as long as it does not pose any security threat or slows down the API performance.

6.1.6. Data types !

The client should know the data types of the attributes of the requested representation of a resource. Message formats provide some data types but they are pretty basic. For instance, JSON defines String, Boolean, Number, Array, and null. Anything more than that we need to define it in the documentations.

We feel that these 5 data types that JSON provides are just a joke for modern APIs and that we should have a much larger list of options to select from. Additionally, we should be able to provide custom types in an easy way, for instance, a field is String but has maximum length of 255 characters, it follows a specific regex etc.

6.1.7 Plot twist: this list is endless

Although we feel that today these capabilities should exist in any modern API, this list is not exclusive. In fact, there could be capabilities in the future that might not seem necessary today. For example, joining together one or more resources, other database-inspired operations applied on resources, internationalization and localization of the data, HTTP/2 Server Push on some requests, Generic Event Delivery Using HTTP Push on other resources on specific states and other capabilities that we haven't even imagined yet. In any case, these capabilities must be transparent and self-descriptive to the client without any documentation or human involvement, other than programming the client to support the Media Type(s) and pointing it to the initial API URI.

6.2. Media Types vs HATEOAS

Now the reader could be wondering: where is the appropriate place to describe those capabilities, in our API's Media Type or using HATEOAS ? What goes where?

6.2.1. Defining a new Media Type is not easy and should be avoided

Creating a new Media Type for our API is generally considered bad practice. Create a new Media Type only if you are sure that none of the already published Media Types can fit in your API design.

Also, extending an existing Media Type or adding a complementing Media Type to an existing one (like application/vnd.api+json+my_custom_data_types) wouldn't work. Not only the existing Media Type specification does not provide any extensibility principles, but also, the main reason is that the client must understand the Media Type before hand. As a result, if we would like to use some new custom types in our (already deployed) API, we would have to publish the Media Type before hand and let humans implement code to fully parse API responses that follow this Media Type or API responses that their media type also include this new media type.

6.2.2. HATEOAS can get pretty heavy

Imagine if we have to describe in a resource, all the available actions along with the available API capabilities in that specific resource. Our API response would just explode in terms of size while making our API super complex.

6.2.3. Balancing between Media Types and HATEOAS

The idea is that Media Types describe the generic capabilities while HATEOAS describe the resource-specific capabilities.

However we should note that Media Types are not parsed by the client (there was never such intention anyway) which means that the client must be programmed by a human before hand in order to support that Media Type. As a result, the Media Type can't be very restrictive because that would mean it would restrict the API designer's freedom to design the API the way she wants.

For instance, for pagination, most RESTy APIs use a page and a per_page parameter in the URL. If the Media Type describes how to do pagination using, say, a URL template on the resource path (like /{resource}?page={page}&per_page={per_page}&offset={offset}) this would mean that all APIs following this Media Type should have the pagination following that URL template. The level of restriction becomes more obvious when describing more complex capabilities.

On the other hand, if everyone follows that Media Type then it's easier to program our clients. Specifically, especially when having a restrictive Media Type, if we create a client that parses responses using that Media Type then it's easy to "configure" it for another API which also follows the same Media Type.

Essentially, HATEOAS should complement the Media Type by providing the Media Type's hypermedia-ed defined semantics in runtime for the client to work properly. For instance, HATEOAS could describe on a per-resource basis if the pagination is supported, what is the maximum per_page etc.

6.2.4. An alternative architecture

We feel that the current Media Type specification and use is outdated. If Software Engineering has learned us something, is that composition can enforce Single Responsibility Principle, if used correctly. Inspired by that, later, we will suggest a new concept, MicroTypes, small reusable modules that combined together can form a Media Type. As a result, clients should be able to even negotiate parts of the Media Type and not the Media Type as a whole.

Also, instead of mixing up data with HATEOAS in the API responses, we will introduce introspectiveness of our resources.

7. API Specs Today

Now that we defined what REST is, according to Roy, what capabilities modern APIs should support, and where they should provide them, let's see the specs for REST(y) APIs available as today, September


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap