Clojureでブログやスマホアプリを作ってみる part1

こんにちは。リサーチ・アンド・イノベーションの小川(J-ogawa)と申します。

iOSアプリの開発とサーバサイドの開発をやっています。

私の記事はClojureがテーマです。

Clojure

なぜClojureなのか。

恐縮ながら個人的趣味に基づきます。Clojureシンタックスが一貫しているのと、表現力が高い所がとても好きです。他にも色々といい点はあると思いますが、好きで採用しています。

数年前にClojure作者のRich Hickeyのプレゼンを見て、感銘を受け、勉強を始めた思い出があります。

まだまだClojureはマイナーで、弊社RNIでも主流はRuby, Ruby on Railsですが、Clojureを採用することも視野に入っていたりいなかったり。

Clojureフレームワークre-frameでブログを作成

Clojureを使ってブログを作成してみました。 フレームワークre-frameというSPAパターンを使用しています。

複数回に分けて、ブログのソースを追いながらClojure(re-frame)の解説を行いたいと思います。 Clojure製アプリのノリを少しでもお伝えできたらと思います。

また、ここから派生してスマートフォンアプリを作ることも予定しています。

ソースはコチラ https://github.com/r-n-i/blog (解説時は20170601タグ地点)

解説記事予定

全4回に分けて解説を行いたいと思います。

  1. re-frameの概要
  2. re-frameのdispatch時の副作用について
  3. サーバサイド
  4. re-frameでiOS & Androidアプリを作る

re-frame

re-frameというSPAパターンを使っています(内部でreactインタフェースのreagentを使用) 公式のdocsがとても充実していて、読み物としてもとても面白いです。

re-frameにおいて、画面表示の流れは以下となります。

dispatch -> update db(単一のデータ) -> subscribe -> update view

今回はこの流れについて解説します。

日本語での情報としては、こちらの解説もとてもわかりやすいです。

では、re-frame製ブログのソースの方を追っていこうと思います。

ビルドツール

Clojureでは、leiningenというビルドツールが主に使われます。 プロジェクト直下のproject.cljにはその設定を書きます。 また、このプロジェクトはleiningen templateのre-frame-templateより生成しています。

lein new re-frame <project-name>

で、re-frame用のプロジェクトの雛形が生成されます。railsでいうrails newです。

起動

作成したブログのソースについては以下手順で起動を試していただけます。

  1. MySQLで、blogデータベースとblog@localhostユーザを作成
  2. git clone https://github.com/r-n-i/blog
  3. cd blog
  4. lein migratus migrate(マイグレーション)
  5. lein figwheel

サーバが立ち上がりhttp://localhost:3449で確認できます。 figwheelは自動でソースリロードしてくれる便利なツールです。

フォルダ構成

以下のようになっています。

├── Procfile
├── README.md
├── project.clj
├── resources
│   ├── migrations
│   └── public
│       ├── css
│       ├── index.html
│       └── vendor
└── src
    ├── clj
    │   └── blog
    │       ├── core.clj
    │       ├── handler.clj
    │       └── server.clj
    └── cljs
        └── blog
            ├── config.cljs
            ├── core.cljs
            ├── db.cljs
            ├── events.cljs
            ├── subs.cljs
            └── views.cljs

srcフォルダの中にソースを書いていきます。

cljフォルダとcljsフォルダがあります。 サーバサイドの方はclj(Clojure)、クライアントの方はcljs(ClojureScript)です。

今回はクライアントサイドについて解説をします。 具体的なコードが続きますが、要点だけ押さえていただければと思います。

クライアントサイド

re-frameはwebページとしてresource/public/index.htmlを返します。

resource/public/index.html

<!doctype html>
<html lang="en">
  <head>
    <meta charset='utf-8'>
    <link rel="stylesheet" href="css/bulma.css">
  </head>
  <body>
    <div id="app"></div>
    <script src="js/compiled/app.js"></script>
    <script>blog.core.init();</script>
  </body>
</html>

cljs以下に書いたClojureScriptコードはコンパイルされて一つの.jsファイルになります。 <script src="js/compiled/app.js"></script>でその.jsファイルを読み込み、blog.core.init()を実行して画面をレンダリングしています。

これはsrc/cljs/core.cljsのinit関数を呼ぶ事に相当します。見てみましょう。

src/cljs/core.cljs

....

(defn ^:export init []
  (re-frame/dispatch-sync [:initialize-db])
  (dev-setup)
  (mount-root)
  (re-frame/dispatch [:get-entries])
  (re-frame/dispatch [:auth]))

initにはいろいろ書いてますが、この中のmount-rootがviewを生成しています。

View

src/cljs/core.cljs

....

 (defn mount-root []
  (re-frame/clear-subscription-cache!)
  (reagent/render [views/main-panel]
                  (.getElementById js/document "app")))

....

このmount-root内でレンダリング関数reagent/render<div id="app"></div>の初期DOMに対してview/main-panelレンダリングしています。 (関数がhoge/fugaとなっている場合のhogeはnamespaceです)

画面についてのコードはsrc/cljs/views.cljsです。 大部分はhiccupというhtmlテンプレートで記述しています。re-frameはこのシンプルなhiccupを直感的に使えるのが魅力です。

最下部の関数main-panelを見てください。

src/cljs/views.cljs

(defn main-panel []
  (fn []
    (let [error @(re-frame/subscribe [:error])
          mode @(re-frame/subscribe [:mode])
          show-login-modal @(re-frame/subscribe [:show-login-modal])]
      [:div
       [nav]
       [header]
       (when error [:div.notification.is-warning error])
       (when (= mode :edit) [editor])
       [entries]
       (when show-login-modal [modal])
       ]
      )))

この関数がこのblogの全体部を構成しています。 div以下を見ていただくとなんとなく各パーツが配置されているんだなという感じがすると思います。

Subscribe

main-panelのlet句でsubscribeをしています。

re-frameでは単一のデータを元にUIを管理しています。 そのデータについて、dbと呼んでいます。(紛らわしい気もしますね・・) dbの変化を購読(監視)するのがsubscribeです。それぞれのパーツが購読したイベントに関してだけ、データの変化を受け取ります。

main-panelは:error, :mode, :show-login-modalの3イベントを購読しています。 イベントにはdbから値を返す関数が設定されていて、dbの変化に伴ってイベントが返す値も変化するときに、購読しているパーツに値(を持つatom)を返します。その際にパーツを再描画します。

ちょっとごちゃごちゃ何言ってるかわかりづらいですが、要はmain-panelは再描画される要因となるデータが3つあります

  • db(単一のデータ)のerror部が更新されたら再描画
    • エラー表示|非表示(when error [:div.notification.is-warning error])
  • db(単一のデータ)のeditモード部が更新されたら再描画
    • 記事編集モードの表示|非表示(when (= mode :edit) [editor])
  • db(単一のデータ)のログインモーダル表示モードが更新されたら再描画
    • ログインモーダル表示|非表示(when show-login-modal [modal])

という感じです。

これら子供のパーツ(editorなど)もそれぞれが独自にsubscribeをできます。これによって、動的に変化する部分のスコープを狭くして把握しやすくできます。

次に、記事一覧パーツのentriesを見ていきます。

(defn entries []
  (fn []
    (let [entries @(re-frame/subscribe [:entries])]
      [:div
       (for [entry- entries] ^{:key entry-}
         [entry entry-])])))

これはentriesイベントを購読しています。 記事一覧もdbのentries領域が更新されることでentriesが再描画をして表示されています。

実は、このサイトは画面を一旦描画した後にentriesをAPIで取得して、取得後に画面更新が行われています(サーバサイドレンダリングを行なっていないため)

ブログの更新をしてみると表示で記事が一瞬遅れていると思います。 画面描画後に 記事一覧取得 -> dbのentries部更新が行われています。

このようなdbに変化をもたらす処理はdispatchです。

Dispatch

dispatchについて、上記で紹介したblog.core.init()(最初に呼ばれる関数)をもう一度見てみます。

src/cljs/core.cljs

....

(defn ^:export init []
  (re-frame/dispatch-sync [:initialize-db])
  (dev-setup)
  (mount-root)
  (re-frame/dispatch [:get-entries])
  (re-frame/dispatch [:auth]))

mount-rootでUIレンダリングを行った後、(re-frame/dispatch [:get-entries])というのをやっていると思います。このdispatchが、dbに変更をもたらす処理となります。画面に変更を起こすにはdispatchが必ず必要です。

dispatch -> update db -> subscribe -> update view となります。

src/cljs/views.cljsを見ると、domのon-clickの際にいろいろとdispatchしているのが見てもらえると思います。

というわけで、足早でしたがざっくりとした解説でした。

まとめ

dispatch -> update db -> subscribe -> update view がre-frameの基本的な流れだということをおさえていただければと思います。

シリーズもので恐縮ですが、次回はdispatchとsubscribeについてもう少し詳細に解説したいと思います。

リサーチ・アンド・イノベーションでは、新技術にアンテナを張り、プロダクトの改善にトライするエンジニアを募集しています。こちら からご応募お待ちしております。

リサーチ・アンド・イノベーション 開発者ブログはじめます

はじめに

リサーチ・アンド・イノベーション 開発部です。
「開発部」が名刺に刷られた正式部署名のはずなんですが
内部では「技術部」あるいは「System Development Division (略してSDD)」と呼ばれることが多いです。
名前なんて、別にいいですね。我々はエンジニアの集団です。

一方、弊社自体はエンジニア集団だけが集った会社ではありません。
色んな才能を持ったメンバーが寄り集まってそれぞれの知見を持ち寄り、以下のサービスを作っています。

Mycomment

mycomment.jp

ユーザが、クライアントから提供された様々なアンケートに答えて報酬を受け取るWebサービスです。
Ruby on Railsで開発しています。

アンケート内容は1問だけの簡単なものから90問ぐらいのヘビー級のものまで様々です。
(ヘビー級のアンケートは、報酬をがっつりGETできます)

Webであること活かして写真や動画をコメントと一緒に提出したりするアンケートもあります。

CODE

code.r-n-i.jp

買物を登録するとポイントが貯まるスマートフォン向けアプリ(iPhone,Android対応)です。
レシートを撮影してバーコードをスキャンすると「いつ、何を買ったか」という買物情報が記録でき、バーコードの数に応じてポイントゲットのチャンスがあります。

CODEの面白いところは、「クエスト」と呼ばれるアンケートの仕組みです。
ユーザが買い物登録をすると、その商品に応じた「クエスト」がもらえることがあります。
「クエスト」はメーカーさんが自社商品等に設定することを想定していて、その商品の確実な購買者に対してアンケートを投げることができます。
ユーザは、このアンケートに答えてもポイントをゲットできます。
こうして「クエスト」で集めたデータはメーカーさんのマーケティングに活用し、商品の改善に役立てて頂いています。

SDDでは、CODEのサーバサイドをRuby on Railsで、iPhoneAndroidのアプリをObjective-CJavaで開発しています。 特にアプリに関してはそれぞれ SwiftKotlin への移行が視野にあるので、 その辺りのことも今後本ブログでお話できればなと思っています。

これからさまざまな情報を発信していきたいと考えております。よろしくお願いいたします。