1. DaVinciPad開発で学んだこと(GAE/J編)

    白石俊平@OpenWeb Technology
  2. 自己紹介(1/2)

    • 株式会社OpenWeb Technology代表
    • Google API Expert (HTML5) / html5-developers-jp管理人
    • 著書に「HTML5&API入門」があります。
  3. 自己紹介(2/2)

    • AppEngine、あんまり詳しくありません(^^;
      アプリの要件を満たすのが最優先だったので・・・
    • 今日は、ぼくの個人的なAppEngine開発経験を発表・共有させてください。
  4. DaVinciPad

  5. DaViniciPadとは

    • プライベートなメモ機能と、マルチポスト機能を組み合わせたサービス
    • Chrome/Safari Extensionとして提供中
    • 「思いついたことを、好きな場所にすぐメモできる」
  6. DaViniciPadとは

    • プライベートなメモ機能と、マルチポスト機能を組み合わせたサービス
    • Chrome/Safari Extensionとして提供中
    • 「思いついたことを、好きな場所にすぐメモできる」
    まあ、微妙なサービスです。
  7. 本日のポイント

    • ライブラリの使用を躊躇わない
    • 「キツツキ型」スピンアップ対策
    • 涙涙のOpenID対策
    • 全文検索機能の実装方法
  8. ライブラリの使用を躊躇わない

  9. ライブラリの使用を躊躇わない

    • 他力大好きですいませんすいません
    • Jarファイル、50個以上ありました。。
  10. 主に依存してるライブラリ

    JAX-RS
    RESTful Webサービスを作るための標準API
    Guice
    シンプルかつ高機能なDIコンテナ
    Bean Validation Framework (JSR-303)
    値の検証をアノテーションベースで行うためのライブラリ
    Slim3 Datastore
    説明不要ッ!
  11. JAX-RS概要(1/2)

    アノテーションを指定するだけで、RESTfulなサービスを構築可能。

    @Path("/items")
    class Items {
      @GET
      @Produces("application/json")
      public Response get() {
        ...
      }
    }
    			
  12. JAX-RS概要(2/2)

    Jersey(JAX-RSの参照実装)には、RESTクライアントライブラリもついてきてお得。

    // リソースの生成
    WebResource r = httpClient.resource("http://api.twitter.com/1/statuses/update.json");
    // OAuthフィルタを設定
    OAuthParameters params = new OAuthParameters().signatureMethod(
    		HMAC_SHA1.NAME).consumerKey(CONSUMER_KEY).token(accessToken);
    OAuthSecrets secrets = new OAuthSecrets().consumerSecret(
    		CONSUMER_SECRET).tokenSecret(accessSecret);
    OAuthClientFilter filter = new OAuthClientFilter(httpClient
    		.getProviders(), params, secrets);
    r.addFilter(filter);
    
    Form form = new Form();
    form.add("status", "Twitterに送るメッセージ。");
    
    // POSTリクエスト実行。
    ClientResponse response = r.type(MediaType.APPLICATION_FORM_URLENCODED)
    		.post(ClientResponse.class, form);
    			
  13. Guice(1/2)

    Google謹製DIコンテナ。
    アノテーションによる指定でほとんどのDI設定を行うので、設定作業が最小限。

    class Item {
      @Inject
      UserService userService;
      ...
    }
    			
  14. Guice(2/2)

    GuiceyFruitを使えば、javax.annotationパッケージと統合できる。
    カスタムスコープやProvider Injectionと組み合わせれば、JavaEE6 / CDIっぽいプログラミングが可能

    class Item {
      @Inject
      UserService userService;
    
      // Provider injection
      @Inject
      Provider<HttpServletRequest> request;
    
      // DI後に呼び出される
      @PostConstruct
      public void init() {
        // コンテキストに応じた適切なオブジェクトが返る
    	HttpServletRequet req = request.get();
    	...
      }
      ...
    }
    			
  15. Bean Validation Framework(1/3)

    基本は、JavaBeanのプロパティ値の検証を行うためのフレームワーク。
    値の制約をアノテーションで指定可能。

    class Person {
      @NotNull @Pattern("\\w{1,20}")
      String name;
    
      @Min(0) @Max(120)
      int age;
    }
    			
  16. Bean Validation Framework(2/3)

    制約をまとめて、一つのアノテーションにすることが可能。

    
    @Constraint(validatedBy = PersonValidator.class)
    @NotNull @Pattern("\\w{1,20}")
    annotation PersonName {
    }
    

    class Person {
      @PersonName
      String name;
    
      @PersonAge
      int age;
    }
    			
  17. Bean Validation Framework(3/3)

    制約アノテーションと、パラメータバリデーション(まだ非標準)の組み合わせは強力!
    以下の例ではJAX-RSと組み合わせて、リクエストパラメータのバリデーションも自動化している。

    @Path("/persons")
    class Persons {
      @POST
      void post(
        @PersonName String name,
        @PersonAge int age) {
        ...
      }
    }
    			
  18. 「キツツキ型」スピンアップ対策

  19. 「キツツキ型」スピンアップ対策(1/2)

    Chrome Extensionなので、ずっとバックグラウンドでpingをうち続けられる。

  20. 「キツツキ型」スピンアップ対策(2/2)

    Retry-Afterヘッダを用いて、pingの間隔をサーバがコントロール可能。

    class PingServlet extends HttpServlet{
      public void doGet(HttpServletRequest req, HttpServletResponse resp) {
        int retry = ...;
        resp.setIntHeader("Retry-After", retry);
      }
    }

    function ping() {
      var request = new XMLHttpRequest();
      request.onreadystatechange = function() {
        if (request.status != 4)
          return;
        var retryAfter = parseInt(request.getResponseHeader("Retry-After"));
        setTimeout(ping, retryAfter);
      }
      request.open("GET", "/ping");
      request.send();
    }
    ping();
  21. 涙、涙のOpenID対策

  22. 涙、涙のOpenID対策(1/3)

    「OpenID4Java」というライブラリをベースに、OpenID対応コードを書いていましたが・・・
    AppEngine本体がOpenIDに対応してしまったせいで、全部捨てるハメに。。

  23. 涙、涙のOpenID対策(2/3)

    AppEngineでのOpenID認証の利用方法

    1. 認証方法でFederated Login (Experimental) を選択
    2. ユーザが選択したOpenIDを引数に指定しつつ、UserService#createLoginURL()を呼び出し、そのURLにリダイレクト
      String loginUrl =
        userService.createLoginURL("/callback", "www.davincipad.net",
          openId, null);
      response.sendRedirect(loginUrl);
    3. あとは、通常のUserService#getCurrentUser()などでログイン済みのUserを取得できる。

  24. 涙、涙のOpenID対策(3/3)

    Userをパラメータとしたクエリは禁止!
    Emailでの比較になってしまう(バグ?もう直ってる?)が、OpenIDプロバイダによっては、メールアドレスを返さない。

    User loggedIn = userService.getCurrentUser();
    AccountMeta meta = AccountMeta.get();
    Account account =
      Datastore.query(meta).filter(meta.user.equal(loggedIn));
  25. 全文検索機能の実装

  26. 全文検索機能の実装(1/5)

    guestbook-example-appengine-full-text-searchを参考に、全文検索機能を実装しました。
    もとはJDO向けのサンプルだったので、ロジックをパクってSlim3上で実現しました。

  27. 全文検索機能の実装(2/5)

    保存時の処理

    1. テキストをトークンにバラして
    2. Set<String>型のプロパティにセットして保存
    public class FTSUtils {
        // CJKに対応したアナライザ
        private static CJKAnalyzer analyzer = new CJKAnalyzer(Version.LUCENE_30);
        public static Set toTokens(String text) {
            Set results = new HashSet();
            // トークンに分割
            TokenStream tokens = analyzer.tokenStream(null, new StringReader(text));
            while (tokens.incrementToken()) {
                TermAttribute term = tokens.getAttribute(TermAttribute.class);
                results.add(term.term());
            }
            return results;
        }
    }
    		  
  28. 全文検索機能の実装(3/5)

    保存時の処理

    1. テキストをトークンにバラして
    2. Set<String>型のプロパティにセットして保存
    // 全文検索のインデックスを保存するためのモデル
    TextItemIndexModel index = new TextItemIndexModel(this);
    // バラしたトークンをセットして保存
    Set<String> terms = FTSUtils.toTokens(getTextData());
    index.setTerms(terms);
    Datastore.put(index);
    		  
  29. 全文検索機能の実装(4/5)

    検索時の処理

    1. 検索クエリをトークンにバラして
    2. INで検索(大丈夫か?)
    Set tokens = FTSUtils.toTokens(query);
    
    
    
    
    		  
  30. 全文検索機能の実装(5/5)

    検索時の処理

    1. 検索クエリをトークンにバラして
    2. INで検索(大丈夫か?)
    Set tokens = FTSUtils.toTokens(query);
    TextItemIndexModelMeta meta = TextItemIndexModelMeta.get();
    List indexKeys =
    	Datastore.query(meta).filter(
    				meta.terms.in(tokens)).asKeyList();
    		  
  31. まとめ&雑感

    • ライブラリを使いすぎたことに対して、後悔はしていない。遅いけど
    • 終わってみると、フレームワークをかき集めて、JavaEE6に近いものを一生懸命自作していた感じ。何やってんだ。
    • Bean Validation Frameworkは、パラメータの検証まで含めれば、かなりいい感じのAPIだと感じました。パラメータ検証はまだ非標準だけど。
    • 商用サービスを作るのって、技術以外のところで難しいことがいっぱいあるなあ、と。ビミョーなサービス作ってすいません。
  32. ご静聴ありがとうございました。

    • Twitter: @Shumpei
    • Blog: http://d.hatena.co.jp/Syunpei
    • mailto: shumpei.shiraishi [at] gmail.com