ボクココ

熱海で開発するブログ

今作のAWS熱に思うこと

最近はどのスタートアップでも「AWSを導入しています!」と意気揚々としているところが多い。 なるほど、ソーシャルゲームでは秒間数100リクエストが来る可能性を見込んでのスケールできるようなサーバ構成、そしてイケてるデプロイ環境。そしてそのスピード感。かっこいいね。 はて、そこまでかっこよくサーバ構成を構築して実際に成功したスタートアップはいかほどのものだろう?まぁ会社にある程度資金があれば、サーバ代は月10万以上いっても大して痛くないのかもしれない。ただそれはAmazon様のマーケティングにうまく踊らされている感が否めない。

もっと早く!もっと柔軟に!

自前でサーバを立てるってのはぶっちゃけ時代遅れなのかもしれない。これからは水は自分で汲んでくるのではなく、蛇口をひねれば誰でも使えるような、そんなサーバインフラ、いわるゆクラウドの全盛期になるだろう。そんなのは数年前から言われているのに、未だにAWSが熱いのはなぜだろう?そんときに知らなければならないのは、どうやって蛇口をひねるかであり、どっから水を汲んでこなければならないのかは知らなくていいのだ。 AWSは本当に負荷が問題になってきてからで良いのではないか。それまでは低コストに低コストを重ねて、月1万で抑えられるような Heroku や Parse の利用を検討すべきではないのか。 それらはまさに蛇口だ。インフラを知らなくてもアプリ開発に集中できる。そしてある程度の負荷までは耐えられる。事実、自分のアプリで秒間10リクエストくらいのアクセスが来るが、無料でも十分対応できる。それなのに、対してアクセスも来ないのにAWSで構築するのは時期尚早だと思う。

ここで反論の声もあるだろう。「いきなり負荷が上がって対応できなくなったらどうするんだ!」と。そんなことは Heroku も Parse も百も承知だ。そこから多少お金をかけて性能のいいサーバに移したり、複数台構成にすればいい。それもブラウザ上でドラッグするだけでできてしまうのだ。 しばらくはそれで乗り切りながら、人気になってきたら徐々にAWSに移行していけば問題ない。

PaaSやBaaSは柔軟性が無い!という人もいるかもしれない。それを言うのはそれらを使ったことが無い人だろう。ご存知の通り、 PaaS には様々な外部ホスティングサービスがあり、 好きなDB、好きなキャッシュ、好きな検索エンジン、動画エンコーダなどを使える。もちろん Cron なども。それらで提供されるアドオン以外でやらなきゃならないことって何があるだろう。自前でC++などで作った独自アルゴリズムを組むんだ!っていう高度なエンジニアがいれば話は別なのかもな。

まとめ

インフラ環境をガチでやるのは、アプリが人気になってきてからで良い。それまではクラウドサービスを使ってできるだけ低コストで運用し、ユーザ獲得に全力を注ごう。

Volley の2重リクエストを防ぐ

結構よく起きるので、メモ。

これが起きてしまう原因は、サーバのレスポンス速度が遅いことにある。

サーバ内でメール送信してたりとか(本当はキューに溜めるべきだが)、アクセスが多くなってきたりしたりとかで遅くなると、Volleyはもう一回リクエストを送ってしまうのだ。

対処法

Volley の RetryPolicy を更新してあげればよい。

    public RequestQueue getRequestQueue() {
        if (mRequestQueue == null) {
            mRequestQueue = Volley.newRequestQueue(getApplicationContext());
        }

        return mRequestQueue;
    }

    public <T> void addToRequestQueue(Request<T> req) {
        req.setTag(TAG);
        req.setRetryPolicy(new DefaultRetryPolicy(50 * 1000, 0, 1.0f));
        getRequestQueue().add(req);
    }

これで結構待ってみてくれるようになって、一件落着。

Android の ScrollView で EditText のキーボード非表示にしつつトップに移動する

毎回 ScrollView 付きのフォームを作るときに苦戦するのでメモ。

例えば、保存ボタンがScrollViewの一番下にあり、それを押すと検証が走る。 検証が失敗した時は一番上にエラーメッセージを出したいといった場合。

問題は、途中にあるEditTextのキーボードを非表示にしつつ、フォーカスを外して一番上まで持って行かなければならないという点。途中のフォーカスにひっかかって一番上までいってくれなくて、ハマる。。

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollView"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:padding="@dimen/blank_medium"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/account_err_txt"
            android:visibility="invisible"
            android:focusable="true"
            android:focusableInTouchMode="true"
            style="@style/FormParts"
            android:text="Error Messages"
            android:textColor="@color/error" />

....

        <Button
            .....

    </LinearLayout>
</ScrollView>

ここで大事なのが、一番上にあるエラーメッセージのTextViewにフォーカスを当てられるようにすることだ。んでコードを書く。

findViewById(R.id.btn).setOnclickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
         // キーボードを隠す
         InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
         inputMethodManager.hideSoftInputFromWindow(ed.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);   

        // フォーカスを一番上にあるエラーテキストにセット
        errorText.requestFocus();
        // スクロールを一番上へ
        scrollView.fullScroll(View.FOCUS_UP);
    }
}

もっといい方法あるのかな。

アプリを無料で他言語対応させる方法

世の中にはお金を払ってアプリを他言語対応しようとする人が多い。これがまた結構無駄に高くて、それでも元が取れるという根拠のある自信があればいいが、まず海外でうまくいくかの保証はあまりないので、とりあえずやってみたい、くらいの感じだろう。

自分のアプリは、現在「日本語」、「英語」、「韓国語」、「ドイツ語」、「ポルトガル語」「スペイン語」に対応している。しかも翻訳にかけた料金は"ゼロ"だ!

では、どうすれば他言語対応を無料で実現するのか?

それは、「ユーザにただでやってもらう」のだ。

そうするための手順を紹介しよう。

  • まず最低限、「英語版」は自分で作らなければならない。英語ができなければ、英語の勉強からやり直そう。海外の人とコミュニケーションがとれないようじゃ、お金を払うしかない。
  • アプリに熱狂的なファンを持とう。誰かの心に突き刺されば良い。
  • アプリ内のどこかに、翻訳募集のエリアを儲けよう。「"アプリ名"はあなたの言語を勉強中です!」みたいな感じで。そこをタップしたら自分にメールが送られるようにすればいい。
  • メールまでは意外と何人か来る。だが、実際にやってくれる超熱狂的ファンはその中のさらに5分の1。彼らに何かしらのお礼ができるような仕組みを作ろう


他言語対応で世界中のユーザから使われるアプリを作ろう! ちなみに他言語対応すると、4分の1以上が海外ユーザくらいにはなる。そして海外ユーザは割と「アプリ内課金」をしてくれる率が高い気がする。是非試してもらいたい。

サービスを1から開発する時の実施方法

ようやく自分のサービスが完成に近づいてきた。エンジニアとして自分の頭の中で描いていたものが実現されていく過程ってのは本当に嬉しい。この思いを他の方にも味わってもらいたいと思い、このエントリーを書いてみる。

なぜサービス開発を開始できないのか

人と話していると、こういうサービスが作りたい、って相談をよく受ける。もちろんその人がエンジニア経験全くなしで言って来る場合もあるけど、エンジニアでもそこで行動を思いとどまることが多く見受けられる。これは自分も実際にそうだったんだけど、 何から開始すればいいのかがわからない という問題が根本にあるように思える。目の前が暗いから先に進むのを拒んでいる。だから今回は一人で何かサービスを作るときに考える手順を紹介しよう。

まずは必要と思われる機能を上げよう

パッと思いついた神懸かり的なアイディアにおいて必要と思われる機能をどんどん書こう。その中でまずは登場人物(レッスンサイトだったら先生、生徒、管理者など)を挙げる。登場人物毎にそれぞれで使う機能を考えてみよう。この時点ではまだ紙ベースで何ら構わない。「これ必要ってことはこれも必要か、、」みたいな連鎖が生まれてい来る感じがなかなか面白い作業だと思う。これを一般的にはユースケースというらしいが、そんなこと知らなくてもいい。

複雑になりそうな部分の流れを書こう

大体機能が決まったら、その中で複雑になりそうな部分の流れを図にまとめてみよう。これで複雑なものをシンプルにし、何を実装しなければならないのかが明確になる。ここからは最初は手書きでも良いけど、後の修正を考えるとソフトウェアを使う方が望ましい。今回は astah というモデリングツールを利用した。例えばこんな感じだ。

f:id:cevid_cpp:20140703100626p:plain

上に関連しそうなものを上げ、それがどのようなフローで関わり合っているのかを示す。左右の空白にコメントを残しておくと、後で便利だ。ここまでできたら、とりあえず誰かに見せてどう思うか聞いてみた方がいいかもしれない。思いがけないアドバイスを貰えるかもしれない。これを一般的にはシーケンス図というが知っておくといいかも。

おおざっぱなデータ構成を書こう

複雑な部分が大体見えてきたら、データベースのテーブル設計にとりあえず入る。ここでどんなのがデータとして保存しないといけないのかを明確にする。リレーションとかも考える必要があるけど、ここら辺は慣れと経験が必要なのは否めない。この時点で完璧な図を目指す必要は無いので、思いつく限り挙げていけばいい。

これを一般的にはER図というが知っておくべき。

画面設計をしてみよう

ある程度できてきたらどういう画面構成にしないといけないのかを考えていこう。各ページにボタンやテキストを配置してみてそれでサービスとして使えるのかを検証していく。ここでER図やシーケンスにおける過不足が見えてくるので、そこを適宜戻って補足していく。自分はPencil というソフトウェアを使ったが、ウェブでもできるような便利なサービスがちょくちょくできて見ているので探してみるといいだろう。ただウェブにあがってるのは大抵1週間無料でそこから有料みたいなのが多かったので無料でPencilくらいの機能があれば十分だと考えている。例えば新規登録画面はこんな感じだ。

f:id:cevid_cpp:20140703101607p:plain

こんな感じで全てのページに適用する。これが出来上がる頃には、洗練された設計図が出来上がるだろう。この全ての行程をクリアするには1〜3週間くらい必要だと思って欲しい。1日で設計作ってすぐ実装ってのは規模の小さいアプリなら良いが、あなたの素晴らしいアイディアを実現するにはもっと時間をかけて設計すべきだ。こういうのをデザイナーに任せてはいけない。意味不明な論理でのページ遷移や実装不可能な提案をしてくる可能性があるからだ。それをいちいち指摘してまた戻ってみたいな面倒なことになぬよう、ここは一番情熱のあるあなたにやって欲しい。

まとめ

最終的には画面設計とER図さえあれば開発はスタートできると思うので、この2つを完璧に作るように頑張る。そしたらもう一心不乱に開発していくだけだ。自分はかつてちょっと設計しただけでいきなり実装に入ろうとし、全く前に進めない状況に立ったことがある。そういうことのないよう、是非これを読んでくれた人にはちゃんとした設計から入ることをお勧めしたい。

サービス利用規約のお話

少人数でサービス開発をしていると、技術的な問題よりもむしろこういう法律とかお金とかそういう方がむしろ大変になる。今回はこの本を買って勉強しているので自分なりに理解したのをざっくり書いてみる。

良いウェブサービスを支える「利用規約」の作り方

良いウェブサービスを支える「利用規約」の作り方

利用規約は守り

一般的にサービス開発は技術力やアイディアで勝負する形だが、守りがしっかりしていないと運用時のトラブルとかの対応ですぐ攻撃に力が入らなくなる。どのゲームとかでもそうであるように、強い守りがあれば引き分けはあっても負けることはない。この負けないってのがやはり大事だ。ま、利用規約だけで負けないって訳にもいかないがw

利用規約の同意は必ず実施

新規登録とかで特に規約も読ませずに登録を完了させるサイトが割と多いが、これはせっかくどっかに利用規約を用意しても同意を貰ってないので意味がなくなってしまう。明確に読んだことを表示してみせないと効果が薄れてしまうみたい。

サービスに応じて規制がある

出合い系、チャット系、旅行系、色々なサービスがあるがそれら各々に規制があることを知る。もし作ろうとしているサービスがグレーゾーンである場合、それが規制に引っかからないという理屈を持っている必要がある。当局から弁明を求められたときに説明できるようにしておかなければならない。それができないとサービス閉鎖のリスクもある。

プライバシーポリシーと特定商取引法に基づく表示

プライバシーポリシーも同様に設置しなければならない。これは例えば取得したユーザのメールアドレスをどのように利用するかを明文化しなければならず、ここで例えばDMを行なうというようなことが書いてないと、なんで勝手にメール送ってくるんだといったトラブルに巻き込まれる。プライバシーポリシーは利用規約と同等の方法でユーザに表示する。
特定商取引法に基づく表示に関しては物販等をしなければ義務はないらしい。ただこの11項目を表示することでお問い合わせ先としての機能を果たすこともできるので、導入している企業が多い模様。

商標

サービス名がパクった、パクられたみたいな話も注意しなければならない。まずはサービス名を特許電子図書館でざっくり調べて問題なさそうなら商標を専門家とともに登録した方が良い。

ユーザアップロードコンテンツ

画像とか投稿させたときに違法なコンテンツをアップロードする輩がいる。これらは例えアップロードされたとしても放置すると会社側にも責任が出て来る。見つけ次第消したりする運用は必要になるようだ。ただ、そのために規約としてユーザがアップロードしたコンテンツはどのように扱われるか、っていうのをちゃんと書いておかなければならない。

今後の方針

まずは自分でできる限り利用規約を他サイトを参考に作成してみて、作り終わったら適宜専門家に見てもらうようなやり方が一番いいかと。こればっかりは突き詰めるとキリがないしこれを頑張るくらいならサービスをよくしていく努力をした方が良い。てことで法律詳しい人は別に用意した方がよさそうだ。

Volley + Gson + Generics = God! (Android で OAuth な Rest Api のクライアント作成)

Android アプリでよくあるパターンとしては Restful な Web API を呼んで、リストや詳細を表示などが挙げられる。こんなとき、JSONで通信しているのであれば、リクエストのパラメータを作り、レスポンスを解析するというコードを書く必要がある。これが例外処理やらnullチェックやらで何かと面倒。そう感じることが多かった。 最近、gsonとの出会いがあった。なるほど、これを使えばJSONをパースせずともGetter, Setter を持つモデルクラスを作ればそこに詰め込んでくれる優れものだ。だがもっとシンプルにできないだろうか。。

Gson と Generics の相性

Generics は型に縛られず共通処理を書けるようになるテクニックだ。これを使えば以下のようなシンプルなREST クライアントが作成できる。

色々書いて気づいたが、下記コードを見るだけだととてもわかりにくい。この部分だけ切り出したりできないか検討します。。

public class RestApi {
    public static <T> void index(String url, final Class<T[]> clazz, final ApiCallbackBase.ApiCallback<T[]> callback) {
        ApiRequest.get(url, getListHandler(clazz, callback));
    }

    private static <T> ApiResponseHandler getListHandler(final Class<T[]> clazz, final ApiCallbackBase.ApiCallback<T[]> callback) {
        final Context context = ApplicationController.getInstance().getApplicationContext();
        return new ApiResponseHandler() {
            @Override
            public void onSuccess(JSONObject jsonObject) {
                try {
                    Gson gson = new GsonBuilder()
                            .setDateFormat(context.getString(R.string.date_parse_in)).create();
                    LogUtil.d(jsonObject.getJSONArray("item").toString());
                    T[] dtoList = gson.fromJson(jsonObject.getJSONArray("item").toString(), clazz);
                    if (callback != null) {
                        callback.onSuccess(dtoList);
                    }
                } catch (JSONException e) {
                    onFailure(new ApiException(e.getMessage(), ApiException.JSON_PARSE_ERROR));
                }
            }

            @Override
            public void onFailure(ApiException e) {
                LogUtil.e("Index Failed..");
                LogUtil.e("msg:" + e.getMessage() + ", status: " + e.getStatusCode());
                if (callback != null) {
                    callback.onFailure(e.getMessage(), e.getStatusCode());
                }
            }
        };
    }

}

ApiRequest で Volley のhttp 通信を行なう。

public class ApiRequest {

    public static void get(String url, final ApiResponseHandler handler) {
        try {
            request(Request.Method.GET, url, null, handler);
        } catch (JSONException e) {
            handler.onFailure(new ApiException(e.getMessage(), ApiException.JSON_PARSE_ERROR));
        }
    }

    private static void request(final int method, final String url, final JSONObject params, final ApiResponseHandler handler) throws JSONException {
        RequestDto reqDto = new RequestDto(method, url, params);
        reqDto.setAccessToken();
        JsonObjectRequest req = new JsonObjectRequest(reqDto.method, reqDto.url, reqDto.params,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        handleSuccessResponse(response, handler);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError e) {
                LogUtil.e("Error: " + e.getMessage());
                e.printStackTrace();

                int status = ApiException.UNKNOWN_ERROR;
                if (e.networkResponse != null) {
                    status = e.networkResponse.statusCode;
                }
                if (status == 401) {
                    if (params != null && params.has(TokenParamDto.GRANT_REFRESH_TOKEN)) {
                        SignupActivity.redirectToSignup();
                    } else {
                        tokenRefresh(method, url, params, handler);
                    }
                    return;
                }
                try {
                    String responseBody = new String(e.networkResponse.data, "utf-8");
                    JSONObject jsonObject = new JSONObject(responseBody);
                    LogUtil.e(jsonObject);
                } catch (Exception e2) { }

                handler.onFailure(new ApiException(e.getMessage(), status));
            }
        }
        );
        ApplicationController.getInstance().addToRequestQueue(req);
    }

抜粋ではあるが、OAuthのHttpクライアントとしてリフレッシュトークン、アクセストークンのやりとりを実装するには自前でやるのが一番早い。これらを必要としないシンプルなREST Apiであれば、Retrofit などのOSSを使うことも検討できるだろう。ただ、中身がVolleyじゃなくなるのでそこら辺、自分としては気持ち悪いところ。そこまでこだわりなければ全然使っていいと思う。