まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

2020年02月

IBM Cloudant(Apache CouchDB) の MapReduce ビューを使って、特定フィールドの値ごとの文書数を返す API を作ってみました。

なお以下の内容は IBM Cloudant でも Apache CouchDB でも同様に有効だと思っていますが、スクリーンショットなどは IBM Cloudant のものを使って説明しています。ご了承ください。


まず、前提として現状 Cloudant DB 内に以下のような JSON 文書が複数格納されているとします:
{
  "_id": "(id値)",
  "name": "(名前)",
  "date": "(日付)"
}

"name" フィールドに名前、"date" フィールドに日付文字列が格納されます。同じ "name" の値でも "date" の値は異なっていたり、同じ "date" の値でも "name" は異なっていたりするとします:
2020021401


この DB の状態から
 名前(name)ごとにグルーピングして、文書数がいくつずつあるか?
を調べる、というのが今回やりたいことです。

例えば上記例の場合であれば、"name" = "K.Kimura" の文書数は 5 、"name" = "K.Hashimoto" の文書数は 3 、"name" = "M.Matsuoka" の文書数は 2 、といった結果を導き出すための方法です。SQL の使える RDB であれば count() 関数と group by 句を使えば簡単そうですが、NoSQL 型である Cloudant でいちいち全件検索してから "name" の値ごとにカウントして・・・という REST API を作らずに調べるにはどうすればいいでしょうか?

その答が本ブログエントリのテーマでもある MapReduce ビューを作って、Cloudant REST API でこのビューを呼び出すことで実現できます。以下、その手順を紹介します。


まず DB 内に MapReduce ビューを定義するデザイン文書を作成します。画面左のメニュー "Design Documents" の+部分をクリックし、"New Doc" を選択します:
2020021402


新規にデザイン文書を追加する編集画面になるので、以下の内容を入力して "Create Document" ボタンをクリックします:
2020021401

{
  "_id": "_design/myindex",
  "language": "query",
  "views": {
    "count_by_name": {
      "map": {
        "fields": {
          "name": "asc"
        },
        "partial_filter_selector": {}
      },
      "reduce": "_count",
      "options": {
        "def": {
          "fields": [
            "name"
          ]
        }
      }
    }
  }
}

JSON の中身を一応解説すると、"myindex" という名前のデザイン文書を作り、その中で "count_by_name" という名前のビューを定義しています。このビューではまず "name" の値ごとにソート(map)し、その結果を _count 関数でカウント(reduce)した結果を値として持つよう定義しています。

正しく操作できていると Design Documents の中に定義した文書が追加されているはずです。これで MapReduce ビューが定義できました。
2020021404


後は Cloudant REST API でこのビューを呼ぶだけで結果を得ることができます。IBM Cloudant のホストURL (https://xxxx.cloudant.com)に続けて、DB 名(mapreduce)、デザイン文書名(myindex)とビュー名(count_by_name)を指定し、以下の URL にウェブブラウザでアクセスします:
https://xxxx.cloudant.com/mapreduce/_design/myindex/_view/count_by_name?group=true


すると以下のような結果が得られ、期待通りの結果を参照することができました:
2020021405
{
  "rows": [
    { "key" : [ "K.Hashimoto" ], "value" : 3 },
    { "key" : [ "K.Kimura" ], "value" : 5 },
    { "key" : [ "M.Matsuoka" ], "value" : 2 }
  ]
}

これで「DB 内にどんな名前の文書が存在しているか」や「各名前ごとの文書数」を簡単に調べることができるようになりました。

後はこのような処理を行う必要があるぶんだけビューを追加で定義しておけば、それぞれのビューごとに(フィールドとその値ごとに)文書数を調べたり、特定フィールド値の合計値を求めることができるようになります。


誤解のないよう最初に書いておきますが、マンホールマップを始めとする僕個人が開発・運用するサービスを終了する予定がある、という意味・意図のコンテンツではありません。 ただ、もし自分に万が一・・・なことがあったら現実問題としてユーザーに迷惑をかける形で終える形になってしまいそうだし、そろそろこういうのを気にする年齢になってきた(苦笑)ようで、同じような立場の人がいたら意見を聞きたいなあ、、とか、今はこう考えているんだけど間違ってたりしないかな、、という思いからこのブログエントリを書くことにしました。 


改めまして、約10年前から「知る人ぞ知る」という感じでマンホールマップという「自称世界最大の位置情報付きマンホール情報共有サービス」を運営しています。もともとマニア向けに作ったものですが、マンホール蓋(特にデザイン性)に興味を持つ人がそこそこ増えつつあるようで、利用者からの情報を共有いただき続けています。マンホールマップ自体に利益を生む仕組みは(現在は)存在していないので、利益面に関してはプラスというよりはマイナスのサービスです。これまで色々運用形態を変えつつも、開発および運用はすべて自分一人で行っています。なおマンホールマップに投稿いただいた画像やテキストの著作権は投稿者にあるものと規定しています:
2020012801


「すべて自分一人で」の部分をより正確に表現すると「自分一人で開発・運用できる範囲でサービスを提供している」という言い方が正しいかもしれません。正直な所、あまりに急激に人気が出すぎてアクセス数が増えすぎるのも(特に運用コスト面で)困ってしまいますw 今後も安いネットサービスを探しつつ(笑)、細々と続けていく予定です。


で、こういう個人開発・運用サービスの場合、サービス提供開始後しばらくして「全く使われなくなる」ケースも少なからずあると思っています。アクセスログや Google Analytics を使えばコストをかけずに利用状況の確認はできる時代になりました。その結果、自分以外ほぼ誰も使っていないことがわかれば、ある程度周知した上でサービスを止めてしまうことにそこまでの心理的な抵抗もないと思っています(自分の場合はこのケースが多くて、人気のないサービスを終了することへの抵抗はほぼありませんw)。そのようなサービスは本ブログエントリの対象外と考えています。 その上で「個人開発・運用サービスをどうやって終わらせるべきか?」を考えました。


例えばの話ですが、もし万が一、この後自分に何かがあってしまった場合、もうマンホールマップをメンテナンスできる人はいません。マンホールマップは現在はマルチクラウド上で機能ごとに稼働していますが、その(銀行口座が解約されたりして)契約が終了した時点でサーバーインスタンスも止まってしまいます。ユーザーデータを救うこともできないし、「こういう状況なので止まる前にバックアップを取ってください」と予め伝えることもできない可能性もあります。 いわば「保険がかかっていない状態」にあって、「自分が救いの手を差し伸べたい」という奇特な人がどこかに存在していたとしてもどうにもならなくなってしまう可能性すらあります。そうなることを避けて方法はないだろうか?と考え、3つ程案を出してみました。


方法の1つは「家族や友人に託す」という方法です。が、これは(ケース・バイ・ケースですが、ある程度のコスト負担があることを理解した上で)そういうことを頼める家族や友人がいれば自分がやっていたように運用してもらえるのではないか、と期待しての方法です。ただ厳密には開発ライセンスなどの権利問題を解決する必要もあり、よほど信頼できる相手と事前にそのような話し合いができていれば、という条件が付く方法です。

#そういえばソースコードって相続の対象になるのかな??


別の方法として「オープンソース化」も考えました。現在マンホールマップのソースコードは公開していません。が、Github 上には存在しているので「多少の準備期間があれば公開」できないことはないです。ソースコードを公開することで、誰かが運用を引き継げる形だけはとっておく、というものです。 ただこの方法にも「既存データをどう扱うか」という問題点があります。ソースコードがあれば新たにマンホールマップサービスを立ち上げることはできるようになりますが、それまでに利用者から投稿いただいたデータが溜まっているデータベースにアクセスできるかどうかは別問題です。特に現行のマンホールマップのように僕の自宅内にデータベース・サーバーやバックアップ・サーバーがあるケースは厄介ですよね。。

手続き的に一番ハードルが低いと想像しているのが「法人化」です(実は大変なのかも・・)。要は個人のサービスではなく、法人のサービスという形にして、法人として(担当者が変わる形で)サービスの開発や運用を引き継ぐというものです。「法人化」が簡単だと思っているわけではありませんが、法人化されていれば別の人が引き継いでサービスを継続する上での権利的な問題は解決できると考えています。本格的に事業者する場合ははじめからこの形態なんでしょうけど、個人開発サービスでも法人化する意味はなくはないのでは、、と思うようになりました。

そして最後が「自分がなんとかできるうちにサービスを終了する」方法です。別にお金に困っているわけでもなく、ユーザーの過疎化に悩まされているわけでもなく、でもどこかで見切りをつけてサービス終了を宣言して、各ユーザーに自分のデータだけでもバックアップしてもらう、という方法です。ただ現実問題として、この場合の判断のタイミングは本当に難しく、いつどこで判断するかというのは正解のない決断に迫られることになると思っています。


まだ切羽詰まってサービス終了を考えているわけではないので急を要する事態ではないのですが、ただ最後のサービス終了決断以外はいずれの場合でも「ある程度のスキルを持った誰かに作業を引き継いでもらう(予めある程度の作業を教えて引き継げるようにしておく)必要がある」ことには変わりはありません。個人で興味本位から作り始めたサービスって、どういう形で終了を迎えるのが理想なんでしょうね。。

同じように個人開発サービスを運用されている方のご意見など、伺って参考にさせていただきたいです。なんかうまい方法ないんですかね。。

ふとしたきっかけがあって PWA(Progressive Web Application) を作る機会がありました。今回は PWA の中でも特にキャッシュ機能を試すことが主目的だったのですが、最低限の機能を実装して公開してみたので、興味ある方は以下を参照して使ってみてください。


【PWA とは】
主に Google 主導で進められているモバイル端末向けウェブアプリケーションの技術で、「モバイル端末でウェブアプリケーションを利用する際に、ネイティブアプリであるかのような UI や動作を可能にする」ことを主目的としています。

なお PWA 自体の説明をすることが本ブログエントリの目的ではないし、そこまで含めてしまうと長くなりそうなので、興味ある方は適当にググってください。


【アプリの内容とシステム構成】
PWA には特徴的な機能がいくつかありますが、今回は特に「キャッシュ機能」を確認するためのアプリケーション実装を行いました。実際のアプリ(パズルゲーム)を使うにはスマートフォンのブラウザで以下の URL にアクセスしてください:
https://pwa-slidegame.mybluemix.net/


正しくアクセスすると以下のような画面が表示されます:
2020020301


このままアプリの実行を続けてもいいのですが、せっかくの PWA なので「インストール」してみます。メニューから「ホーム画面に追加」を選びます(下図は iPhone Safari での場合):
2020020302


正しく追加できるとホーム画面に以下のようなアイコンが追加され、次回からはここをタップすることで同じアプリを起動できるようになります。というわけでこちらをタップします:
2020020303


先程に似た画面が表示されます。ただアドレスバーなどは表示されておらず、PWA として起動してネイティブアプリっぽく起動しています:
2020020304


「(カテゴリーを選択)」と書かれた部分をタップすると、この後選択する画像のカテゴリー一覧が表示されます。実際には色々なカテゴリーがあっていくつか試すこともできるのですが、キャッシュの説明のため以下は「こども」カテゴリーを選択したものとして(それ以外のカテゴリーは選択していないものとして)説明を続けます:
2020020305


カテゴリーを選択すると、「いらすとや」の選択したカテゴリーの画像がカルーセル表示されます。画像を右や左にフリックすることで次の画像や前の画像を表示できます:
2020020306


カルーセルから「パズルにしたい画像」を選んでタップします。では↓の画像を選んでタップしたものとして以下を続けます:
2020020307


すると選択した画像が 4x4 のスライドパズル化され、実際に遊ぶことができるようになります:
2020020308


(簡単なパズルではないのですが・・)パズルを進めていって、完成すると、・・・
2020020309


パズル完成までに要した時間と動かした回数が表示されます:
2020020310


続いて過去の(回数の少ない)トップ10が表示されます。直近の記録がこの中に含まれていれば強調表示される、というゲームです:
2020020311


ここまではネットワークに接続された状態で行いました。PWA ではデータをキャッシュすることで、オフラインでもある程度の利用が可能になります。その部分を試してみましょう。この時点で起動していた PWA アプリをいったん終了(タスク一覧から終了)しておいてください。

まずはスマホをオフライン状態にします。WiFi もモバイルデータも接続されていない OFF の状態に設定します:
2020020312


あらためてホーム画面のアイコンからアプリを起動します。先程起動した時に表示されたトップページは自動的にキャッシュされていて、ネットワーク接続がなくても表示され、カテゴリー選択状態になります:
2020020313


カテゴリーとして「こども」を選択した場合も先程の実行時にキャッシュできているので、ネットワーク接続がないにも関わらず画像含めて表示することができています。なお、ここで表示されているのはあくまでキャッシュに含まれている画面であって、ネットワーク接続時の最新状態が表示されているわけではありません:
2020020314


しかし先程選択しなかったカテゴリー(例えば「職業」)を選択すると、こちらの内容は接続状態でのキャッシュが存在していないため、カルーセルの中身を取得することができず、何も表示されない、という状況になります。ある意味でキャッシュが有効に動いていることが確認できます:
2020020315


またオフラインでもパズルクリアまではできますが、その後のデータ記録目的での HTTP POST には失敗します:
2020020401



といったオフライン時にキャッシュ済みのデータを使って一部動かすことができるようになるのが PWA のキャッシュ機能です。


この PWA アプリケーションは以下のようなシステム構成となっています。構成そのものはウェブアプリケーションのシステム構成そのもので、この中で「いらすとや」のページをリアルタイムにスクレイピングしてカテゴリー一覧やカテゴリーの画像一覧を取り出して、アプリケーションの中で利用しています。アプリケーションそのものにキャッシュの機能は含まれていません:
2020013101



【PWA でのキャッシュの実現方法】
アプリケーションを PWA 化する際にいくつか設定のためのファイルを追加するのですが、それらの準備が完了していると、以下のような仕組みでキャッシュが(自動的に)実現されます。

まず、キャッシュデータが全くない状態でオフライン操作することはできません。画像とか API の実行結果とか以前に、トップページの HTML がないと何も表示できません。つまりオフラインでキャッシュ表示するためにはオンライン時にある程度の情報をキャッシュしておく必要があります。PWA では オンライン時に HTTP GET する際のパスとその結果を自動的にキャッシュします。上述の説明で行った流れをキャッシュの中身を意識した上で再度理解しておきましょう:
2020013102


そしてスマホがオフラインとなった後に PWA を起動すると、キャッシュの中身を使いながらオフラインで動作します:
2020013103


ただ(オフラインなので当然ですが)キャッシュにない HTTP GET は使えませんし、HTTP POST でデータをサーバーに送信することもできません:
2020013104


このアプリケーションのソースコードはこちらで公開しています。Node.js 上で動かすことができますが、PWA として稼働させるには HTTPS アクセスが必須となります。実際に自分のサーバーで PWA として動かす場合は HTTPS アクセスできるサーバーへデプロイしてお使いください:
https://github.com/dotnsf/pwa-slidegame


CSS で画面内の特定要素に回転をかけることができる、ということを知り、その様子を視覚的に確認できるサービスを作って公開してみました。

この技術(というほどのものではないけど)は CSS の tranform 属性を使って、回転軸と回転角度を指定することで実現できます。ともあれまずは実際に挙動を確認してみましょう:
https://dotnsf.github.io/transform-rotate/


上記 URL にアクセスすると以下のような画面が表示されます。画像(デフォルトではいらすとやさんの「コンピュータを使うペンギン」のイラスト)と、その下に3つのスライダーバーが表示されているはずです:
2020020101


x のスライダーバーを動かすと、動かした角度だけ画面が縦方向に回転します:
2020020102


y のスライダーバーを動かすと、同様に横方向に回転します:
2020020103


z のスライダーバーは画面と平行に回転します:
2020020104


x, y, z のスライダーバーを同時に動かすこともできます。縦、横、平行すべて指定したぶんだけ回転して画像が表示される、ということがわかると思います:
2020020105


これらはすべて CSS の transform 属性だけで実現しています。以下、その技術的な内容を紹介しますが、前提として x, y, z という軸の名前とその意味、そして立体的な位置関係を理解しておく必要があるので、そちらから説明します。以下の図のような関係になっていると理解してください:
20200201


図のような x 軸、y 軸、z 軸があって、自分は z 軸正方面から画面を見ています。実際に見えている画面は x 軸と y 軸からなる平面上に描かれています。

そしてスライダーバーはそれぞれ x 軸を中心に回転、y 軸を中心に回転、z 軸を中心に回転するスライダーバーになっていて、それぞれ指定した角度だけ回転した結果が画面に表示されます。そのため(結果的には)x で回転すると縦方向、y で回転すると横方向に回転したように見えます。また z で回転すると見えている平面上で時計回り/反時計回りに回転しているように見せることができています。

で、これをどう実現しているかという部分が CSS の transform 属性です。例えば id="a" の要素を x 軸中心に 30 度回転させて表示したい場合は、以下のようなスタイルを適用します:
#a{
  transform: rotateX( 30deg );
}

同様にして y 軸中心に 30 度回転させて表示したい場合は以下です:
#a{
  transform: rotateY( 30deg );
}

z 軸中心に 30 度回転させる場合は以下です:
#a{
  transform: rotateZ( 30deg );
}

「30 度」という部分は -180 から 180 まで変えることができます。それぞれの結果は上述のようになります。上述のサービスではこれらのスタイルを各スライダーバーの値を参照しながら動的に変更して表示しており、その結果がこのようになっています。


これはオマケの情報ですが、このサービスではデフォルトでペンギンの画像を表示して回転させていますが、imgurl という画像の URL をパラメータ指定することで表示画像を変更することができます。例えばこの画像を使いたい場合は、画像の URL(https://1.bp.blogspot.com/-A-AUINeSdLY/XexqsbbnoVI/AAAAAAABWiA/1zwr87fQJbsntV_Ez_ky6-RJPRfgeCZ5ACNcBGAsYHQ/s400/kinshi_mark_computer.png)をパラメータとして以下のように指定します:

https://dotnsf.github.io/transform-rotate/?imgurl=https://1.bp.blogspot.com/-A-AUINeSdLY/XexqsbbnoVI/AAAAAAABWiA/1zwr87fQJbsntV_Ez_ky6-RJPRfgeCZ5ACNcBGAsYHQ/s400/kinshi_mark_computer.png


こんな感じで表示できるはずです:
2020020106




このページのトップヘ