簡體   English   中英

在 RESTful 響應中構建鏈接鍵(對於 HATEOAS)

[英]Building the links key in RESTful response (for HATEOAS)

我為葡萄牙創建了一個 geo API,現在我正嘗試通過引入 HATEOAS 來符合 RESTful 標准。

示例:此響應是特定市鎮 ( municipio ) /municipios/{municipality}/freguesias的教區 ( freguesias ) 列表

{
  "nome": "Porto",
  "freguesias": [
    "Bonfim",
    "Campanhã",
    "Paranhos",
    "Ramalde",
    "União das freguesias de Aldoar, Foz do Douro e Nevogilde",
    "União das freguesias de Cedofeita, Santo Ildefonso, Sé, Miragaia, São Nicolau e Vitória",
    "União das freguesias de Lordelo do Ouro e Massarelos"
  ]
}

每個教區(數組freguesias的元素)都有自己的鏈接: /freguesias/{parish}

您將如何在響應中構建links鍵?

從狹義上講,REST 不是基於除 HTTP 之外的標准作為其主要傳輸層,URI 作為其命名方案和各種定義明確的媒體類型定義,它用於與能夠處理此類消息的對等方交換消息。 REST 是一種架構風格,如果您願意,可以使用一組間接方法將客戶端與服務器分離,從而允許服務器有效地自由發展而不會破壞客戶端,因為這些在設計時就考慮到了變化。

HATEOAS 只不過是超文本的縮寫, hypertext as the engine of application state這意味着交換的媒體類型應該允許客戶在無需聯系或查找外部文檔的情況下推進他們的任務。 這可以通過將 URI 附加到鏈接關系名稱來實現,這允許 URI 隨着時間的推移進行交換,並且客戶端仍然能夠通過關系名稱查找 URI 以“調用”,或者通過返回包含元素的表示格式“教”客戶如何構建請求。

關於鏈接關系名稱,這些名稱應該基於注冊名稱,例如firstlastnextprevup ,或者按照Web 鏈接擴展機制定義自定義名稱。 在后一種情況下,您使用的 URI 不一定需要指向資源或文檔,例如https://acme.com/rel/parent 這基本上充當語義 Web 三元組中的謂詞,它將上下文中附加 URI 標識的目標資源設置為當前資源。 一個 URI 甚至可以附加到多個鏈接關系名稱。 如果客戶端不理解某個鏈接關系名稱,它應該忽略該關系名稱。 這樣的關系名稱可以在 media-types 或profiles中進一步定義。

服務器可以“教導”客戶端如何通過由媒體類型定義的表單表示或元素來構造請求。 即想到HTLM 形式 HTML 表單確實向客戶端解釋了發送到服務器的請求應該是什么樣子。 它描述了資源支持的屬性和服務器期望作為發送請求時使用的 HTTP 方法的輸入,以及將請求發送到的目標 URI 和將請求編組到的表示格式。 這通常作為application/x-www-form-urlencoded隱式給出。 表單的元素甚至可以提示客戶屬性具有什么類型、其數值的可接受范圍,甚至允許客戶通過各種小部件選擇某個日期或時間點。

當然,這一切都取決於所交換的媒體類型的能力。 application/json沒有這些,因此以這種表示格式交換資源 state 對客戶端不會很有幫助,除非它內置了對返回數據的支持,這已經表明與服務的緊密耦合. 正如 Evert 已經提到的超文本應用程序語言 (HAL) ,這種基於 JSON 的媒體類型允許服務器向客戶端傳授 URI 是什么,普通的 JSON 沒有這個概念,即允許將 URI 附加到鏈接關系名稱。 因此,它是一種很好的通用媒體類型,用於向客戶端描述通用資源。 它雖然缺乏教導客戶如何提出基於表格的請求的能力。 幸運的是,還有其他媒體類型,例如Ion ( Amazon Ion ) 或 HAL 的擴展,即HAL forms可以彌補這一差距。

因此,雖然 inf3rno 給出了一個示例,說明如何在響應中構建鏈接,但實際生成的表示實際上取決於協商的媒體類型。

在 HAL 中,您的 JSON 負載可以這樣表示:

{
    "nome": "Porto",
    "_links": {
        "self": {
            "href": "/municipios/Porto"
        },
        ...
    },
    "_embedded": {
        "freguesias": {
            "_links": {
                "self": {
                    "href": "/municipios/Porto/freguesias"
                },
                "https://.../rel/parent": {
                    "href": "/municipios/Porto"
                },
                "https://.../rel/freguesias/Bonfim": {
                    "href": "/municipios/Porto/freguesias/Bonfim"
                },
                "https://.../rel/freguesias/Campanha": {
                    "href": "/municipios/Porto/freguesias/Campanha"
                },
                ...
            },
            ...
        }
    }
}

在 Ion 中,相同的資源 state 可能如下所示:

{
    "name": "Porto",
    "self": { "href": "/municipios/Porto" },
    "freguesias": {
        "href": "/municipios/Porto/freguesias",
        "value": [
            "https://.../rel/parent": { "href": "/municipios/Porto" },
            "https://.../rel/freguesias/Bonfim": { "href": "/municipios/Porto/freguesias/Bonfim" },
            "https://.../rel/freguesias/Campanha": { "href": "/municipios/Porto/freguesias/Campanha" },
            ...
        ]
    }
}

你喜歡哪一個取決於你。 一般來說,您的應用程序能夠處理的媒體類型越多,它們就越有可能與網絡中的其他對等點進行互操作,而無需您手動接觸您的應用程序。 也就是說,您可以添加對 HAL 和 ION 的支持,然后讓客戶端通過內容協商的方式決定它喜歡的表示形式。

最簡單的答案是這樣的:

{
  "nome": "Porto",
  "uri": "/municipios/Porto",
  "freguesias": [
    {"uri": "/municipios/Porto/freguesias/Bonfim", "name": "Bonfim"},
    {"uri": "/municipios/Porto/freguesias/Campanhã", "name" "Campanhã"},
    ...
  ]
}

盡管如果有人寫了這個,那么他們就錯過了我們在這里跟蹤超鏈接的要點,當您在此處將 object 圖再擴展一級時,您會自動跟蹤超鏈接。

克服資源 id - 鏈接問題的經典解決方案是添加自引用。

{
    "nome": "Porto",
    "links": [
        {"relation": "self", "uri": "/municipios/Porto"}
    ],
    "freguesias": [
        {
            "name": "Bonfim",
            "links": [
                {"relation": "self", "uri": "/municipios/Porto/freguesias/Bonfim"}
            ]
        },
        {
            "name" "Campanhã",
            "links": [
                {"relation": "self", "uri": "/municipios/Porto/freguesias/Campanhã"}
            ]
        },
        ...
    ]
}

self在這里是一個鏈接關系,它描述了鏈接URI所標識的資源與包含該鏈接的資源的關系。 您可以在此處找到標准鏈接關系: https://www.iana.org/assignments/link-relations/link-relations.xhtml

我更喜歡的另一種方法是描述當您點擊包括非 GET 鏈接(如 POST 等)的鏈接時發生的操作。

{
    "nome": "Porto",
    "links": [
        {"operation": "GetMunicipality", "uri": "/municipios/Porto", "method": "GET"}
    ],
    "freguesias": [
        {
            "name": "Bonfim",
            "links": [
                {"operation": "GetMunicipalityParish", "uri": "/municipios/Porto/freguesias/Bonfim", "method": "GET"}
            ]
        },
        {
            "name" "Campanhã",
            "links": [
                {"operation": "GetMunicipalityParish", "uri": "/municipios/Porto/freguesias/Campanhã", "method": "GET"}
            ]
        },
        ...
    ]
}

沒有必要有鏈接數組,尤其是在第二種情況下,因為關系名稱是通用的,而操作名稱是 API 特定的,因此它們不太可能干擾屬性名稱。

{
    "nome": "Porto",
    "GetMunicipality": {"uri": "/municipios/Porto", "method": "GET"}
    "freguesias": [
        {
            "name": "Bonfim",
            "GetMunicipalityParish": {"uri": "/municipios/Porto/freguesias/Bonfim", "method": "GET"}
        },
        {
            "name" "Campanhã",
            "GetMunicipalityParish": {"uri": "/municipios/Porto/freguesias/Campanhã", "method": "GET"}
        },
        ...
    ]
}

以這種方式自動處理它比較困難,但如果你更喜歡這種更扁平的方法,那么你可以添加 object 類型。

{
    "type": "Municipality",
    "nome": "Porto",
    "GetMunicipality": {"type": "Link", "uri": "/municipios/Porto", "method": "GET"}
    "freguesias": [
        {
            "type": "Parish",
            "name": "Bonfim",
            "GetMunicipalityParish": {"type": "Link", "uri": "/municipios/Porto/freguesias/Bonfim", "method": "GET"}
        },
        {
            "type": "Parish",
            "name" "Campanhã",
            "GetMunicipalityParish": {"type": "Link", "uri": "/municipios/Porto/freguesias/Campanhã", "method": "GET"}
        },
        ...
    ]
}

至於教區列表,它是與市政當局不同的資源,因此您需要在那里使用 object 而不是使用數組並添加諸如項目或成員之類的東西。

{
    "type": "Municipality",
    "nome": "Porto",
    "GetMunicipality": {"type": "Link", "uri": "/municipios/Porto", "method": "GET"}
    "freguesias": {
        "ListMunicipalityParishes": {"type": "Link", "uri": "/municipios/Porto/freguesias", "method": "GET"},
        "type": "ParishList",
        "members": [
            {
                "type": "Parish",
                "name": "Bonfim",
                "GetMunicipalityParish": {"type": "Link", "uri": "/municipios/Porto/freguesias/Bonfim", "method": "GET"}
            },
            {
                "type": "Parish",
                "name" "Campanhã",
                "GetMunicipalityParish": {"type": "Link", "uri": "/municipios/Porto/freguesias/Campanhã", "method": "GET"}
            },
            ...
        ]
    }
}

第一個接近於 HAL 方法,第二個接近於 Hydra 方法。 現在,如果您進一步思考,從不同的角度來看,如果您擴展屬性而不是跟隨與它們相關的超鏈接,並且這些自引用鏈接似乎在此數據結構中處於錯誤的級別,則您會以更少的請求獲得相同的信息,例如您當您已經獲得列表時,不會調用ListMunicipalityParishes 所以可以這樣簡化:

api.GetMunicipality("Porto") -> GET /municipios/Porto

{
    "@type": "Municipality",
    "nome": "Porto",
    "ListMunicipalityParishes": {
        "@type": "Link",
        "uri": "/municipios/Porto/freguesias",
        "method": "GET",
        "response": [
                {
                    "@type": "Parish",
                    "name": "Bonfim"
                },
                {
                    "@type": "Parish",
                    "name" "Campanhã"
                },
                ...
        ]
    }
}

您可以使用當前框架或嘗試自定義框架。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM