目次

発見可能]




4. ** 単一リソースの入手


1概要

この記事では、Spring RESTサービスでの発見可能性の実装およびHATEOAS制約の充足に焦点を当てます。


2イベントを通じて発見可能性を切り離す

Webレイヤーの

別の側面または懸念

としての発見可能性は、HTTP要求を処理するコントローラーから切り離す必要があります。そうするために、コントローラはHTTP応答の追加操作を必要とするすべてのアクションのためのイベントを発生させます。

まず、イベント:

public class SingleResourceRetrieved extends ApplicationEvent {
    private HttpServletResponse response;

    public SingleResourceRetrieved(Object source,
      HttpServletResponse response) {
        super(source);

        this.response = response;
    }

    public HttpServletResponse getResponse() {
        return response;
    }
}
public class ResourceCreated extends ApplicationEvent {
    private HttpServletResponse response;
    private long idOfNewResource;

    public ResourceCreated(Object source,
      HttpServletResponse response, long idOfNewResource) {
        super(source);

        this.response = response;
        this.idOfNewResource = idOfNewResource;
    }

    public HttpServletResponse getResponse() {
        return response;
    }
    public long getIdOfNewResource() {
        return idOfNewResource;
    }
}

それから、

2つの簡単な操作 – id



create



によってfindすることで、Controller:

@Controller
@RequestMapping(value = "/foos")
public class FooController {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Autowired
    private IFooService service;

    @RequestMapping(value = "foos/{id}", method = RequestMethod.GET)
    @ResponseBody
    public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) {
        Foo resourceById = Preconditions.checkNotNull(service.findOne(id));

        eventPublisher.publishEvent(new SingleResourceRetrieved(this, response));
        return resourceById;
    }

    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    public void create(@RequestBody Foo resource, HttpServletResponse response) {
        Preconditions.checkNotNull(resource);
        Long newId = service.create(resource).getId();

        eventPublisher.publishEvent(new ResourceCreated(this, response, newId));
    }
}

これらのイベントは任意の数の

分離されたリスナー

によって処理されることができ、それぞれがそれ自身の特定のケースに焦点を合わせ、それぞれが全体のHATEOAS制約を満たすことに向かって動きます。

リスナーはコールスタックの最後のオブジェクトになるはずです。リスナーに直接アクセスする必要はありません。そのようなものとして、彼らは非公開です。

** 3新しく作成されたリソースのURIを検出可能にする

**

リンク:/restful-web-service-discoverability[HATEOASへの以前の投稿]で説明したように、新しいリソースを作成する操作では、応答の


Location


HTTPヘッダーにそのリソースのURIが返されるはずです。これはリスナーによって処理されます。

@Component
class ResourceCreatedDiscoverabilityListener
  implements ApplicationListener< ResourceCreated >{

    @Override
    public void onApplicationEvent( ResourceCreated resourceCreatedEvent ){
       Preconditions.checkNotNull( resourceCreatedEvent );

       HttpServletResponse response = resourceCreatedEvent.getResponse();
       long idOfNewResource = resourceCreatedEvent.getIdOfNewResource();

       addLinkHeaderOnResourceCreation( response, idOfNewResource );
   }
   void addLinkHeaderOnResourceCreation
     ( HttpServletResponse response, long idOfNewResource ){
       URI uri = ServletUriComponentsBuilder.fromCurrentRequestUri().
         path("/{idOfNewResource}").buildAndExpand(idOfNewResource).toUri();
       response.setHeader( "Location", uri.toASCIIString() );
    }
}

私たちは現在

ServletUriComponentsBuilder

を利用しています – これはSpring 3.1で現在のRequestの使用を助けるために** 導入されました。こうすれば、何も渡す必要はなく、単純に静的にアクセスできます。

APIが

ResponseEntity

を返す場合 –

Location

サポートhttps://jira.springsource.org/browse/SPR-8020[ここで紹介]も使用できます。


4単一リソースの取得

単一のリソースを取得すると、クライアントはその特定のタイプのすべてのリソースを取得するためにURIを発見できるはずです。

@Component
class SingleResourceRetrievedDiscoverabilityListener
 implements ApplicationListener< SingleResourceRetrieved >{

    @Override
    public void onApplicationEvent( SingleResourceRetrieved resourceRetrievedEvent ){
        Preconditions.checkNotNull( resourceRetrievedEvent );

        HttpServletResponse response = resourceRetrievedEvent.getResponse();
        addLinkHeaderOnSingleResourceRetrieval( request, response );
    }
    void addLinkHeaderOnSingleResourceRetrieval ( HttpServletResponse response ){
        String requestURL = ServletUriComponentsBuilder.fromCurrentRequestUri().
          build().toUri().toASCIIString();
        int positionOfLastSlash = requestURL.lastIndexOf( "/" );
        String uriForResourceCreation = requestURL.substring( 0, positionOfLastSlash );

        String linkHeaderValue = LinkUtil
          .createLinkHeader( uriForResourceCreation, "collection" );
        response.addHeader( LINK__HEADER, linkHeaderValue );
    }
}

リンクリレーションのセマンティクスはhttp://microformats.org/wiki/existing-rel-values#non

HTML

rel

values[several microformats]で指定および使用されている

“ collection” __ relation型を使用しますが、まだ標準化されていません。



Link


ヘッダーは、発見可能性のために最も使用されているHTTPヘッダーの1つです。このヘッダを作成するためのユーティリティはとても簡単です。

public final class LinkUtil {
    public static String createLinkHeader(final String uri, final String rel) {
        return "<" + uri + ">; rel="" + rel + """;
    }
}


5ルートでの発見可能性

ルートはサービス全体の中の入り口です。これは、初めてAPIを使用するときにクライアントが接触する場所です。 HATEOAS制約が全体を通して考慮されそして実行されることになっているならば、それはここから始めるべき場所です。システムのすべての主要なURIがルートから検出可能でなければならないという事実は、この時点ではそれほど驚くべきことではありません。

それでは、コントローラを見てみましょう。

@RequestMapping( value = "admin",method = RequestMethod.GET )
@ResponseStatus( value = HttpStatus.NO__CONTENT )
public void adminRoot( HttpServletRequest request, HttpServletResponse response ){
    String rootUri = request.getRequestURL().toString();

    URI fooUri = new UriTemplate( "{rootUri}/{resource}" ).expand( rootUri, "foo" );
    String linkToFoo = LinkUtil.createLinkHeader
      ( fooUri.toASCIIString(), "collection" );
    response.addHeader( "Link", linkToFoo );
}

これはもちろん、Foo Resourcesの単一のサンプルURIに焦点を当てた概念の説明図です。実際の実装では、同様に、クライアントに公開されるすべてのResourcesのURIを追加する必要があります。


5.1. 発見可能性は、URIを変更することではありません

これは物議をかもすことがありますが、一方でHATOASの目的は、クライアントにAPIのURIを発見させ、ハードコードされた値に頼らないことです。その一方で、これはWebの仕組みではありません。

はい、URIは発見されましたが、ブックマークもされています。

微妙だが重要な違いはAPIの進化です – 古いURIはまだ動作するはずですが、APIを発見するクライアントは新しいURIを発見するべきです – それはAPIが動的に変化することを可能にします。変更します。

結論として – RESTful WebサービスのすべてのURIが


クールなURI


(およびクールなURI

変更しない

) – これは、HATEOAS制約を順守してもAPIを進化させるときに非常に有用ではないことを意味するわけではありません。


6. 発見可能性に関する警告

以前の記事に関するいくつかの議論が述べているように、発見可能性の最初の目的は

documentation

を最小限にするか、またはまったく使わないで、得られた応答を通してクライアントにAPIの使い方を学び理解させることです。実際、これはそれほど遠く取られた理想と見なされるべきではありません – それは私たちがあらゆる新しいWebページを消費する方法です –

いかなる文書もなしで

。そのため、RESTのコンテキストでこの概念がより問題となる場合は、それが可能かどうかという問題ではなく、技術的な実装の問題である必要があります。

そうは言っても、技術的には、私たちはまだ完全に機能するソリューションからは程遠い – 仕様とフレームワークのサポートはまだ進化している、そしてそのため、いくつかの妥協がなされなければならないかもしれない。それにもかかわらず、これらは妥協であり、そのように見なされるべきです。


7. 結論

この記事では、Spring MVCを使用したRESTfulサービスのコンテキストにおける発見可能性のいくつかの特性の実装について説明し、根本的に発見可能性の概念に触れました。

これらすべての例とコードスニペットの実装は、https://github.com/eugenp/tutorials/tree/master/spring-rest-full

にあります。

– これはMavenベースのプロジェクトです。そのため、インポートしてそのまま実行するのは簡単なはずです。