mirror of
https://github.com/twirl/The-API-Book.git
synced 2025-01-05 10:20:22 +02:00
Annex to Section I and corresponding fixes
This commit is contained in:
parent
8f31fa11b9
commit
7985d1380d
@ -1,4 +1,4 @@
|
|||||||
### On versioning
|
### On Versioning
|
||||||
|
|
||||||
Here and throughout we firmly stick to [semver](https://semver.org/) principles of versioning:
|
Here and throughout we firmly stick to [semver](https://semver.org/) principles of versioning:
|
||||||
1. API versions are denoted with three numbers, i.e. `1.2.3`.
|
1. API versions are denoted with three numbers, i.e. `1.2.3`.
|
||||||
|
@ -252,7 +252,7 @@ POST /v1/orders
|
|||||||
|
|
||||||
The `POST /orders` handler checks all order parameters, puts a hold of corresponding sum on user's credit card, forms a request to run, and calls the execution level. First, correct execution program needs to be fetched:
|
The `POST /orders` handler checks all order parameters, puts a hold of corresponding sum on user's credit card, forms a request to run, and calls the execution level. First, correct execution program needs to be fetched:
|
||||||
```
|
```
|
||||||
POST /v1/programs/match
|
POST /v1/program-matcher
|
||||||
{ "recipe", "coffee-machine" }
|
{ "recipe", "coffee-machine" }
|
||||||
→
|
→
|
||||||
{ "program_id" }
|
{ "program_id" }
|
||||||
@ -275,7 +275,7 @@ POST /v1/programs/{id}/run
|
|||||||
```
|
```
|
||||||
|
|
||||||
Please note that knowing the coffee machine API kind isn't required at all; that's why we're making abstractions! We could possibly make interfaces more specific, implementing different `run` and `match` endpoints for different coffee machines:
|
Please note that knowing the coffee machine API kind isn't required at all; that's why we're making abstractions! We could possibly make interfaces more specific, implementing different `run` and `match` endpoints for different coffee machines:
|
||||||
* `POST /v1/programs/{api_type}/match`
|
* `POST /v1/program-matcher/{api_type}`
|
||||||
* `POST /v1/programs/{api_type}/{program_id}/run`
|
* `POST /v1/programs/{api_type}/{program_id}/run`
|
||||||
|
|
||||||
This approach has some benefits, like a possibility to provide different sets of parameters, specific to the API kind. But we see no need in such fragmentation. `run` method handler is capable of extracting all the program metadata and perform one of two actions:
|
This approach has some benefits, like a possibility to provide different sets of parameters, specific to the API kind. But we see no need in such fragmentation. `run` method handler is capable of extracting all the program metadata and perform one of two actions:
|
||||||
|
@ -51,7 +51,7 @@ let recipes = api.getRecipes();
|
|||||||
// Retrieve a list of all available coffee machines
|
// Retrieve a list of all available coffee machines
|
||||||
let coffeeMachines = api.getCoffeeMachines();
|
let coffeeMachines = api.getCoffeeMachines();
|
||||||
// Build a spatial index
|
// Build a spatial index
|
||||||
let coffeeMachineRecipesIndex = buildGeoIndex(recipes, coffee-machines);
|
let coffeeMachineRecipesIndex = buildGeoIndex(recipes, coffeeMachines);
|
||||||
// Select coffee machines matching user's needs
|
// Select coffee machines matching user's needs
|
||||||
let matchingCoffeeMachines = coffeeMachineRecipesIndex.query(
|
let matchingCoffeeMachines = coffeeMachineRecipesIndex.query(
|
||||||
parameters,
|
parameters,
|
||||||
@ -69,7 +69,7 @@ A necessity of adding a new endpoint for searching becomes obvious. To design su
|
|||||||
|
|
||||||
Then our new interface would look like:
|
Then our new interface would look like:
|
||||||
```
|
```
|
||||||
POST /v1/coffee-machines/search
|
POST /v1/offers/search
|
||||||
{
|
{
|
||||||
// optional
|
// optional
|
||||||
"recipes": ["lungo", "americano"],
|
"recipes": ["lungo", "americano"],
|
||||||
@ -92,20 +92,20 @@ Here:
|
|||||||
* an `offer` — is a marketing bid: on what conditions a user could have the requested coffee beverage (if specified in request), or a some kind of marketing offering — prices for the most popular or interesting products (if no specific preference was set);
|
* an `offer` — is a marketing bid: on what conditions a user could have the requested coffee beverage (if specified in request), or a some kind of marketing offering — prices for the most popular or interesting products (if no specific preference was set);
|
||||||
* a `place` — is a spot (café, restaurant, street vending machine) where the coffee machine is located; we never introduced this entity before, but it's quite obvious that users need more convenient guidance to find a proper coffee machine than just geographical coordinates.
|
* a `place` — is a spot (café, restaurant, street vending machine) where the coffee machine is located; we never introduced this entity before, but it's quite obvious that users need more convenient guidance to find a proper coffee machine than just geographical coordinates.
|
||||||
|
|
||||||
**NB**. We could have been enriched the existing `/coffee-machines` endpoint instead of adding a new one. This decision, however, looks less semantically viable: coupling in one interface different modes of listing entities, by relevance and by order, is usually a bad idea, because these two types of rankings implies different usage features and scenarios.
|
**NB**. We could have been enriched the existing `/coffee-machines` endpoint instead of adding a new one. This decision, however, looks less semantically viable: coupling in one interface different modes of listing entities, by relevance and by order, is usually a bad idea, because these two types of rankings implies different usage features and scenarios. Furthermore, enriching the search with ‘offers’ pulls this functionality out of `coffee-machines` namespace: the fact of getting offers to prepare specific beverage in specific conditions is a key feature to users, with specifying the coffee-machine being just a part of an offer.
|
||||||
|
|
||||||
Coming back to the code developers are writing, it would now look like that:
|
Coming back to the code developers are writing, it would now look like that:
|
||||||
```
|
```
|
||||||
// Searching for coffee machines
|
// Searching for offers
|
||||||
// matching a user's intent
|
// matching a user's intent
|
||||||
let coffeeMachines = api.search(parameters);
|
let offers = api.search(parameters);
|
||||||
// Display them to a user
|
// Display them to a user
|
||||||
app.display(coffeeMachines);
|
app.display(offers);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Helpers
|
#### Helpers
|
||||||
|
|
||||||
Methods similar to newly invented `coffee-machines/search` are called *helpers*. The purpose they exist is to generalize known API usage scenarios and facilitate implementing them. By ‘facilitating’ we mean not only reducing wordiness (getting rid of ‘boilerplates’), but also helping developers to avoid common problems and mistakes.
|
Methods similar to newly invented `offers/search` are called *helpers*. The purpose they exist is to generalize known API usage scenarios and facilitate implementing them. By ‘facilitating’ we mean not only reducing wordiness (getting rid of ‘boilerplates’), but also helping developers to avoid common problems and mistakes.
|
||||||
|
|
||||||
For instance, let's consider the order price question. Our search function returns some ‘offers’ with prices. But ‘price’ is volatile; coffee could cost less during ‘happy hours’, for example. Developers could make a mistake thrice while implementing this functionality:
|
For instance, let's consider the order price question. Our search function returns some ‘offers’ with prices. But ‘price’ is volatile; coffee could cost less during ‘happy hours’, for example. Developers could make a mistake thrice while implementing this functionality:
|
||||||
* cache search results on a client device for too long (as a result, the price will always be nonactual);
|
* cache search results on a client device for too long (as a result, the price will always be nonactual);
|
||||||
@ -249,7 +249,7 @@ Let's try to group it together:
|
|||||||
|
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"results": {
|
"results": [{
|
||||||
// Place data
|
// Place data
|
||||||
"place": { "name", "location" },
|
"place": { "name", "location" },
|
||||||
// Coffee machine properties
|
// Coffee machine properties
|
||||||
@ -267,7 +267,7 @@ Let's try to group it together:
|
|||||||
"pricing": { "currency_code", "price", "localized_price" },
|
"pricing": { "currency_code", "price", "localized_price" },
|
||||||
"estimated_waiting_time"
|
"estimated_waiting_time"
|
||||||
}
|
}
|
||||||
}
|
}, …]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
167
src/en/clean-copy/02-Section I. The API Design/06.md
Normal file
167
src/en/clean-copy/02-Section I. The API Design/06.md
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
### Annex to Section I. Generic API Example
|
||||||
|
|
||||||
|
Let's summarize the current state of our API study.
|
||||||
|
|
||||||
|
##### Offer search
|
||||||
|
```
|
||||||
|
POST /v1/offers/search
|
||||||
|
{
|
||||||
|
// optional
|
||||||
|
"recipes": ["lungo", "americano"],
|
||||||
|
"position": <geographical coordinates>,
|
||||||
|
"sort_by": [
|
||||||
|
{ "field": "distance" }
|
||||||
|
],
|
||||||
|
"limit": 10
|
||||||
|
}
|
||||||
|
→
|
||||||
|
{
|
||||||
|
"results": [{
|
||||||
|
// Place data
|
||||||
|
"place": { "name", "location" },
|
||||||
|
// Coffee machine properties
|
||||||
|
"coffee-machine": { "brand", "type" },
|
||||||
|
// Route data
|
||||||
|
"route": { "distance", "duration", "location_tip" },
|
||||||
|
"offers": {
|
||||||
|
// Recipe data
|
||||||
|
"recipe": { "id", "name", "description" },
|
||||||
|
// Recipe specific options
|
||||||
|
"options": { "volume" },
|
||||||
|
// Offer metadata
|
||||||
|
"offer": { "id", "valid_until" },
|
||||||
|
// Pricing
|
||||||
|
"pricing": { "currency_code", "price", "localized_price" },
|
||||||
|
"estimated_waiting_time"
|
||||||
|
}
|
||||||
|
}, …],
|
||||||
|
"cursor"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Working with recipes
|
||||||
|
|
||||||
|
```
|
||||||
|
// Returns a list of recipes
|
||||||
|
// Cursor parameter is optional
|
||||||
|
GET /v1/recipes?cursor=<cursor>
|
||||||
|
→
|
||||||
|
{ "recipes", "cursor" }
|
||||||
|
```
|
||||||
|
```
|
||||||
|
// Returns the recipe by its id
|
||||||
|
GET /v1/recipes/{id}
|
||||||
|
→
|
||||||
|
{ "recipe_id", "name", "description" }
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Working with orders
|
||||||
|
|
||||||
|
```
|
||||||
|
// Creates an order
|
||||||
|
POST /v1/orders
|
||||||
|
{
|
||||||
|
"coffee_machine_id",
|
||||||
|
"currency_code",
|
||||||
|
"price",
|
||||||
|
"recipe": "lungo",
|
||||||
|
// Optional
|
||||||
|
"offer_id",
|
||||||
|
// Optional
|
||||||
|
"volume": "800ml"
|
||||||
|
}
|
||||||
|
→
|
||||||
|
{ "order_id" }
|
||||||
|
```
|
||||||
|
```
|
||||||
|
// Returns the order by its id
|
||||||
|
GET /v1/orders/{id}
|
||||||
|
→
|
||||||
|
{ "order_id", "status" }
|
||||||
|
```
|
||||||
|
```
|
||||||
|
// Cancels the order
|
||||||
|
POST /v1/orders/{id}/cancel
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Работа с программами
|
||||||
|
|
||||||
|
```
|
||||||
|
// Returns an identifier of the program
|
||||||
|
// corresponding to specific recipe
|
||||||
|
// on specific coffee-machine
|
||||||
|
POST /v1/program-matcher
|
||||||
|
{ "recipe", "coffee-machine" }
|
||||||
|
→
|
||||||
|
{ "program_id" }
|
||||||
|
```
|
||||||
|
```
|
||||||
|
// Return program description
|
||||||
|
// by its id
|
||||||
|
GET /v1/programs/{id}
|
||||||
|
→
|
||||||
|
{
|
||||||
|
"program_id",
|
||||||
|
"api_type",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"sequence_id",
|
||||||
|
"type": "set_cup",
|
||||||
|
"parameters"
|
||||||
|
},
|
||||||
|
…
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Running programs
|
||||||
|
|
||||||
|
```
|
||||||
|
// Runs the specified program
|
||||||
|
// on the specefied coffee-machine
|
||||||
|
// with specific parameters
|
||||||
|
POST /v1/programs/{id}/run
|
||||||
|
{
|
||||||
|
"order_id",
|
||||||
|
"coffee_machine_id",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "volume",
|
||||||
|
"value": "800ml"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
→
|
||||||
|
{ "program_run_id" }
|
||||||
|
```
|
||||||
|
```
|
||||||
|
// Stops program running
|
||||||
|
POST /v1/runs/{id}/cancel
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Managing runtimes
|
||||||
|
|
||||||
|
```
|
||||||
|
// Creates a new runtime
|
||||||
|
POST /v1/runtimes
|
||||||
|
{ "coffee_machine_id", "program_id", "parameters" }
|
||||||
|
→
|
||||||
|
{ "runtime_id", "state" }
|
||||||
|
```
|
||||||
|
```
|
||||||
|
// Returns the state
|
||||||
|
// of the specified runtime
|
||||||
|
GET /v1/runtimes/{runtime_id}/state
|
||||||
|
{
|
||||||
|
"status": "ready_waiting",
|
||||||
|
// Command being currently executed
|
||||||
|
// (optional)
|
||||||
|
"command_sequence_id",
|
||||||
|
"resolution": "success",
|
||||||
|
"variables"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```
|
||||||
|
// Terminates the runtime
|
||||||
|
POST /v1/runtimes/{id}/terminate
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user