SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事(AD)

JAX-RS/CDIのサポートでマイクロサービスの開発も容易に ~ Java EE 7正式対応で大幅強化された「WebSphere Application Server Liberty Core」

  • X ポスト
  • このエントリーをはてなブックマークに追加

ダウンロード sample.zip (87.5 KB)

7 JSONを使ったWebサービス連携の試み

 JAX-RS 2.0によってクライアントサイドとしてJSONデータを扱いやすくなったということは、Webサービスを構築する際に外部のRESTful Webサービスと連携させることも容易になったということでもある。外部のWebサービスから取得したJSONデータを、簡単に自前のWebサービスのデータソースとして利用できるからだ。

 そこで次のサンプルでは、先ほどのZipcodeServiceと連携する新しいWebサービスを考えてみよう。今回作ろうとしているのは、7桁の郵便番号を指定すると、それに対応した住所に加えて、緯度・経度の値を返すサービスである。プロジェクト名は「ZipGeoService」とした。

 まず、内部的に住所と緯度・経度をセットで扱うためのクラスをAddressGpsクラスとして次のように定義した。

リスト7.1
public class AddressGps {
    private String zip;           // 郵便番号
    private String address;       // 住所
    private double[] location;    // [緯度,経度]
    
    public AddressGps(String zip, String address, double[] location) {
        this.zip = zip;
        this.address = address;
        this.location = location;
    }
    
    // Setter/Getterは省略
}

 クライアントから見た窓口となるJAX-RSのリソースクラスは、SearchDataというクラス名で次のように実装する。

リスト7.2
@ApplicationScoped
@Path("/geosearch")
public class SearchData {

    @Inject
    private AddressGpsList addressList;        // データソース
    
    /**
     * 指定された郵便番号の住所と緯度・経度データを検索し、結果をJSON形式で返す
     * レスポンスとして返されるJSONの形式:
     *  [ {"address":"HOGEHOGE","location":[xxx,xxx],"zip":"xxxxxxx"},
     *    {"address":"PIYOPIYO","location":[xxx,xxx],"zip":"xxxxxxx"}, ... ]
     */
    @Path("search")
    @GET
    @Produces({MediaType.APPLICATION_JSON})
    public List<AddressGps> search(
                      @QueryParam("zip") String zipcode)
    {
        List<AddressGps> address = addressList.search(zipcode);
        return address;
    }
    
}

 このクラスのsearch()メソッドが、郵便番号を「zip」パラメータで受け取り、検索結果をJSON形式で返すメソッドである。コンテキストパスは「/address/search」で、GETリクエストを受け付ける。

 肝心のデータソースはAddressGpsListクラスとして定義しており、実装はCDIの@Injectアノテーションを利用してインジェクトしている。ZipcodeServiceのときと同様に、これでJAX-RSのフロントエンドと内部ロジックの依存性を取り除くことができる。

 本稿の例ではAddressGpsListクラスの実装は次のようにした。

リスト7.3
@ApplicationScoped
public class AddressGpsList {
    
    final String baseurl = "http://localhost:9080/ZipcodeService/ws/address/";
    
    public List<AddressGps> search(String zipcode) {
        List<AddressGps> result = new ArrayList<>();  // 結果格納用のList
        
        Client client = ClientBuilder.newClient();
        
        // ZipcodeServiceに対してリクエストを発行
        String request = "{ \"zip\":\"" + zipcode + "\" }";  // リクエスト用JSONデータ
        WebTarget target = client.target(this.baseurl + "jsonsearch");
        Entity<String> entity = Entity.entity(request, MediaType.APPLICATION_JSON_TYPE);
        String response = target.request(MediaType.APPLICATION_JSON_TYPE).post(entity, String.class);

        // レスポンスのJSONデータからJsonObjectを作成
        JsonReader jsonReader = Json.createReader(new StringReader(response));
        JsonArray jsonArray = jsonReader.readArray();
        List<JsonObject> list = jsonArray.getValuesAs(JsonObject.class);
        
        // Google Maps APIへのリクエストを発行
        String googlemapUrl = 
                "http://maps.googleapis.com/maps/api/geocode/json?address=" + 
                        zipcode + "&language=ja&sensor=false";
        WebTarget mapTarget = client.target(googlemapUrl);
        String mapResponse = mapTarget.request(MediaType.APPLICATION_JSON_TYPE).get(String.class);
        // レスポンスのJSONデータを解析して、緯度・経度を取得
        double[] location = getGeoLocation(mapResponse);
        
        // ZipcodeServiceからのレスポンスを1件ずつ処理する
        for(JsonObject obj: list) {
            // 郵便番号、住所、緯度・経度からAddressGpsを作成してListに格納
            String address = obj.getString("address");
            result.add(new AddressGps(zipcode, address, location));
        }
        
        // 結果のListを返す
        return result;
    }
    
    private double[] getGeoLocation(String jsonString) {
        // JsonObjectを作成
        JsonReader jsonReader = Json.createReader(new StringReader(jsonString));
        JsonObject rootObj = jsonReader.readObject();
        
        // "status"オブジェクトを取得し、値が"OK"の場合のみ処理する
        String status = rootObj.getJsonString("status").getString();
        if(status.equals("OK")) {
            // "results"配列を取得
            JsonArray resultsArray = rootObj.getJsonArray("results");
            
            // "geometry"オブジェクト内の"location"オブジェクトを取得
            JsonObject geometryObj = 
                    resultsArray.getJsonObject(0).getJsonObject("geometry").getJsonObject("location");
            
            // 緯度・経度を取得
            double lat = geometryObj.getJsonNumber("lat").doubleValue();    // 緯度
            double lng = geometryObj.getJsonNumber("lng").doubleValue();    // 経度
            
            // 戻り値の配列を作成して返す
            double[] location = {lat, lng};
            return location;
            
        } else {
            // "status"がOK以外の場合はnullを返す
            return null;
        }
    }
}

 データの取得元は2か所あり、1つはZipcodeServiceで、もう一つはGoogle Maps APIである。前者からは住所を取得し、後者からは緯度・経度を取得し、それらを合わせてAddressGpsオブジェクトを作りレスポンスとして返すようにしている。

 まずZipcodeServiceに対しては、/address/jsonsearchに対して郵便番号をJSON形式で渡し、返ってきたJSONデータから住所の部分を取り出している。JSONデータの解析にはJSON-PのオブジェクトモデルAPI(javax.json)を利用している。なお、この例ではJSONを使ったPOSTリクエストを行っているが、GETリクエストでも同様の結果を得ることができる。

 一方、Google Maps APIについては、「Geocoding API」を使うことで郵便番号から対応する各種位置情報を取得することができる。具体的には、「address」パラメータに指定してGETリクエストを送ればいい。実は住所の情報はGoogle Maps APIからも取得することができるが、今回は複数のRESTfulサービスを組み合わせるサンプルということで、緯度・経度の情報のみりようするこのAPIではリクエストパスとして「http://maps.googleapis.com/maps/api/geocode/json」を使うことで結果をJSON形式で受け取ることができる。レスポンスデータなどの詳細はGoogle Maps Geocoding APIのサイトを参照していただきたい。緯度・経度の値は、locationオブジェクトのlatプロパティおよびlngプロパティにセットされているので、これをJSON-P APIを使って取り出せばよい。

 最後にApplidationConfigクラスについては、リクエストを受け取るのがSearchDataクラスなので次のようなコードになった。

リスト7.4
@javax.ws.rs.ApplicationPath("ws")
public class ApplicationConfig extends Application{
        
    public Set<Class<?>> getClasses(){
        HashSet<Class<?>> set = new HashSet<Class<?>>();
        set.add(SearchData.class);
        return set;
    }
}

 以上が完成したら、ZipcodeServiceとZipGeoServiceの2つのプロジェクトをそれぞれLiberty Profile上で実行し、Webブラウザから「http://localhost:9080/ZipGeoService/ws/geosearch/search?zip=郵便番号」のURLにアクセスしてみよう。例えば郵便番号の部分に「5220002」を指定した場合には、図7.5のような結果が返ってくるはずだ。

図7.5
図7.5

8 まとめ

 今回は最新版のLibertyプロファイルおよびLiberty Coreで新たに正式サポートされたJAX-RS 2.0、CDI 1.1、そしてJSON-Pを用いて極めて簡単なRESTful Webサービスを実装してみた。JAX-RS 2.0でサーバーサイドだけでなくクライアントサイドのAPIが充実し、Java単体でのWebサービス間連携が非常に簡単に実装できるようになった。これはデータソースやビジネスロジックとして外部のサービスを手軽に利用できるようになったことを意味している。さらに、そこにCDIを組み合わせれば、フロントエンドと内部ロジックの切り分けが容易になり、アプリケーション全体の柔軟性が大きく向上することになる。

 Liberty Coreの場合、Java EE 7のWeb Profileに準拠しているためこれらの機能を追加のAPIやプラグインなしに利用できるのが強みである。サーバーのフィーチャーとしてWeb Profileが一括で選択できるなどの修正も地味だがうれしい。また、LibertyプロファイルがJava EE 7 Full Profileにも対応したことから、Web Profileに収まらない機能強化が必要になった場合でも、よりシームレスに移行できるという強みも加わった。

 冒頭でも紹介したように、今後は「大きなシステムを小さなサービスの組み合わせで構成する」という考え方がより重要になってくるだろう。開発のスピードに重点を置いたLiberty Coreはそのような時流に乗るための要に成り得る存在と言っていいだろう。

Libery Core関連資料提供のお知らせ

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

杉山 貴章(スギヤマ タカアキ)

有限会社オングスにて、Javaを中心としたソフトウェア開発や、プログラミング関連書籍の執筆、IT系の解説記事やニュース記事の執筆などを手がけている。そのかたわら、専門学校の非常勤講師としてプログラミングやソフトウェア開発の基礎などを教えている。著書に『Javaアルゴリズム+データ構造完全制覇』『Ja...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

【AD】本記事の内容は記事掲載開始時点のものです 企画・制作 株式会社翔泳社

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/8834 2015/08/05 10:39

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング