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

Resource prototypes #16

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 237 additions & 0 deletions rfcs/resource-prototypes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
---
RFC: XXXX
Author: Egor Baranov
Status: Draft
Created: 2017-05-03
Last Modified: 2017-05-03
---

# API Blueprint RFC XXXX: Resource prototypes

## Table of Contents

- [Abstract](#abstract)
- [Motivation](#motivation)
- [Rationale](#rationale)
- [Backwards Compatibility](#backwards-compatibility)

## Abstract

This RFC proposes an ability to craete resource prototypes and use it to reduce
code duplication.

## Motivation

When you describe big API several resources can have identical responses.
Good examples of such problem are:

* request format checks - when several requests can return common error like
`WrongFormat`
* limit rate - when API checks how many requests have user done during some
period of time and any request can return error like `LimitExceeded`
* authorization checking - some part of API can require authorization so several
actions can return HTTP 403 forbidden
* deferred actions - when API request don't perform its job synchronously but
put them into queue and return an identifier back to user; such actions will
have same response format

All these cases leads to code duplication: you have to write the same response
again and again. This problem can be cause of misspellings and logical errors.
So ability to add common responses and headers in one place for multiple actions
is required.

## Rationale

### Resource prototypes

One of the possible solutions to reduce duplication is creating resource
prototype and setting it to
[resource](https://github.com/apiaryio/api-blueprint/blob/master/API%20Blueprint%20Specification.md#resource-section)
or
[resource group](https://github.com/apiaryio/api-blueprint/blob/master/API%20Blueprint%20Specification.md#def-resourcegroup-section)

We have complicated API with several duplicate responses:

```apib
# Data Structures

## Post

+ title: `test` (string, required)
+ body: `hello world` (string, required)

## User

+ name: `John Smith` (string, required)
+ email: `[email protected]` (string, required)

## Posts [/posts]

### List posts [GET]

+ Response 200
+ Attributes (array[Post], required, fixed-type)

+ Response 429 (application/json)
+ Attributes
+ status: tooManyRequests (string, required, fixed)
+ waitFor: `10` (number, required) - wait before next request

+ Response 400 (application/json)
+ Attributes
+ status: badRequest (string, required, fixed)
+ errors (array[string], required, fixed-type) - list of errors in request

### Show post [GET]

+ Response 200
+ Attributes (Post, required)

+ Response 429 (application/json)
+ Attributes
+ status: tooManyRequests (string, required, fixed)
+ waitFor: `10` (number, required) - wait before next request

+ Response 400 (application/json)
+ Attributes
+ status: badRequest (string, required, fixed)
+ errors (array[string], required, fixed-type) - list of errors in request

## Users [/users]

### List users [GET]

+ Response 200
+ Attributes (array[User], required, fixed-type)

+ Response 429 (application/json)
+ Attributes
+ status: tooManyRequests (string, required, fixed)
+ waitFor: `10` (number, required) - wait before next request

+ Response 400 (application/json)
+ Attributes
+ status: badRequest (string, required, fixed)
+ errors (array[string], required, fixed-type) - list of errors in request

+ Response 403 (application/json)
+ Attributes
+ status: forbidden (string, required, fixed)

### Create user [POST]

+ Response 200
+ Attributes (User, required)

+ Response 429 (application/json)
+ Attributes
+ status: tooManyRequests (string, required, fixed)
+ waitFor: `10` (number, required) - wait before next request

+ Response 400 (application/json)
+ Attributes
+ status: badRequest (string, required, fixed)
+ errors (array[string], required, fixed-type) - list of errors in request

+ Response 403 (application/json)
+ Attributes
+ status: forbidden (string, required, fixed)
```

As you can see responses 429, 400 and 403 are duplicated several times. It is
possible to extract response description into
[Data Structures](https://github.com/apiaryio/api-blueprint/blob/master/API%20Blueprint%20Specification.md#def-data-structures)
section and it will help to reduce duplication but you have to write something
like

```apib
+ Response 429 (application/json)
+ Attributes (TooManyRequestsError)
```

for every resource. It is possible to remove duplication completely using new
proposed syntax:

```apib
# Data Structures

## Post

+ title: `test` (string, required)
+ body: `hello world` (string, required)

## User

+ name: `John Smith` (string, required)
+ email: `[email protected]` (string, required)

## Authorized (Resource Prototype)

+ Response 403 (application/json)
Copy link
Member

@kylef kylef Jul 26, 2017

Choose a reason for hiding this comment

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

I do not like how we're adding serialisation into data structures. Data structures are already decoupled from the serialisation and this feels like a step backwards. The point of creating data structures is that they would be decoupled from the serialisations.

Data Structures are agnostic to serialisation of media types. With MSON, I can describe the data and defer the decision whether they will be send as JSON, XML or HAL over the wire.

I had some ideas about decoupling the serialisation and wrote then into #17 so we can decouple the serialisation from request/response bodies which could perhaps fit in here. But this would likely be more part of the larger picture of abstracting the protocol layer from API Blueprint.

Copy link
Author

Choose a reason for hiding this comment

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

@kylef imho you are trying to solve another problem. I see two independent problems:

  1. do not repeat identical responses (problem which I want to solve)
  2. decouple mson from http protocol (problem which you want to solve)

I think we shouldn't mix these problems. I just want to solve small problem which would help to reduce api description dramatically. Decoupling is a bigger problem which can help to describe different API (not only JSON HTTP API) and it requires much more changes so I think it's a good idea to solve it now. My changes do not affect decoupling at all because it just repeats current response structure in new section. When we find solution for bigger problem it will be easy to make changes in resource section and the same changes in resource prototype section but for now it would be logically to make the same structure in resource and resource prototype sections.

I thinks Data Structures is not the best place for Resource Prototype items so I prefer to create another section called Resource Prototypes and move all resource prototypes there. It will be more logically for users and also easier to implement.

@kylef @pksunkara what do you think about it?

Copy link
Author

Choose a reason for hiding this comment

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

@kylef I've created separate section for resource prototypes. Structure of resource prototype differs a lot from mson data structures, so it will be better for user to have resource prototypes in separate section because in this case API will be more readable and clear.

+ Attributes
+ status: forbidden (string, required, fixed)

## Common Resource (Resource Prototype)

+ Response 429 (application/json)
+ Attributes
+ status: tooManyRequests (string, required, fixed)
+ waitFor: `10` (number, required) - wait before next request

+ Response 400 (application/json)
+ Attributes
+ status: badRequest (string, required, fixed)
+ errors (array[string], required, fixed-type) - list of errors in request

# Group Blog (Common Resource)

## Posts [/posts]

### List posts [GET]

+ Response 200
+ Attributes (array[Post], required, fixed-type)

### Show post [GET]

+ Response 200
+ Attributes (Post, required)

## Users (Authorized) [/users]

### List users [GET]

+ Response 200
+ Attributes (array[User], required, fixed-type)

### Create user [POST]

+ Response 200
+ Attributes (User, required)
```

In `Data Structures` section two prototypes were defined: `Common Resource`
and `Authorized` with base type `Resource Prototype`. Both of them have some
responses. To assign prototype for all resources of resource group you need to
use such syntax: `Section name (Prototype Name)`. If section has nested section
then all resources of nested section will have responses defined in prototype.
`Users` resource group will have responces from `Common Resource` prototype
(because it's a prototype of its parent group) and from `Authorized` prototype.

It is possible to assign several prototypes for one resource or resource group:

```apib
# Group Blog (Common Resource, Authorized)
```

It is possible when one prototype has its own prototype:

```apib
# Data Structures

## Common Resource (Rate, Format)
```

## Backwards Compatibility

There will be no backward compatibility problems.