スプリングブックt 2入門(4-starter-hateoas)

27133 ワード

Spring HATEOAS:HATEOASルールを満たすSpring RESTアプリケーションを作成するのに便利なライブラリです.このようなRESTアプリケーションは、返信されたリソースがHAL仕様を満たし、dataだけでなく、関連リソースのlinkも含んでいるので、サーバ端とクライアントコードを大幅に結合することができる.
一例を挙げると、以下のリソースが要求される.
Hypermedia要素(link)が付帯されているJSONフォーマットの応答リソースはこのようなものです.
{
  "content":"Hello, World!",
  "_links":{
    "self":{
      "href":"http://localhost:8080/greeting?name=World"
    }
  }
}
いくつかの概念
  • REST:Representational state transfer
  • HATEOAS:Hypermedia as the Engine of Appliation State
  • HAL:Hypertxt Apple Language
  • Richardsonが提案したRESTの成熟度モデル
    このモデルはRESTサービスを成熟度によって4段階に分けている.
    第一の階層(Level 0)のWebサービスは、HTTPを伝送方式として使用するだけであり、実際には、リモート・メソッド呼び出し(RPC)の具体的な形式の一つである.SOAPとXML-RPCはいずれもこのようなものです.
    第二の階層のWebサービスは、資源の概念を導入している.各リソースは、対応する識別子と表現を有する.
    第3の階層(Level 2)のWebサービスは、異なるHTTP方法を用いて異なる動作を行い、HTTP状態コードを用いて異なる結果を表す.HTTP GET方法がリソースを取得する場合、HTTP_DELETE方法がリソースを削除する.
    第4段階(レベル3)のWebサービスはHATEOASを使用する.リソースの表現にはリンク情報が含まれています.クライアントは、リンクに従って実行可能な動作を発見することができる.
    HATEOAS制約
    HATEOASはRESTアーキテクチャの中で最も複雑な制約であり、成熟したRESTサービスを構築する核心でもある.その重要性は、クライアントとサーバとの間の厳しい契約を打破し、クライアントがよりインテリジェントで適応できるようにすることであり、RESTサービス自体の進化と更新も容易になる.
    上記のREST成熟度モデルからは、HATEOASを使ったRESTサービスが最も成熟度が高く、またオススメのやり方が見られます.HATEOASを使用しないRESTサービスに対しては、クライアントとサーバの実現との間に密接に結合されている.クライアントは、サーバが提供する関連文書に基づいて、露出されたリソースと対応する動作を理解する必要がある.サーバが変化した場合、リソースのURIを修正する場合、クライアントは対応する修正を行う必要があります.HATEOASを使用するRESTサービスでは、クライアントは、サーバから提供されるリソースの表現を通じて、実行可能な動作をインテリジェントに発見することができる.サーバが変化した場合、クライアントはリソースのURIと他の情報が動的に発見されたので、修正する必要がない.
    HATEOASの核心はリンクです.リンクの存在は、クライアントが実行可能な動作を動的に見つけることを可能にする.
    工事を始める
    eclipseを開けて、mavenプロジェクトを構築します.pom.xmlは以下の通りです.
    <project
      xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0modelVersion>
    
      <groupId>org.springframeworkgroupId>
      <artifactId>gs-hateoas-serviceartifactId>
      <version>0.0.1-SNAPSHOTversion>
      <packaging>jarpackaging>
    
      <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <java.version>1.8java.version>
      properties>
    
      <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.0.0.M5version>
      parent>
    
      <dependencies>
        <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starter-hateoasartifactId>
        dependency>
        <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starter-testartifactId>
          <scope>testscope>
        dependency>
        <dependency>
          <groupId>com.jayway.jsonpathgroupId>
          <artifactId>json-pathartifactId>
          <scope>testscope>
        dependency>
      dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-maven-pluginartifactId>
          plugin>
        plugins>
      build>
    
      <repositories>
        <repository>
          <id>spring-milestonesid>
          <name>Spring Milestonesname>
          <url>https://repo.spring.io/libs-milestoneurl>
          <snapshots>
            <enabled>falseenabled>
          snapshots>
        repository>
      repositories>
      <pluginRepositories>
        <pluginRepository>
          <id>spring-milestonesid>
          <name>Spring Milestonesname>
          <url>https://repo.spring.io/libs-milestoneurl>
          <snapshots>
            <enabled>falseenabled>
          snapshots>
        pluginRepository>
      pluginRepositories>
    project>
    
    リソース表現モデルを作成します.Spring HATEOASは、HAL仕様に準拠したJSONの結果を返すためにhttp://localhost:8080/greetingオブジェクトを追加することができます.
    // src/main/java/hello/User.java
    package hello;
    
    import org.springframework.hateoas.ResourceSupport;
    
    import com.fasterxml.jackson.annotation.JsonCreator;
    import com.fasterxml.jackson.annotation.JsonProperty;
    
    public class User extends ResourceSupport {
    
        private Integer uid;
    
        private final String code;
    
        private final String name;
    
        @JsonCreator
        public User(@JsonProperty("id") Integer uid, @JsonProperty("code") String code, @JsonProperty("name") String name) {
            this.uid = uid;
            this.code = code;
            this.name = name;
        }
    
        /**
         * @return the code
         */
        public String getCode() {
            return code;
        }
    
        /**
         * @return the name
         */
        public String getName() {
            return name;
        }
    
        /**
         * @return the id
         */
        public Integer getUid() {
            return uid;
        }
    
    }
    // src/main/java/hello/UserList.java
    package hello;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    
    import org.springframework.hateoas.ResourceSupport;
    
    public class UserList extends ResourceSupport {
    
        private List list;
    
    
        public UserList(Collection users) {
            this.list = new ArrayList();
            this.list.addAll(users);
        }
    
        /**
         * @return the code
         */
        public List getUserList() {
            return list;
        }
    }
    
    次はコントローラなどです.
    // src/main/java/hello/UserController.java
    package hello;
    
    import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
    import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
    
    import java.util.Arrays;
    
    import javax.websocket.server.PathParam;
    
    import org.springframework.http.HttpEntity;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseStatus;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class UserController {
    
        @RequestMapping(method = RequestMethod.GET, value = "/user/list")
        public HttpEntity list(@RequestParam(value = "name", required = false) String name) {
    
            User[] users = { new User(1, "jack", "jack T"), new User(2, "tom", "tom C") };
            UserList list = new UserList(Arrays.asList(users));
    
            list.add(linkTo(methodOn(UserController.class).list(name)).withSelfRel());
            list.add(linkTo(methodOn(UserController.class).get("")).withRel("get"));
            list.add(linkTo(methodOn(UserController.class).insert(null)).withRel("new"));
            list.add(linkTo(methodOn(UserController.class).update("")).withRel("update"));
    
            return new ResponseEntity(list, HttpStatus.OK);
        }
    
        @RequestMapping(method = RequestMethod.GET, value = "/user/{id}")
        public HttpEntity get(@PathParam(value = "uid") String uid) {
    
            User user = new User(1, "jack", "jack T");
    
            user.add(linkTo(methodOn(UserController.class).get(uid)).withSelfRel());
            user.add(linkTo(methodOn(UserController.class).insert(null)).withRel("new"));
            user.add(linkTo(methodOn(UserController.class).update(uid)).withRel("update"));
            user.add(linkTo(methodOn(UserController.class).list("")).withRel("collection"));
    
            return new ResponseEntity(user, HttpStatus.OK);
        }
    
        @RequestMapping(method = RequestMethod.PUT, value = "/user/new")
        @ResponseStatus(HttpStatus.CREATED)
        public HttpEntity insert(@RequestBody User auser) {
            String code = auser.getCode();
            String name = auser.getName();
    
            User user = new User(1000, code, name);
    
            user.add(linkTo(methodOn(UserController.class).insert(null)).withSelfRel());
            user.add(linkTo(methodOn(UserController.class).get("")).withRel("get"));
            user.add(linkTo(methodOn(UserController.class).update("")).withRel("update"));
            user.add(linkTo(methodOn(UserController.class).list("")).withRel("collection"));
    
            return new ResponseEntity(user, HttpStatus.OK);
        }
    
        @RequestMapping(method = RequestMethod.PATCH, value = "/user/{id}")
        public HttpEntity update(@PathParam(value = "uid") String uid) {
    
            User user = new User(1, "jack", "jack T");
    
            user.add(linkTo(methodOn(UserController.class).update(uid)).withSelfRel());
            user.add(linkTo(methodOn(UserController.class).get(uid)).withRel("get"));
            user.add(linkTo(methodOn(UserController.class).insert(null)).withRel("new"));
            user.add(linkTo(methodOn(UserController.class).list("")).withRel("collection"));
    
            return new ResponseEntity(user, HttpStatus.OK);
        }
    }
    
    最後はブートクラスです
    // src/main/java/hello/App.java
    package hello;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
    
    /**
     * Hello world!
     */
    @SpringBootApplication
    public class App extends SpringBootServletInitializer {
        public static void main(String[] args) {
            SpringApplication.run(App.class, args);
        }
    }
    効果を表示
    maven spring-boot:runサービスを開始します.
    curlを使ってサーバにアクセスする:
    $ curl http://localhost:8080/user/list
    {
      "userList" : [ {
        "id" : 1,
        "code" : "jack",
        "name" : "jack T"
      }, {
        "id" : 2,
        "code" : "tom",
        "name" : "tom C"
      } ],
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/user/list{?name}",
          "templated" : true
        },
        "get" : {
          "href" : "http://localhost:8080/user/{id}",
          "templated" : true
        },
        "new" : {
          "href" : "http://localhost:8080/user/new"
        },
        "update" : {
          "href" : "http://localhost:8080/user/{id}",
          "templated" : true
        }
      }
    }
    $ curl -X PUT -H 'content-type: application/json' -d '{"code":"TestAddCode"}' http://localhost:8080/user/new
    
    {
        "id": 1000,
        "code": "TestAddCode",
        "name": null,
        "_links": {
            "self": {
                "href": "http://localhost:8080/user/new"
            },
            "get": {
                "href": "http://localhost:8080/user/{id}",
                "templated": true
            },
            "update": {
                "href": "http://localhost:8080/user/{id}",
                "templated": true
            },
            "collection": {
                "href": "http://localhost:8080/user/list?name="
            }
        }
    }
    次のミッション
    spring-boot-starter-data-jpa spring-boot-starter-data-rest
    関連記事
    spring hateoasプロジェクトhttp://projects.spring.io/spring-hateoas/
    スプリングガイドhttps://spring.io/guides/gs/rest-hateoas/
    Spring HATEOASを使ってRESTサービスを開発します.https://www.ibm.com/developerworks/cn/java/j-lo-SpringHATEOAS/index.html
    REST成熟度モデルhttps://www.crummy.com/writing/speaking/2008-QCon/act3.html
    HAL仕様http://stateless.co/hal_specification.