1. 概要

この記事では、SpringHATEOASプロジェクトを使用してハイパーメディア駆動型のRESTWebサービスを作成するプロセスについて説明します。

2. 春-HATEOAS

Spring HATEOASプロジェクトは、HATEOAS(アプリケーション状態のエンジンとしてのハイパーテキスト)の原則に従うREST表現を簡単に作成するために使用できるAPIのライブラリです。

一般的に言えば、原則は、APIが各応答とともに、次の潜在的なステップに関する関連情報を返すことによって、アプリケーションを通じてクライアントをガイドする必要があることを意味します。

この記事では、クライアントとサーバーを分離し、理論的にはAPIがクライアントを壊すことなくURIスキームを変更できるようにすることを目的として、SpringHATEOASを使用した例を作成します。

3. 準備

まず、SpringHATEOAS依存関係を追加しましょう。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
    <version>2.1.4.RELEASE</version>
</dependency>

Spring Bootを使用していない場合は、次のライブラリをプロジェクトに追加できます。

<dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
    <version>0.25.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.plugin</groupId>
    <artifactId>spring-plugin-core</artifactId>
    <version>1.2.0.RELEASE</version>
</dependency>

いつものように、MavenCentralのstarter HATEOAS spring-hateoas 、およびspring-plugin-coreの依存関係の最新バージョンを検索できます。

次に、SpringHATEOASをサポートしないCustomerリソースがあります。

public class Customer {

    private String customerId;
    private String customerName;
    private String companyName;

    // standard getters and setters
}

また、SpringHATEOASをサポートしていないコントローラークラスがあります。

@RestController
@RequestMapping(value = "/customers")
public class CustomerController {
    @Autowired
    private CustomerService customerService;

    @GetMapping("/{customerId}")
    public Customer getCustomerById(@PathVariable String customerId) {
        return customerService.getCustomerDetail(customerId);
    }
}

最後に、 Customerリソース表現:

{
    "customerId": "10A",
    "customerName": "Jane",
    "customerCompany": "ABC Company"
}

4. HATEOASサポートの追加

Spring HATEOASプロジェクトでは、サーブレットコンテキストを検索したり、パス変数をベースURIに連結したりする必要はありません。

代わりに、 Spring HATEOASは、URIを作成するための3つの抽象化(RepresentationModel、Link、およびWebMvcLinkBuilder )を提供します。 これらを使用してメタデータを作成し、それをリソース表現に関連付けることができます。

4.1. リソースへのハイパーメディアサポートの追加

プロジェクトは、リソース表現を作成するときに継承するRepresentationModelという基本クラスを提供します。

public class Customer extends RepresentationModel<Customer> {
    private String customerId;
    private String customerName;
    private String companyName;
 
    // standard getters and setters
}

Customerリソースは、RepresentationModelクラスから拡張され、add()メソッドを継承します。 したがって、リンクを作成すると、新しいフィールドを追加しなくても、その値をリソース表現に簡単に設定できます。

4.2. リンクの作成

Spring HATEOASは、メタデータ(リソースの場所またはURI)を格納するためのLinkオブジェクトを提供します。

まず、簡単なリンクを手動で作成します。

Link link = new Link("http://localhost:8080/spring-security-rest/api/customers/10A");

LinkオブジェクトはAtomリンク構文に従い、リソースとの関係を識別するrelと実際のhref属性で構成されます。リンク自体。

Customer リソースに新しいリンクが含まれているため、次のようになります。

{
    "customerId": "10A",
    "customerName": "Jane",
    "customerCompany": "ABC Company",
    "_links":{
        "self":{
            "href":"http://localhost:8080/spring-security-rest/api/customers/10A"
         }
    }
}

応答に関連付けられたURIは、selfリンクとして修飾されます。 self リレーションのセマンティクスは明確です。これは、リソースにアクセスできる標準的な場所にすぎません。

4.3. より良いリンクの作成

ライブラリによって提供されるもう1つの非常に重要な抽象化は、 WebMvcLinkBuilderです。これは、リンクのハードコーディングを回避することにより、URIの構築を簡素化します。

次のスニペットは、WebMvcLinkBuilderクラスを使用して顧客の自己リンクを構築する方法を示しています。

linkTo(CustomerController.class).slash(customer.getCustomerId()).withSelfRel();

みてみましょう:

  • linkTo()メソッドは、コントローラークラスを検査し、そのルートマッピングを取得します
  • slash()メソッドは、customerId値をリンクのパス変数として追加します
  • 最後に、 withSelfMethod()は、関係を自己リンクとして修飾します

5. 関係

前のセクションでは、自己参照関係を示しました。 ただし、より複雑なシステムには他の関係も含まれる場合があります。

たとえば、顧客は注文と関係を持つことができます。 Orderクラスもリソースとしてモデル化してみましょう。

public class Order extends RepresentationModel<Order> {
    private String orderId;
    private double price;
    private int quantity;

    // standard getters and setters
}

この時点で、特定の顧客のすべての注文を返すメソッドを使用してCustomerControllerを拡張できます。

@GetMapping(value = "/{customerId}/orders", produces = { "application/hal+json" })
public CollectionModel<Order> getOrdersForCustomer(@PathVariable final String customerId) {
    List<Order> orders = orderService.getAllOrdersForCustomer(customerId);
    for (final Order order : orders) {
        Link selfLink = linkTo(methodOn(CustomerController.class)
          .getOrderById(customerId, order.getOrderId())).withSelfRel();
        order.add(selfLink);
    }
 
    Link link = linkTo(methodOn(CustomerController.class)
      .getOrdersForCustomer(customerId)).withSelfRel();
    CollectionModel<Order> result = CollectionModel.of(orders, link);
    return result;
}

このメソッドは、HAL戻りタイプに準拠する CollectionModel オブジェクトと、各注文および完全なリストの「_self」リンクを返します。

ここで注意すべき重要な点は、顧客注文のハイパーリンクが getOrdersForCustomer()メソッドのマッピングに依存していることです。 これらのタイプのリンクをメソッドリンクと呼び、WebMvcLinkBuilderがそれらの作成をどのように支援できるかを示します。

6. コントローラメソッドへのリンク

WebMvcLinkBuilder は、SpringMVCコントローラーの豊富なサポートを提供します。 次の例は、 CustomerControllerクラスのgetOrdersForCustomer()メソッドに基づいてHATEOASハイパーリンクを構築する方法を示しています。

Link ordersLink = linkTo(methodOn(CustomerController.class)
  .getOrdersForCustomer(customerId)).withRel("allOrders");

methodOn()は、プロキシコントローラーでターゲットメソッドをダミー呼び出ししてメソッドマッピングを取得し、customerIdをURIのパス変数として設定します。

7. 春のHATEOASの実行

セルフリンクとメソッドリンクの作成をすべてgetAllCustomers()メソッドにまとめましょう。

@GetMapping(produces = { "application/hal+json" })
public CollectionModel<Customer> getAllCustomers() {
    List<Customer> allCustomers = customerService.allCustomers();

    for (Customer customer : allCustomers) {
        String customerId = customer.getCustomerId();
        Link selfLink = linkTo(CustomerController.class).slash(customerId).withSelfRel();
        customer.add(selfLink);
        if (orderService.getAllOrdersForCustomer(customerId).size() > 0) {
            Link ordersLink = linkTo(methodOn(CustomerController.class)
              .getOrdersForCustomer(customerId)).withRel("allOrders");
            customer.add(ordersLink);
        }
    }

    Link link = linkTo(CustomerController.class).withSelfRel();
    CollectionModel<Customer> result = CollectionModel.of(allCustomers, link);
    return result;
}

次に、 getAllCustomers()メソッドを呼び出しましょう。

curl http://localhost:8080/spring-security-rest/api/customers

そして、結果を調べます。

{
  "_embedded": {
    "customerList": [{
        "customerId": "10A",
        "customerName": "Jane",
        "companyName": "ABC Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A"
          },
          "allOrders": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/orders"
          }
        }
      },{
        "customerId": "20B",
        "customerName": "Bob",
        "companyName": "XYZ Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/20B"
          },
          "allOrders": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/20B/orders"
          }
        }
      },{
        "customerId": "30C",
        "customerName": "Tim",
        "companyName": "CKV Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/30C"
          }
        }
      }]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/spring-security-rest/api/customers"
    }
  }
}

各リソース表現内には、selfリンクとallOrdersリンクがあり、顧客のすべての注文を抽出します。 顧客が注文を持っていない場合、注文のリンクは表示されません。

この例は、SpringHATEOASがRESTWebサービスでAPIの検出可能性をどのように促進するかを示しています。 リンクが存在する場合、クライアントはそれをたどり、顧客のすべての注文を受け取ることができます:

curl http://localhost:8080/spring-security-rest/api/customers/10A/orders
{
  "_embedded": {
    "orderList": [{
        "orderId": "001A",
        "price": 150,
        "quantity": 25,
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/001A"
          }
        }
      },{
        "orderId": "002A",
        "price": 250,
        "quantity": 15,
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/002A"
          }
        }
      }]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/spring-security-rest/api/customers/10A/orders"
    }
  }
}

8. 結論

このチュートリアルでは、SpringHATEOASプロジェクトを使用してハイパーメディア駆動型SpringRESTWebサービスを構築する方法について説明しました。

この例では、クライアントがアプリケーションへの単一のエントリポイントを持つことができ、応答表現のメタデータに基づいてさらにアクションを実行できることがわかります。

これにより、サーバーはクライアントを壊すことなくURIスキームを変更できます。 また、アプリケーションは、表現に新しいリンクまたはURIを配置することにより、新しい機能をアドバタイズできます。

最後に、この記事の完全な実装は、GitHubプロジェクトにあります。