Wednesday, July 1, 2015

Restful Web Service Design



http://zalando.github.io/restful-api-guidelines/#_zalando_restful_api_and_event_scheme_guidelines
https://devcenter.heroku.com/articles/platform-api-reference#ranges

https://geemus.gitbooks.io/http-api-design/content/en/foundations/require-versioning-in-the-accepts-header.html
To prevent surprise, breaking changes to users, it is best to require a version be specified with all requests. Default versions should be avoided as they are very difficult, at best, to change in the future.

It is best to provide version specification in the headers, with other metadata, using the Accept header with a custom content type, e.g.:
Accept: application/vnd.heroku+json; version=3

https://geemus.gitbooks.io/http-api-design/content/en/requests/actions.html
Prefer endpoint layouts that don’t need any special actions for individual resources. In cases where special actions are needed, place them under a standard actions prefix, to clearly delineate them:
+

/resources/:resource/actions/:action
e.g.
/runs/{run_id}/actions/stop

Use downcased and dash-separated path names, for alignment with hostnames, e.g:
+

service-api.com/users
service-api.com/app-setups
Downcase attributes as well, but use underscore separators so that attribute names can be typed without quotes in JavaScript, e.g.:
service_class: "first"

Support non-id dereferencing for convenience

In some cases it may be inconvenient for end-users to provide IDs to identify a resource. For example, a user may think in terms of a Heroku app name, but that app may be identified by a UUID. In these cases you may want to accept both an id or name, e.g.:
$ curl https://service.com/apps/{app_id_or_name}
$ curl https://service.com/apps/97addcf0-c182
$ curl https://service.com/apps/www-prod
Do not accept only names to the exclusion of IDs.

Minimize path nesting

In data models with nested parent/child resource relationships, paths may become deeply nested, e.g.:
/orgs/{org_id}/apps/{app_id}/dynos/{dyno_id}
Limit nesting depth by preferring to locate resources at the root path. Use nesting to indicate scoped collections. For example, for the case above where a dyno belongs to an app belongs to an org:
/orgs/{org_id}
/orgs/{org_id}/apps
/apps/{app_id}
/apps/{app_id}/dynos
/dynos/{dyno_id}

Provide the full resource representation (i.e. the object with all attributes) whenever possible in the response. Always provide the full resource on 200 and 201 responses, including PUT/PATCH and DELETE requests

202 responses will not include the full resource representation

Provide resource (UU)IDs

Give each resource an id attribute by default. Use UUIDs unless you have a very good reason not to. Don’t use IDs that won’t be globally unique across instances of the service or other resources in the service, especially auto-incrementing IDs.
Render UUIDs in downcased 8-4-4-4-12 forma

Nest foreign key relations

Serialize foreign key references with a nested object, e.g.:
{
  "name": "service-production",
  "owner": {
    "id": "5d8201b0..."
  },
  // ...
}
Instead of e.g.:
{
  "name": "service-production",
  "owner_id": "5d8201b0...",
  // ...
}
This approach makes it possible to inline more information about the related resource without having to change the structure of the response or introduce more top-level response fields

Show rate limit status

Rate limit requests from clients to protect the health of the service and maintain high service quality for other clients. You can use a token bucket algorithm to quantify request limits.
Return the remaining number of request tokens with each request in the RateLimit-Remaining response header.

Keep JSON minified in all responses

Provide machine-readable JSON schema

Provide a machine-readable schema to exactly specify your API. Use prmd to manage your schema, and ensure it validates with prmd verify

In addition to endpoint details, provide an API overview with information about:
  • Authentication, including acquiring and using authentication tokens.
  • API stability and versioning, including how to select the desired API version.
  • Common request and response headers.
  • Error serialization format.
  • Examples of using the API with clients in different languages

Provide executable examples

If you use prmd to generate Markdown docs, you will get examples for each endpoint for free
https://www.infoq.com/news/2014/08/heroku-http-design-guide
The guide starts with a number of general recommendations:
  • Require all API calls to use TLS. Reply with 403 Forbidden to non-TLS ones
  • Always version an API. Use the Accept header to specify the version. Require clients to specify the API version instead of relying on a default one
  • Use the ETag header to support caching of resources
  • Identify each response with X-Request-ID; useful for logging or debugging purposes
  • Use Range, Content-Range, Next-Range to deal with large responses
Return appropriate status codes,
  • Return complete resources when available
  • Accept serialized JSON in requests for symmetry with JSON responses
  • Use consistent path formats. Resource names should use the plural form unless they refer to singletons
  • Use lowercasing for paths and attributes to keep in line with host names
  • Allow some resources, such as application names, to be specified by their name besides UUID
  • Try to limit the depth of resource nesting by allocating them closer to the root path
  • Use globally unique IDs for each resource?
  • Provide standard timestamps for resources
  • For timestamps, use UTC in format ISO8601
  • Nest foreign key relations to be able to include more details for the related resource without changing the structure of the response
  • Provide detailed errors, including a machine readable ID, a human readable error message and an optional URL pointing to more details
  • Establish a request rate limit and inform the client on the number of available request tokens with each response, using a header like RateLimit-Remaining
  • JSON should be minified in all responses
The guide includes more related advice:
  • Provide the JSON schema in a machine readable format
  • Include detailed APIs documentation for developers
  • Allow developers to test the APIs
  • APIs should be marked according to their stability status: prototype/development/production
https://zhuanlan.zhihu.com/p/34289466
两大原则:规范的(regular),可预测的(predictable)。
  1. 只使用名词
  2. 要有切入点
  3. 要合适的选择id形式
  4. 要表达资源间的联系
  5. 要支持查询
  6. 要支持返回部分资源
  7. 处理更复杂的计算逻辑
要有切入点:原则上来讲,一个api应该有一个root path '/', 其返回一个url map,包括了所有的resouces所对应的url。这样客户端更容易去发现和使用api。

要合适的选择id形式:例如api/dogs/{id} 中的{id}如何表达,是类似于‘/dogs/1’,还是‘/dogs/haha’? 一般来说取决于后端的数据库,大多数情况下,使用RDBMS,像mysql之类的主键自增功能,我比较倾向于使用自增主键的整数直接作为entity的id,避免很多问题,如果使用MongoDB的话,不妨试一试用字符串作为entity的id,可读性会提高,但是如何维护一个全局唯一的字符主键,你得三思。

要表达资源间的联系:比如 GET /persons/1/dogs 返回所有属于person 1的狗。
这种模式可以表述为:
/{relationship-name}[/{resource-id}]/…/{relationship-name}[/{resource-id}]

要支持查询

GET /persons;1/dogs GET /persons;name=blabla/dogs 
这种模式可以表述为:
/{relationship-name}[;{selector}]/…/{relationship-name}[;{selector}]
更复杂的查询条件:
GET /dogs?color=red&state=running&location=park 
注意这里的三个查询子条件之间的关系是‘与(and)’,如果要表达是‘或(or)’的逻辑,那就得设计更复杂的query解释机制。但其实实际使用中,表达‘或’的查询条件很少使用。
要支持返回部分资源:
/dogs?fields=name,color,location 
返回的resource中,只包含name,color,location三种信息。
处理更复杂的计算逻辑: 有很多例子,比如货币转换,大多数开发人员给出的方案是:
/convert/100/EUR/CNY 或者 /convert?quantity=100&unit=EUR&in=CNY
切记:URL是用来表述资源resource,而不是表述计算的过程。在URL中使用名词,避免动词。
改进的方案1:
GET /monetary-amount/100/EUR HTTP/1.1
Host: Currency-Converter.com
Accept-Currency:CNY //在http的header中添加Accept-Currency来指明货币的种类。
改进的方案2:
POST /currency-converter HTTP/1.1
Host: Currency-Converter.com 
Content-Length: 69
{
"amount": 100,
"inputCurrency": "EUR",
"outputCurrency": "CNY"
}
两个方案都可行,但是方案2有两个注意的地方:POST返回的结果可能无法再server端缓存;你是在构建一个计算的过程,而非资源的表述,如何理解?就像数据库操作中的‘store procedure’,你可以使用,并且功能强大,但是接口变得复杂,逻辑变得耦合。
添加self link:self link提供了一个上下文环境,客户端可以更容易理解当前的resource的位置
{
 "user": {
   "html_url": "octocat (The Octocat)",
   "type": "User",
   "url": "https://api.github.com/users/octocat"
 }
}

集合数据:

方案1:集合collection也是一种resource,也具有self和kind属性,这样所有的单独entity和collection都具有更加统一的规范
{
 "self": "https://dogtracker.com/dogs",
 "kind": "Collection",
 "contents": [
   {
    "self": "https://dogtracker.com/dogs/12344",
    "kind": "Dog",
    "name": "Fido",
    "furColor": "white"
   },
   {
    "self": "https://dogtracker.com/dogs/12345",
    "kind": "Dog",
    "name": "Rover",
    "furColor": "brown"
   }
  ]
}
方案2:看起来更加简洁,但是客户端可能需要去添加额外的逻辑去处理collection
[
 {
  "self": "https://dogtracker.com/dogs/12344",
  "kind": "Dog",
   "name": "Fido",
   "furColor": "white"
  },
  {
   "self": "https://dogtracker.com/dogs/12345",
   "kind": "Dog",
   "name": "Rover",
   "furColor": "brown"
  }
]
还有一种做法是,针对一个collection,使用自定义的media type header(比如‘Collection+JSON’)这个方法可行,但是会让客户端的处理逻辑复杂。
集合数据:
方案1:集合collection也是一种resource,也具有self和kind属性,这样所有的单独entity和collection都具有更加统一的规范
{
 "self": "https://dogtracker.com/dogs",
 "kind": "Collection",
 "contents": [
   {
    "self": "https://dogtracker.com/dogs/12344",
    "kind": "Dog",
    "name": "Fido",
    "furColor": "white"
   },
   {
    "self": "https://dogtracker.com/dogs/12345",
    "kind": "Dog",
    "name": "Rover",
    "furColor": "brown"
   }
  ]
}
方案2:看起来更加简洁,但是客户端可能需要去添加额外的逻辑去处理collection
[
 {
  "self": "https://dogtracker.com/dogs/12344",
  "kind": "Dog",
   "name": "Fido",
   "furColor": "white"
  },
  {
   "self": "https://dogtracker.com/dogs/12345",
   "kind": "Dog",
   "name": "Rover",
   "furColor": "brown"
  }
]
还有一种做法是,针对一个collection,使用自定义的media type header(比如‘Collection+JSON’)这个方法可行,但是会让客户端的处理逻辑复杂。
数据分页:当数据返回的集合变大时,显然不可能一次性把所有数据都返回给客户端,最好能分批的返回,比如:
GET https://dogtracker.com/dogs?limit=25,offset=0 
返回
{
"self": "https://dogtracker.com/dogs?limit=25,offset=0",
"kind": "Page",
"pageOf": "https://dogtracker.com/dogs",
"next": "https://dogtracker.com/dogs?limit=25,offset=25",
"contents": [...】
}
在返回的数据中,加入‘pageOf’来指明查询的起点,‘next’指明下一页的url,当返回第二页的时候,还需加入‘previous’来指明上一页。
如果选择JSON作为唯一的数据格式,那么最好支持Http的patch方法,现在有两种patch的模式:JSON Patch和JSON Merge Patch,选择一个来用于资源的更新操作。现在也有很多API只提供PUT来更新资源,这意味着每次请求都必须发送整个resource enrity,势必会消耗更多的payload,但是实现起来更容易。
  1. 不(显式)支持多版本
  2. 使用Http Accept Header
第一种,什么都不做,不支持多版本的api。这个想法的背后依据是,根据调研发现,大多数的中小型规模的平台服务,客户规模都在一个可控的范围,api的升级不会很频繁,你只需通知你的客户,在某个时间点api会更新,然后再server端做一些兼容性的数据迁移,比如增加或删除某个数据库中的表的某个列的名字。大多数情况下,支持多版本api费力不讨好,测试和部署的成本很大,收益却很小。你要做就是保持唯一个可用api服务的兼容性,而不是提供多个版本的api让用户使用。
第二种,如果你一定要支持versioning,那么就在http的accept header中添加version信息,不要在url中使用version信息,千万不要用/api/v1/xxx。
https://pages.apigee.com/rs/apigee/images/api-design-ebook-2012-03.pdf

https://blog.philipphauer.de/restful-api-design-best-practices/

POST (Create)
GET (Read)PUT (Update)DELETE (Delete)
/employeesCreates a new employeeLists all employeesBatch update employeesDelete all employees
/employees/56(Error)Show details of the employee 56Update the employee 56Delete the employee 56
Use the Query String (?) for Optional or Complex Parameter.
GET /employees?state=internal&maturity=senior
2xx: Success3xx: Redirect4xx: Client Error5xx: Server Error200 OK
201 Created301 Moved Permanently
304 Not Modified400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found500 Internal Server Error
http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
What about actions that don't fit into the world of CRUD operations?
This is where things can get fuzzy. There are a number of approaches:
  1. Restructure the action to appear like a field of a resource. This works if the action doesn't take parameters. For example an activate action could be mapped to a boolean activatedfield and updated via a PATCH to the resource.
  2. Treat it like a sub-resource with RESTful principles. For example, GitHub's API lets you star a gist with PUT /gists/:id/star and unstar with DELETE /gists/:id/star.
  3. Sometimes you really have no way to map the action to a sensible RESTful structure. For example, a multi-resource search doesn't really make sense to be applied to a specific resource's endpoint. In this case, /search would make the most sense even though it isn't a resource. This is OK - just do what's right from the perspective of the API consumer and make sure it's documented clearly to avoid confusion.
PUT to /resources/:id and send the data transition=void with the request.
Pretty print by default & ensure gzip is supported
  • X-Rate-Limit-Limit - The number of allowed requests in the current period
  • X-Rate-Limit-Remaining - The number of remaining requests in the current period
  • X-Rate-Limit-Reset - The number of seconds left in the current period
Auto loading related resource representations
there would be a significant efficiency gain from allowing related data to be returned and loaded alongside the original resource on demand.
In this case, embed would be a comma separated list of fields to be embedded. Dot-notation could be used to refer to sub-fields. For example:
GET /tickets/12?embed=customer.name,assigned_user

Of course, ability to implement something like this really depends on internal complexity. This kind of embedding can easily result in an N+1 select issue.



- Short, Meaningful, Predictable, Readable urls
- Nouns, not verbs.
Lower case.
Use hypens rather than spaces or underlines.
Use a plural path for collections.
Put individual resources under the plural collection path.
Favor hackable urls over direct urls.

Idempotent
stateless
versioned
PATCH      → Patch - to update parts of a resource, update one or more fields as opposed to all fields.
SLA
Bankers Algorithm

actions
POST /transactions
from=1&to=2&amount=500.00

https://codeplanet.io/principles-good-restful-api-design/
  • Resource: A single instance of an object. For example, an animal.
  • Collection: A collection of homogeneous objects. For example, animals.
  • Endpoint: An API URL on a Server which represents either a Resource or an entire Collection.
  • Idempotent: Side-effect free, can happen multiple times without penalty.
  • URL Segment: A slash-separated piece of information in the URL.
  • ?limit=10: Reduce the number of results returned to the Consumer (for Pagination)
  • ?offset=10: Send sets of information to the Consumer (for Pagination)
  • ?animal_type_id=1: Filter records which match the following condition (WHERE animal_type_id = 1)
  • ?sortby=name&order=asc: Sort the results based on the specified attribute (ORDER BY name ASC)
  • GET /collection: Return a listing (array) of Resource objects
  • GET /collection/resource: Return an individual Resource object
  • POST /collection: Return the newly created Resource object
  • PUT /collection/resource: Return the complete Resource object
  • PATCH /collection/resource: Return the complete Resource object
  • DELETE /collection/resource: Return an empty document
When designing your API, you should be able to work with tools which allow you to look at raw HTTP packets. Consider using Wireshark
http://blog.2partsmagic.com/restful-uri-design/
What are the criteria for a good REST-ful URI?
Short (as possible). 
Hackable ‘up the tree’. The user should be able to remove the leaf path and get an expected page back.
Meaningful
Predictable
Help visualize the site structure. 
Readable.
Nouns, not verbs.
Query args (everything after the ?) are used on querying/searching resources (exclusively). They contain data the affects the query.
Consistent
Return a representation (e.g. XML or json) based on the request headers, like Accept and Accept-Language rather than a change in the URI.
Tied to a resource. Permanent.
The URI will continue to work while the resource exists, and despite the resource potentially changing over time.
Report canonical URIs. If you have two different URIs for the same resource, ensure you put the canonical URL in the response.
Follows the digging-deeper-path-and-backspace convention. URI path can be used like a backspace.
Uses name1=value1;name2=value2 (aka matrix parameters) when filtering collections of resources.

Tips for creating good REST-ful URIs
Lower case. 
Use hypens rather than spaces or underlines. 
Use a plural path for collections. 
Put individual resources under the plural collection path.
Favor hackable urls over direct urls.

Avoid query args on an non-query/non-search resource.
Avoid extensions

Using Accept HTTP request headers to negotiate views.
Using ‘vendor specific’ Accept headers
e.g. text/vnd.redrata.summary+html; application/vnd.redrata.deep+json.

Direct URLs vs. hackable URLs
hackable/discoverable/end-user-editable url:
/conversations/conversation-{id}/messages/message-{id}

Use Query params for filtering.
Ideally we should use Matrix params to return part of a resource.
Use query params to model matrix use cases
API implementation must be stateless
API should reflect client use case, not represent implementation specifics
Service API is versioned

HTTP Verbs:
POST   → Create – to create a new resource
GET     → Read - to get a resource
PUT     → Update - to replace all fields of previously created resource
DELETE → Delete - to delete a resource, can be marked for deletion OR physical deletion?
PATCH      → Patch - to update parts of a resource, update one or more fields as opposed to all fields.

Subdomains to separate logical parts of the system
APIs have proper governance
APIs are backward compatible
API must establish SLAs for CRUD operations
Well documented.

Services adhere to separation of data , business logic and integration points
Every service must provide health check APIs that implement sanity functional health
Services must be built with resiliency and security at the core
If a dependent service is down or not performing to SLAs, the service should still be able to self heal and serve the clients.
http://www.w3.org/Provider/Style/URI

http://restful-api-design.readthedocs.org/en/latest/methods.html
Asynchronous Requests
In that case, a “202 Accepted” status can be returned to the client. 

Action on Resource
http://stackoverflow.com/questions/25287734/rest-actions-and-url-api-design-considerations
http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http
POST /accounts/1/transfer/500.00/to/2
But this is wrong! What you really need to do is consider the nouns. You're not transferring money, you're creating a Transaction resource:

POST /transactions HTTP/1.1
Host: <snip, and all other headers>

from=1&to=2&amount=500.00
https://en.wikipedia.org/wiki/Create,_read,_update_and_delete
http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
OperationSQLHTTPDDS
CreateINSERTPUT / POSTwrite
Read (Retrieve)SELECTGETread / take
Update (Modify)UPDATEPUT / PATCHwrite
Delete (Destroy)DELETEDELETEdispose
http://stackoverflow.com/questions/10711122/other-restful-actions-on-a-resource
"Remove redeye", "crop", and "resize" all sound like actions which "change or update a resource." They would belong in a PUT action. (I think you mixed up PUT and POST in your question.
For several modifying actions on the same resource (POST), I tend to use a custom header to define the kind of modification. In this case your resources

/images/[id]/remove_redeye
/images/[id]/crop
/images/[id]/resize
would translate to:

POST /images/[id] HTTP/1.1
X-RESTAction [remove_redeye|crop|resize]
PUT /vm/123 
{ "state": "restarting" } 

Restful API Example:

HTTP Caching in Java with JAX-RS
https://devcenter.heroku.com/articles/jax-rs-http-caching
Time-based cache headers
        CacheControl cc = new CacheControl();
        cc.setMaxAge(86400);
        cc.setPrivate(true);

        ResponseBuilder builder = Response.ok(myBook);
        builder.cacheControl(cc)
Conditional cache headers
Conditional requests are those where the browser can ask the server if it has an updated copy of the resource. The browser will send one or both of the ETag andIf-Modified-Since headers about the cached resource it holds. The server can then determine whether updated content should be returned or the browser’s copy is the most recent.
        Book myBook = getBookFromDB(id);
        CacheControl cc = new CacheControl();
        cc.setMaxAge(86400);

        EntityTag etag = new EntityTag(Integer.toString(myBook.hashCode()));
        ResponseBuilder builder = request.evaluatePreconditions(etag);

        // cached resource did change -> serve updated content
        if(builder == null){
                builder = Response.ok(myBook);
                builder.tag(etag);
        }

        builder.cacheControl(cc);
        return builder.build();
http://www.programcreek.com/java-api-examples/index.php?source_dir=JerseyTest-master/contribs/bill-burke-book/ex10_1/src/main/java/com/restfully/shop/services/CustomerResource.java
public Response getCustomer(@PathParam("id") int id,
                            @HeaderParam("If-None-Match") String sent,
                            @Context Request request)
{
   Customer cust = customerDB.get(id);
   if (cust == null)
   {
      throw new WebApplicationException(Response.Status.NOT_FOUND);
   }

   if (sent == null) System.out.println("No If-None-Match sent by client");

   EntityTag tag = new EntityTag(Integer.toString(cust.hashCode()));

   CacheControl cc = new CacheControl();
   cc.setMaxAge(5);

   Response.ResponseBuilder builder = request.evaluatePreconditions(tag);
   if (builder != null)
   {
      System.out.println("** revalidation on the server was successful");
      builder.cacheControl(cc);
      return builder.build();
   }
   // Preconditions not met!

   cust.setLastViewed(new Date().toString());
   builder = Response.ok(cust, "application/xml");
   builder.cacheControl(cc);
   builder.tag(tag);
   return builder.build();
}
http://stackoverflow.com/questions/20022683/why-does-evaluatepreconditions-dont-work
@Test
public void testETag() throws InterruptedException {
    WebTarget webTarget = target("rest").path("e_tag").queryParam("userId", "eric");

    Response head = webTarget.request().get();
    EntityTag eTag = head.getEntityTag();
    System.out.println(head.getStatus() + "\t" + eTag);
    Assert.assertEquals(200, head.getStatus());
    Thread.sleep(1000);

    Response head1 = webTarget.request().header("If-None-Match", eTag).get();
    System.out.println(head1.getStatus() + "\t" + head1.getEntityTag());
    Assert.assertEquals(304, head1.getStatus());
}
https://docs.oracle.com/javaee/6/api/javax/ws/rs/core/Request.html
Method Summary
 Response.ResponseBuilderevaluatePreconditions()
          Evaluate request preconditions for a resource that does not currently exist.
 Response.ResponseBuilderevaluatePreconditions(java.util.Date lastModified)
          Evaluate request preconditions based on the passed in value.
 Response.ResponseBuilderevaluatePreconditions(java.util.Date lastModified, EntityTag eTag)
          Evaluate request preconditions based on the passed in value.
 Response.ResponseBuilderevaluatePreconditions(EntityTag eTag)
          Evaluate request preconditions based on the passed in value.
Making a conditional PUT
Resource updates can also be conditional. The client would receive 412 Precondition Failed if the ETag send by the client does not match the one on the server.
http://www.javacodegeeks.com/2013/10/http-caching-using-jax-rs.html

https://dzone.com/articles/restful-design-patterns
HTTP PUT is used to modify the object, the request body contains the representation of the Object after successful modification.


HTTP PUT is also used to create the object if the caller has complete control of assigning the object id, the request body contains the representation of the Object after successful creation.
If the caller has no control in the object id, HTTP POST is made to the object's parent container with the request body contains the representation of the Object. The response body should contain a reference to the URL of the created object.

Call a method of the Object

HTTP POST is used to invoke a method of the object, the method is indicated in a mandated parameter "action". The arguments of the method can also be encoded in the URL (for primitive types) or in the request body (for complex types)
POST /library/books/668102?action=buy&user=ricky

Container Patterns

The immediate parent of a container must be an object (can be a singleton object without an id or an object with an id). Container "contains" other objects or containers. If a container is destroyed, everything underneath will be destroyed automatically in a recursive manner.
http://www.xyz.com/library/books
http://www.xyz.com/library/dvds
http://www.xyz.com/library/books/668102/chapters

In GET operation, by default the container only return the URL reference of its immediate children. An optional parameter "expand" can be used to request the actual representation of all children and descendants.

A more sophisticated GET operation can contain a "criteria" parameter to show only the children that fulfills certain criteria.
GET http://www.xyz.com/library/book?query=(author='ricky')

Reference Patterns
Long running transactions
Concurrent Updates (ETags)
http://calvin1978.blogcn.com/articles/services.html
4. 现在更喜欢称呼自家的东西是JSON内容的HTTP API,而不是Restful Service。由那些R虫上脑,坚信一切皆是资源,死抱着Restful的大腿不肯放的学院派来设计协议,简直是灾难。

我遇到的死硬派:一个原子的操作,但认为涉及到两个"资源",生生拆成两个调用,万一第二个调用没收到我咋办呢,再额外做个超时回退的操作么。又如,通知接口明明可以把要通知的内容都送过去,被换成了一个资源的超链接...问题是,我发完通知就想删掉它呀,不会供着它让你哪天来访问...   2014-9-18

后来发现,其实不止我一个这样想,比如Heroku的那份设计指南,也叫做HTTP API Design Guide,这份指南集合了学院派与实战派中好的那一部分。

但后来,TW的技术雷达将Micro Services放到了谨慎的位置,毕竟分拆多了,运维的水平一定要跟上 2014-04-15

http://blog.dataman-inc.com/115-shurenyun-rest
面向用户的服务通常包含大量以同步方式通信的微服务,这意味着各微服务彼此依赖。这种处理方式在各服务皆能正常运转时并不会带来什么麻烦,然而一旦某项服务发生故障,即会引发不可阻挡的故障链并导致最终用户面临拒绝服务状况。
同样的,如果某项微服务速度缓慢,则响应时间也会被同时拖累。
使用REST等同步通信机制,Roper指出,“从技术角度讲,我们仍然相当于构建了一款整体式方案。无论各组件是否被拆分为独立服务,系统本身还是会像整体应用那样高度关联。”
像Apache Kafka这样的软件能够自动处理异步服务中的低级细节,“如果使用简单的REST调用,大家的服务有可能瘫痪。而通过这种方式,情况就不会那么糟糕。”
http://blog.jimmylv.info/2015-11-11-what-is-really-rest
2008年10月Fielding写了一篇博 客,做出了一个非常明确的断言:REST APIs must be hypertext-driven!(REST API必须是超文本驱动的!)超文本驱动这个理念变成了一个缩写词HATEOAS,这个缩写词来自于当初Fielding博士论文中的一句话: hypermedia as the engine of application state(将超媒体作为应用状态的引擎)。其实超文本驱动(Hypertext Driven)的理念才是REST架构风格最核心的理念,也是REST风格的架构达到松耦合目标的根本原因。
第一级:在架构中引入资源(Resource)的概念。
第二级:每一个URI代表一种资源,支持HTTP动词。

第三级:HATEOAS,使用超媒体(hypermedia)作为应用状态引擎。
GET https://api.example.com/profile

{
  "name": "Steve",
  "picture": {
    "large": "https://somecdn.com/pictures/1200x1200.png",
    "medium": "https://somecdn.com/pictures/100x100.png",
    "small": "https://somecdn.com/pictures/10x10.png"
  }
}
由于在响应中包含了链接地址,因此使用该API的客户端就能够自由选择要下载怎样的信息。这些链接告知了客户端有哪些选择,并且它们的地址在哪里。因此在这里我们无需同时返回三个不同版本的用户档案图片,我们所做的只是告诉客户端有三种可用的图片尺寸可以选择,并且告诉客户端能够在哪里找到这些图片。这样一来,客户端就能够根据不同的场景,做出符合自身需要的选择。而且,如果客户端只需要一种格式的图片,那就无需下载全部三种版本的图片了。这样一来可谓一箭三雕:既减少了网络负载,又增进了客户端的灵活性,更增进了API的可探索性。
超媒体的核心概念就是所谓的<link>元素,而这些相互链接的资源实际上描述了一个协议,即引导我们达成某个目标的一系列步骤,例如订购一杯咖啡所需要的点单、付款、取咖啡等等。这就是超媒体的本质:经由资源之间的链接,我们改变整个应用的状态,即超媒体转换了分布式应用的状态。需要注意的是,服务器和消费者两者间交换的是资源状态的表述,而不是应用的状态,被转移的表述中包括了反应应用状态的链接。
http://softwareengineering.stackexchange.com/questions/294445/rest-autocomplete-endpoint-design
REST services are typically focused on resources, with the path separator (forward slash) denoting sub-resources in a hierarchy. Resources are typically things ("nouns" in English) rather than actions ("verbs" in English).
To conform to this style, your "suggest" endpoint (a verb) could be named "suggestions" (a noun) because it provides access to suggestions. Just as an example, a sub-resource for suggestions might be individual suggestions with URLs like /suggest/Hilton%20New%20York where the response could be a link to the actual "Hilton New York" hotel resource.
Actions like "suggest" are typically represented through HTTP verbs, e.g. GET /suggestions/.
Your "term" is essentially a filter, which are typically passed through query parameters or in the request body. A more robust implementation is described in this SO answer.
https://maps.googleapis.com/maps/api/place/autocomplete/output?parameters
For example, you want to make a simple algorithmic calculation like how much tax someone should pay, or do a natural language translation (one language in request; another in response), or convert one currency to another. None involve resources returned from a database.
In these cases:
Use verbs not nouns
For example, an API to convert 100 euros to Chinese Yen
/convert?from=EUR&to=CNY&amount=100
Make it clear in your API documentation that these are different.
While a simple search could be modeled as a resourceful API (for example, dogs/?q=red), a more complex search across multiple resources requires a different design.
This will sound familiar if you've read the aforementioned API design tip about using verbs not nouns when results don't return a resource from the database - rather the result is some action or calculation.

If you want to do a global search across resources, I suggest you follow the Google model
Global search
/search?q=fluffy+fur
Here, search is the verb; ?q represents the query.
Scoped search
To add scope to your search, you can prepend with the scope of the search. For example, search in dogs owned by resource ID 5678
/owners/5678/dogs/search?q=fluffy+fur
Formatted results
For search or for any of the action oriented (non-resource) responses, you can prepend with the format as follows:
/search.xml?q=fluffy+fur

Say, you want a count of all of some resource (scoped based on whatever the requester has access to).
/dogs/count
In this way, you think about "count" as a reserved word.
This means that you can never have "count" as an identifier on a resource - doing so would return that specific resource.  Otherwise, this would get a count of the resource (dogs) available in the database.
https://groups.google.com/forum/#!forum/api-craft


https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
201 Created
202 Accepted
204 No Content
301 Moved Permanently

304 Not Modified
If the client has performed a conditional GET request and access is allowed, but the document has not been modified, the server SHOULD respond with this status code. The 304 response MUST NOT contain a message-body, and thus is always terminated by the first empty line after the header fields.

400 Bad Request

The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications.

401 Unauthorized

The request requires user authentication. The response MUST include a WWW-Authenticate header field (section 14.47) containing a challenge applicable to the requested resource. The client MAY repeat the request with a suitable Authorization header field (section 14.8). If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response

403 Forbidden

The server understood the request, but is refusing to fulfill it. Authorization will not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead.

10.4.5 404 Not Found

The server has not found anything matching the Request-URI. No indication is given of whether the condition is temporary or permanent. The 410 (Gone) status code SHOULD be used if the server knows, through some internally configurable mechanism, that an old resource is permanently unavailable and has no forwarding address. This status code is commonly used when the server does not wish to reveal exactly why the request has been refused, or when no other response is applicable.

10.4.6 405 Method Not Allowed

The method specified in the Request-Line is not allowed for the resource identified by the Request-URI. The response MUST include an Allow header containing a list of valid methods for the requested resource.

10.4.9 408 Request Timeout

The client did not produce a request within the time that the server was prepared to wait. The client MAY repeat the request without modifications at any later time.

10.4.10 409 Conflict

The request could not be completed due to a conflict with the current state of the resource. This code is only allowed in situations where it is expected that the user might be able to resolve the conflict and resubmit the request. The response body SHOULD include enough
information for the user to recognize the source of the conflict. Ideally, the response entity would include enough information for the user or user agent to fix the problem; however, that might not be possible and is not required.
Conflicts are most likely to occur in response to a PUT request. For example, if versioning were being used and the entity being PUT included changes to a resource which conflict with those made by an earlier (third-party) request, the server might use the 409 response to indicate that it can't complete the request. In this case, the response entity would likely contain a list of the differences between the two versions in a format defined by the response Content-Type.

10.4.11 410 Gone

The requested resource is no longer available at the server and no forwarding address is known. This condition is expected to be considered permanent. Clients with link editing capabilities SHOULD delete references to the Request-URI after user approval. If the server does not know, or has no facility to determine, whether or not the condition is permanent, the status code 404 (Not Found) SHOULD be used instead. This response is cacheable unless indicated otherwise.
The 410 response is primarily intended to assist the task of web maintenance by notifying the recipient that the resource is intentionally unavailable and that the server owners desire that remote links to that resource be removed. Such an event is common for limited-time, promotional services and for resources belonging to individuals no longer working at the server's site. It is not necessary to mark all permanently unavailable resources as "gone" or to keep the mark for any length of time -- that is left to the discretion of the server owner.

10.4.12 411 Length Required

The server refuses to accept the request without a defined Content- Length. The client MAY repeat the request if it adds a valid Content-Length header field containing the length of the message-body in the request message.

10.4.13 412 Precondition Failed

The precondition given in one or more of the request-header fields evaluated to false when it was tested on the server. This response code allows the client to place preconditions on the current resource metainformation (header field data) and thus prevent the requested method from being applied to a resource other than the one intended.

10.4.14 413 Request Entity Too Large

The server is refusing to process a request because the request entity is larger than the server is willing or able to process. The server MAY close the connection to prevent the client from continuing the request.
If the condition is temporary, the server SHOULD include a Retry- After header field to indicate that it is temporary and after what time the client MAY try again.

10.4.15 414 Request-URI Too Long

10.5.3 502 Bad Gateway

The server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request.

10.5.4 503 Service Unavailable

The server is currently unable to handle the request due to a temporary overloading or maintenance of the server. The implication is that this is a temporary condition which will be alleviated after some delay. If known, the length of the delay MAY be indicated in a Retry-After header.

10.5.5 504 Gateway Timeout

The server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server (e.g. DNS) it needed to access in attempting to complete the request.

Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts