1. 程式人生 > >RESTful levels & HATEOAS

RESTful levels & HATEOAS

ext 作者 網頁 www 引導 people val example search

  1. 什麽是RESTful
  • REST這個詞,是Roy Thomas Fielding在他2000年的博士論文中提出的。翻譯過來就是"表現層狀態轉化。”

Fielding在論文中將REST定位為“分布式超媒體應用(Distributed Hypermedia System)”的架構風格,它在文中提到一個名為“HATEOAS(Hypermedia as the engine of application state)”的概念。

HATEOAS又是什麽鬼?

我們知道REST是使用標準的HTTP方法來操作資源的,但僅僅因此就理解成帶CURD的Web數據庫架構就太過於簡單了。 這種說法忽略了一個核心概念: “超媒體即應用狀態引擎(hypermedia as the engine of application state)

”。<u> 超媒體是什麽? 當你瀏覽Web網頁時,從一個連接跳到一個頁面,再從另一個連接跳到另外一個頁面,就是利用了超媒體的概念: 把一個個把資源鏈接起來。</u>
要達到這個目的,就要求在表述格式裏邊加入鏈接來引導客戶端。在《RESTFul Web Services》一書中,作者把這種具有鏈接的特性成為連通性。
RESTful API最好做到Hypermedia,或HATEOAS,即返回結果中提供鏈接,連向其他API方法,使得用戶不查文檔,也知道下一步應該做什麽。比如,當用戶向api.example.com的根目錄發出請求,會得到這樣一個文檔。

{"link": {
  "rel":   "collection https://www.example.com/zoos",
  "href":  "https://api.example.com/zoos",
  "title": "List of zoos",
  "type":  "application/vnd.yourformat+json"
}}

上面代碼表示,文檔中有一個link屬性,用戶讀取這個屬性就知道下一步該調用什麽API了。rel表示這個API與當前網址的關系(collection關系,並給出該collection的網址),href表示API的路徑,title表示API的標題,type表示返回類型。
Hypermedia API的設計被稱為HATEOAS。Github的API就是這種設計,訪問api.github.com會得到一個所有可用API的網址列表。

{
  "current_user_url": "https://api.github.com/user",
  "authorizations_url": "https://api.github.com/authorizations",
  // ...
}

從上面可以看到,如果想獲取當前用戶的信息,應該去訪問api.github.com/user,然後就得到了下面結果。

{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3"
}
以上內容都摘自阮一峰和其它作者博客,如有冒犯,請及時告知
我當時第一眼看到HATEOAS也是一臉懵逼,因為在Spring依賴中看到過這個詞,所以就留意了一下。其實在我看來,HATEOAS是符合RESTful規範的一個方面,客戶端在消費RESTful服務的時候,出了得到資源本身以外,還可以得到一些相關其他信息,比如,其他相關鏈接,返回類型等等。

2.構建RESTful服務最佳實踐

  • 第一條也是最容易犯錯的:<u>URI中不應該包含動詞</u>。 因為"資源"表示一種實體,所以應該是名詞,URI不應該有動詞,動詞應該放在HTTP協議中。舉例來說,某個URI是/posts/show/1,其中show是動詞,這個URI就設計錯了,正確的寫法應該是/posts/1,然後用GET方法表示show。如果某些動作是HTTP動詞表示不了的,你就應該把動作做成一種資源。比如網上匯款,從賬戶1向賬戶2匯款500元,錯誤的URI是:POST /accounts/1/transfer/500/to/2,正確的寫法是把動詞transfer改成名詞transaction,然後以參數的方式註明其它參數
POST /accounts/transaction?from=1&to=2&amount=500.00
  • RESTful API最好做到Hypermedia(HATEOAS),即返回結果中提供鏈接,連向其他API方法,使得用戶不查文檔,也知道下一步應該做什麽。
  • 其它需要註意的地方參見文末貼出的鏈接

下面重頭戲來了:

3.使用SpringBoot構建符合Hypermedia規範的RESTful 服務

我以後每次都要說一遍:SpringBoot框架是所有Java開發者的福音
在SpringBoot中構建符合Hypermedia規範的RESTful 服務簡單到不能再簡單-----只需要添加一條依賴:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

添加一個簡單的領域類:

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String firstName;
    private String lastName;

    //getter and setter
}

以及一個dao層接口:

//@RepositoryRestResource(collectionResourceRel = "people",path="people")
public interface PersonRepository  extends PagingAndSortingRepository<Person,Long>{
    List<Person> findByLastName(@Param("name") String name);
}

註釋掉的標簽可選,主要是在只用RESTful的時候可以改變URI,比如,加上此處就把/person變成/people

一切都和我們正常開發web沒啥區別,但是現在,見證奇跡的時刻到了:

1.GET localhost:8080
返回:

{
    "_links":{
        "people":{
            "href": "http://localhost:8080/people{?page,size,sort}",
            "templated": true
        },
        "profile":{
            "href": "http://localhost:8080/profile"
        }
    }
}

上面的返回中包括了people這個資源的鏈接明確指出了我們可以用類似http://localhost:8080/people?page=1&size=10&sort=firstname這樣的方式請求資源。

2.增加一個people資源:POST localhost:8080/people,請求數據用json{ "firstName" : "李", "lastName" : "雷" }
返回:

{
    "firstName": "李",
    "lastName": "雷",
    "_links":{
        "self":{
            "href": "http://localhost:8080/people/5"
        },
        "person":{
            "href": "http://localhost:8080/people/5"
        }
    }
}

返回信息中出了新加入信息的各個字段,還有一個href鏈接指向它--這是合乎情理的,客戶端總是想要看看新加入的這條信息長什麽樣,從這個角度說,這條返回信息還是很貼心的。
3.GET localhost:8080/people/

{
    "_embedded":{
        "people":[
            {
                "firstName": "李",
                "lastName": "雷",
                "_links":{"self":{"href": "http://localhost:8080/people/5" }, "person":{"href": "http://localhost:8080/people/5"…}
            }
        ]
    },
    "_links":{
        "self":{
            "href": "http://localhost:8080/people"
        },
        "profile":{
            "href": "http://localhost:8080/profile/people"
        },
        "search":{
            "href": "http://localhost:8080/people/search"
        }
    },
    "page":{
        "size": 20,
        "totalElements": 1,
        "totalPages": 1,
        "number": 0
    }
}

返回一個people列表,包含所有數據
page標簽的出現,是由於我們的repository繼承了PagingAndSortingRepository接口
search標簽的出現,是由於我們的repository聲明了一個方法,List<Person> findByLastName(@Param("name") String name);這個方法可以像search標簽描述的那樣調用:http://localhost:8080/people/search/findByLastName{?name},示例:http://localhost:8080/people/search/findByLastName?name=雷

4.PUT localhost:8080/people/5,json:{ "firstName" : "李", "lastName" : "小雷" }

{
    "firstName": "李",
    "lastName": "小雷",
    "_links":{
        "self":{
            "href": "http://localhost:8080/people/5"
        },
        "person":{
            "href": "http://localhost:8080/people/5"
        }
    }
}

資源被正確更新
5.PATCH localhost:8080/people/5,json:{ "lastName" : "大雷" }

{
    "firstName": "李",
    "lastName": "大雷",
    "_links":{
        "self":{
            "href": "http://localhost:8080/people/5"
        },
        "person":{
            "href": "http://localhost:8080/people/5"
        }
    }
}

RESTful levels & HATEOAS