iT邦幫忙

2018 iT 邦幫忙鐵人賽
0

好 既然 HATEOAS(Hypermedia As The Engine Of Application State) 有帶來一定程度上的好處, 那通常你必須在 Entity 中去定義其中的關連性 (OneToOne, OneToMany, ManyToOne, ManyToMany), 說實在這種關聯操作太麻煩了.....後來我幾乎都是直接原始資料對應到欄位比較多, 但是我又希望有 HATEOAS 這種描述相關資源的效果...

舉例來說 這是我們一個專案的回覆資料, 並包含 links 可以取得 project 的相關資訊,而不必關心 project url 的變動

{  
   "projectId":"QpbR7viWdy",
   "projectName":"123",
   "score":72,
   "createdDate":null,
   "createdBy":null,
   "lastModifiedDate":"2017-07-20T09:24:28Z",
   "lastModifiedBy":null,
   "_links":{  
      "self":{  
         "href":"http://localhost:8080/scoreProjects/n5NehJba7k"
      },
      "scoreProject":{  
         "href":"http://localhost:8080/scoreProjects/n5NehJba7k"
      },
      "project":{  
         "href":"http://localhost:8080/projects/QpbR7viWdy"
      },
      "scoreProjectMembers":{  
         "href":"http://localhost:8080/scoreProjectMembers/search/findByProjectId?projectId=QpbR7viWdy"
      },
      "projectMembers":{  
         "href":"http://localhost:8080/api/project/QpbR7viWdy/members"
      }
   }
}

ScoreProject 這物件是關於專案的分數,那我們希望讓他知道專案的 url 位置讓前端自己取得
可以這樣寫,當然 Project 也必須是 RepositoryRestResource 的一個 Rest 資源

resource.add(repositoryEntityLinks.linkToSingleResource(Project.class, scoreProject.getProjectId()));

轉換出來的結果

"project": {
    "href": "http://localhost:8080/projects/QpbR7viWdy"
}

那我們想利用已經定義好在 RepositoryRestResource 裡面的搜尋條件要怎麼設定
舉例說這是我們 Project 下面每一個 Member 的分數資訊

@RepositoryRestResource
public interface ScoreProjectMemberRepostiory extends JpaRepository<ScoreProjectMember, String> {
    @RestResource(path = "findByProjectId", exported = true)
    List<ScoreProjectMember> findByProjectId(@Param("projectId") String projectId);
}

我們可以這樣指向搜尋功能

resource.add(repositoryEntityLinks.linksToSearchResources(ScoreProjectMember.class).getLink("findByProjectId").expand(scoreProject.getProjectId()).withRel("scoreProjectMembers"));

轉換出來的結果

scoreProjectMembers": {
    "href": "http://localhost:8080/scoreProjectMembers/search/findByProjectId?projectId=QpbR7viWdy"
}

或是我們自己定義好的 RestController 位置想加進去要如何寫
這是一支我們自己做的 Controller 要找出 Project 下所有 Member

@RestController
@RequestMapping("api")
public class ProjectMemberRestController {
    @Autowired
    private ProjectService projectService;

    @ResponseStatus(HttpStatus.OK)
    @GetMapping(value = "project/{projectid}/members", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public List<ProjectMember> findMemberByProjectid(
            @PathVariable("projectid") String projectid) {
        List<ProjectMember> projectMemberList = projectService.findMember(projectid);
        return projectMemberList;
    }
}

指向我們寫的 Controller

resource.add(ControllerLinkBuilder.linkTo(ControllerLinkBuilder.methodOn(ProjectMemberRestController.class).findMemberByProjectid(scoreProject.getProjectId())).withRel("projectMembers"));

轉換出來的結果

"projectMembers": {
    "href": "http://localhost:8080/api/project/QpbR7viWdy/members"
}

完整的寫法就是下面這樣子而已, spring 掃描到後就自動可以起作用了

import com.ps.controller.api.ProjectMemberRestController;
import com.ps.model.Project;
import com.ps.model.ScoreProject;
import com.ps.model.ScoreProjectMember;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceProcessor;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import org.springframework.stereotype.Component;

/**
 * Created by samchu on 2017/7/21.
 */
@Component
public class ScoreProjectResourcesProcessor implements ResourceProcessor<Resource<ScoreProject>> {

    @Autowired
    private RepositoryEntityLinks repositoryEntityLinks;

    @Override
    public Resource<ScoreProject> process(Resource<ScoreProject> resource) {
        ScoreProject scoreProject = resource.getContent();

        // 指向單一物件
        resource.add(repositoryEntityLinks.linkToSingleResource(Project.class, scoreProject.getProjectId()));
        // "project": {
        //   "href": "http://localhost:8080/projects/QpbR7viWdy"
        // }

        // 指向搜尋功能
        resource.add(repositoryEntityLinks.linksToSearchResources(ScoreProjectMember.class).getLink("findByProjectId").expand(scoreProject.getProjectId()).withRel("scoreProjectMembers"));
        // scoreProjectMembers": {
        //   "href": "http://localhost:8080/scoreProjectMembers/search/findByProjectId?projectId=QpbR7viWdy"
        // }

        // 指向我們寫的 Controller
        resource.add(ControllerLinkBuilder.linkTo(ControllerLinkBuilder.methodOn(ProjectMemberRestController.class).findMemberByProjectid(scoreProject.getProjectId())).withRel("projectMembers"));
        // "projectMembers": {
        //   "href": "http://localhost:8080/api/project/QpbR7viWdy/members"
        // }
        
        // 可以指定查詢頁數
        resource.add(repositoryEntityLinks.linkToCollectionResource(Project.class).expand("5","20").withRel("projects")); // rojects?page=5&size=20

        return resource;
    }
}

在透過 Spring Data Rest 做任何取得或查詢的時候就都可以提供相關的資源位置給前端


上一篇
Day 33 - Spring HATEOAS
下一篇
Day 35 - 讓 Page 物件像 Spring Data 一樣支援 HATEOAS
系列文
30天從零開始 使用 Spring Boot 跟 Spring Cloud 建構完整微服務架構35

尚未有邦友留言

立即登入留言