不要被名字嚇到-RESTful、HATEOAS、Spring boot之整合

最近在使用Spring Boot構(gòu)建RESTful服務的時候遇到了一些不清楚的地方,查閱資料后寫下此文,試圖大致解釋清楚什么是RESTful,什么是正確的RESTful,以及怎么在SpringBoot中定義和使用RESTful。

  1. 什么是RESTful
Paste_Image.png

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

HATEOAS又是什么鬼?

我們知道REST是使用標準的HTTP方法來操作資源的,但僅僅因此就理解成帶CURD的Web數(shù)據(jù)庫架構(gòu)就太過于簡單了。 這種說法忽略了一個核心概念: “超媒體即應用狀態(tài)引擎(hypermedia as the engine of application state)”。<u> 超媒體是什么? 當你瀏覽Web網(wǎng)頁時,從一個連接跳到一個頁面,再從另一個連接跳到另外一個頁面,就是利用了超媒體的概念: 把一個個把資源鏈接起來。</u>
要達到這個目的,就要求在表述格式里邊加入鏈接來引導客戶端。在《RESTFul Web Services》一書中,作者把這種具有鏈接的特性成為連通性。
RESTful API最好做到Hypermedia,或HATEOAS,即返回結(jié)果中提供鏈接,連向其他API方法,使得用戶不查文檔,也知道下一步應該做什么。比如,當用戶向api.example.com的根目錄發(fā)出請求,會得到這樣一個文檔。

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

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

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

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

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

2.構(gòu)建RESTful服務最佳實踐

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

下面重頭戲來了:

3.使用SpringBoot構(gòu)建符合Hypermedia規(guī)范的RESTful 服務

我以后每次都要說一遍:SpringBoot框架是所有Java開發(fā)者的福音
在SpringBoot中構(gòu)建符合Hypermedia規(guī)范的RESTful 服務簡單到不能再簡單-----只需要添加一條依賴:

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

添加一個簡單的領(lǐng)域類:

@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

一切都和我們正常開發(fā)web沒啥區(qū)別,但是現(xiàn)在,見證奇跡的時刻到了:

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,請求數(shù)據(jù)用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列表,包含所有數(shù)據(jù)
page標簽的出現(xiàn),是由于我們的repository繼承了PagingAndSortingRepository接口
search標簽的出現(xiàn),是由于我們的repository聲明了一個方法,List<Person> findByLastName(@Param("name") String name);這個方法可以像search標簽描述的那樣調(diào)用: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"
        }
    }
}

資源也被更新了。
PUT和PATCH有什么區(qū)別?
PUT相當于整體替換,也就是說,如果我把上面PATCH換成PUT,(我們注意到傳過去的參數(shù)中沒有firstname),那么資源對象的firstname將為空:

{
  "firstName": null,
  "lastName": "大雷",
  ....
}

而PATCH則沒有這個問題,---只改該改的
所以,數(shù)據(jù)庫的update操作最好用PATCH,但是這個方法也有一個問題,一些老舊瀏覽器不支持,什么時候用,自己權(quán)衡吧。

DELETE 就略了

這篇文章到這里就收尾了。

Paste_Image.png

我相信各位看官和我一樣,有一個奇怪的問題縈繞在心頭。。。。。

where is controller?
where is controller?

where is controller?

從頭至尾我們并沒有寫任何控制器,這不科學??!
這恰恰體現(xiàn)了RESTful的思想,我們要的是資源,不是服務。所以我們只要規(guī)定好怎么獲取資源就行了,其它的萬能的SpringBoot已經(jīng)幫我們做了,看到這里有沒有對Sping有了森森的感激之情?
Spring你這是要逆天啊,不用寫三層,不用寫業(yè)務邏輯也就完了,你現(xiàn)在連controller也不讓我寫了,作為一個程序猿我還有什么樂趣???
實際上這也是將來的趨勢,現(xiàn)在無后端應用越來越多,比如基于firebase的,以后的后端程序猿不是寫三層了,應該重點關(guān)注服務器性能優(yōu)化、分布式計算方面,三層啊數(shù)據(jù)庫啊這些事就交給框架自動去完成吧,保證又快又好。

參考文章:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容