astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

今どきのシャッフルランチを支える技術

こんにちは、すしざんまいが恋しいfdkです。

今回は、件の社内プロダクトaimeshiの舞台裏を現場レポートします。

lab.astamuse.co.jp

aimeshiとは

社内ワークショップから発足したプロジェクトで、 部門の垣根を超えたコミュニケーションを活性化するためのプロセスの全部または一部をシステムにより自動化するシャッフルランチの仕組みです。

f:id:astamuse:20190129195726p:plain

*1

aimeshiのコア・コンセプト

  • しがらみなし(恣意的な人選、店選びを排除)
  • 負担なし(スケジューリング、イベント進行を自動化。参加者のお財布にも優しい)
  • ハズレなし(メンバーは言わずもがな、美味しいお店を自動推薦)

小さくて大きな目標

それは、イベント実施率を100%にすることです。

限られた予算で一定期間の検証を行うのが最初で最後のミッションです。

aimeshiには強制力はありませんが、愛があります。各人の都合に合わせて、自然と参加したくなるような仕組みづくりを目指しました。

最初のオファーからコミュニケーションが始まるのです。

インシデント発生

順風満帆の滑り出しから数週間を経たある火曜日の夜、いつもと趣の異なるお知らせが届きました。

f:id:astamuse:20190129193021p:plain

イベントキャンセルとは、決められた時間内に一定の人数が集まらない場合に、当日のイベントの開催を取り止めることです。aimeshiにもプライドがあります。

しかし、これは100%のイベント実施率を目指すわたしたち運営チームにとっては寝耳に水。はじめて味わう敗北です。

状況と原因の分析

事象を分析した結果、定足数5人のうち3人まで確定している状態で、残りの2オファーの回答期限内にイベントの受付の締め切りを迎えてしまったということでした。 最小催行人数が4人というルールに従い、イベントは見送られました。

対応策

当初のフローは次のようなものでした。

最初にイベントの定足数分の回答期限付きオファーを出す。期限内に参加回答をしたメンバーを順次確定する。欠席回答もしく回答期限切れの場合は新たに別のメンバーにオファーを出す。

以下のような対策案が上がり、2と3をすぐさま対応しました。

  1. イベントの受付締め切りに向けて、オファーの回答期限を調整し、オファー切り替えを行う

  2. 初回のオファーを定足数よりも大きな数で出し、先着順でメンバー確定するようにルールを変更する

  3. システムだけに頼らず、オファーの回答を促すコンシェルジュサービスを導入する

振り返りと考察

これまでの1ヶ月半で、11戦10勝1敗の成績、全体の約6割のメンバーがaimeshiに参加しました。

f:id:astamuse:20190129193409p:plain

メンバー毎の参加回数を集計してみると、2回以上のリピーターが一定数いることがわかります。

また、1イベントあたりの確定までの平均オファー数は14.91回となっており、かなりのフラれ率であることがわかりました。これは、オファー時に個々のスケジュールを考慮しないこと、企画に対して一定の心理的障壁がありそうなこと、PR不足などが要因として考えられます。

一方で、オファー到達率は100%、1人あたりの平均受信オファー数は3.28回となっており、公平性という観点では妥当な線に達しているものと言えます。

では、参加したメンバーの反応はどうでしょうか。参考までに、社内コミュニケーションツールにおけるaimeshi関連の累積トピック数をグラフ化すると、順調な伸びを示しており、一定の反響があることが見て取れます。

f:id:astamuse:20190129193619p:plain
2018年12月12日が初回。β期間として週2回開催。

今後の課題

  • イベントあたりの平均オファー数を指標とし、これを下げること
  • より賢いお店の選定アルゴリズムの開発
  • AIによる、より魅力的なお誘いメッセージ
  • 会話に花が咲くトピック推薦エンジンの開発
  • おひとり様向けのsolomeshiの開発
  • キャッシュレス化

検証期間中に2回目、3回目の参加を果たしたメンバーは開拓者であり、リピーターであり、大のaimeshiファンと言えましょう。このようなファンは大切にしなくてはなりません。エバンジェリストとして彼・彼女らに次期のaimeshi運営をぜひともお願いしたい次第です。

まとめ

企画・開発段階もさることながら、いざ運用を開始してみると、色々な事象が起こります。チームaimeshiは、その度に喧々諤々と議論をし、それぞれが自ら進んで役割を果たす、いいチームです。そんなチームに支えられてaimeshiは歩みを進めています。

現場からは以上です。

*1:チャーリーさんの「ビジネスモデル図解ツールキット」を使わせていただきました。 note.mu

schemaspy+jenkinsで最新のER図をブラウザで閲覧できるようにする

こんにちは、アプリチームのchotaroです。

いくつかの新規サービスの開発を控え、アプリチームにも人の入れ替わりが発生してきています。

そんな中、新規参画者への情報共有が一つの課題になりました。

今回はそのためにやった一つのTIPSを紹介します。

解決する課題

  • 逐次DBの構造が変更されるのに対して、プロジェクト内のER図がメンテできておらず最新のER図が存在しないこと
  • 新規参入者が全体の構造を理解するのに、時間がかかってしまうこと

解決方法

schemaspyとjenkinsを組み合わせて、チームメンバーが最新のER図をWEBブラウザ上で確認できるようにする。

実践

schemaspy

schemaspyは、オープンソースで提供されている、ER図生成ツールです。(ライセンスはLGPLv3)

接続先やスキーマ名の情報を与えることで対象のDB・スキーマを読み取り静的なhtmlを一式生成します。

スキーマごとにディレクトリを生成し、スキーマ内でのカラムの検索やリレーションを表現した図を出力してくれます。

f:id:astamuse:20190123113640p:plain
スキーマごとの画面イメージ

html一式が出力されるため、例えばCIサーバ上に展開するようにすれば、チームメンバー全員が閲覧可能になります。

ER図の出力

基本的にはドキュメントに全部書いてあります。親切。

下準備

  • graphvizのインストール(ubuntuであれば apt-get install graphviz してあげてください。dot -V でinstallされているか確認できます。 )
  • java8のインストール

必要なライブラリの準備

  • ライブラリ本体SchemaSpy releases
  • 接続先に合わせたjdbc driver(postgresqlであれば、wget https://jdbc.postgresql.org/download/postgresql-42.2.5.jar )

上記を一つのdirectoryに入れ、同じ階層にschemaspy,propertiesを作成します。

# type of database. Run with -dbhelp for details
schemaspy.t={{接続先のDBの種類 postgresqlなら[pgsql] }}
# optional path to alternative jdbc drivers.
schemaspy.dp={{ 接続用のJDBC driver [/path/to/driver/postgresql-42.2.5.jar]}}
# database properties: host, port number, name user, password
schemaspy.host={{ 接続先host }}
schemaspy.port={{ 接続先port番号 }}
schemaspy.db={{ 接続先DB名 }}
schemaspy.u={{ user name }}
schemaspy.p={{ password }}
# output dir to save generated files
schemaspy.o={{ output先のdirectory }}
# db scheme for which generate diagrams
# schemaspy.s= {{ 対象のスキーマ名 (今回は全スキーマを対象としたいのでコメントアウトしています。) }}

実行

ここまでで準備してきたdirectoryで

java -jar [jar名] -all

とコマンドを実行します。(-allは全スキーマを分析対象とするためのオプションです。)

ciサーバ上で出力が確認できたらnginxなどWEBサーバーの設定を仕込み、まずページを見られるか確認します。

こんなページが閲覧可能になればOKです

f:id:astamuse:20190123103929p:plain
schemaspy index.html

okならばjenkinsにjobを作っていきます。

Jenkins jobでER図の最新化を自動化する

シェル実行で以下のようなスクリプトを設定。

rm -rf /path/to/output/*
cd ~/schemaspy
# jarの実行。
java -jar schemaspy-6.0.0.jar -all
ls -al /path/to/output/

このとき、書き込み先のディレクトリのpermissionには注意してください。jenkinsユーザーの書き込み権限が出力先のdirectoryにあることが前提になります。権限がない場合、処理がfailしてしまいます。

また一部のみ出力ではなく全量を出力するため、ゴミが残らないよう毎回クリーンアップするようにしています。

このjob自体をwebアプリのデプロイjobのあとに起動するように設定して完了です。

こうすることで実際に動いているwebアプリが、どんな構造のDBを元にしているのかがすぐ確認できるようになりました。

効果

これから先に備えた変更なので、この先どうなるか、というとこです。今後に期待。

ただし、ローカルで利用してみた所感では、やはり無いのと有るのでは自分自身の実装に対する確信が違います。

ひと目でわかること、は大事。

課題

リレーションを明示的に貼っていないとリレーション図を閲覧できません。(当たり前ですが……

なのでリレーションを明示的に貼ってはいないものの同一なもの(USER.user_idとACCESS_LOG.user_idのような関係)を読み取ることが難しいです。

docker上にDBを立ててそこにddlを流し、別途alter文を流して擬似的に見せることはできますが……そうするとそのalter文までメンテナンスしなくてはならないため諸々コストがかかります。

悩ましいところですが、新規チームメンバーの参画のタイミングで継続的に改善していくしかないかなというところです。

以上、主にschemaspyの紹介でした。

アプリチームでは今後もこのような改善活動を継続しつつ、サービス開発に勤しんでいきます。

もし興味持たれた方おられましたら、下記バナーから開発環境や採用情報など見れますので、是非見てみてください。

それでは失礼します。

strapi+nuxt.jsでHeadless CMSの夜明けを感じる

デザイン部でフロントエンドエンジニアをしているkitoです。
近年のCMS界隈には、いわゆるHeadless化の波がきています。CMSのHeadless化とは、CMS(Content Management System)からクライアントサイドを切り離し、クライアントサイドのフレームワークでUIを構築するアーキテクチャです。

デカップルド・アーキテクチャと呼ばれることがあるようですが、サーバサイドとクライアントサイドがAPIを通じてコンテンツをやりとりすることで、Webアプリだけではなくてネイティブアプリからの要求にも適切に答えられるようになります。スケーリングに関しても、Headless CMSの方が取り回しが良いのではないでしょうか。

WordpressがWP REST APIを開発し、Headless化へと道筋をつけたことは特筆すべきです。WP REST APIの公式サイトでは、冒頭に「WordPress はアプリケーションフレームワークへと生まれ変わろうとしています。」と気負って書かれています。
とはいえ、Headless CMSの主役が、WordPressになるかどうかは未知数です。WordPressのエコシステムは巨大で、蓄積されたリソースは非常に豊かであると思いますが、クライアントサイドのフレームワークとの相性は、おそらく現時点ではReact.jsやVue.jsの周辺から派生してきたHeadless CMSの方が優っているのではないかと思います。

今回は、まだalpha版ですが使ってみて好感触だったstrapiというHeadless CMSと、クライアントサイドはVue.jsのフレームワークでSSR(サーバーサイドレンダリング)に対応しているNuxt.jsをあわせて使ってみたいと思います。

strapi

まずHeadless CMSのstrapiをインストールします。(Node.js 10.x以上、NPM 6.x以上が必要になります)

npm install strapi@alpha -g

インストールできているか確認します。

strapi -v

インストールされていれば、「3.0.0-alpha.x」のようなバージョン番号が表示されます。

データベースを任意に作成してください。MongoDBやPostgreSQLも使えますが、今回はMySQLを使います。

mysql -u root -p
mysql> create database strapi_db;

下記コマンドでアプリを作成していきます。 my-project以下にstrapiとnuxt.jsをそれぞれインストールします。

mkdir my-project
cd my-project
strapi new strapi

使用するデータベースを聞かれるので、MySQLを選択します。

Lets configurate the connection to your database:
  Choose your main database:
  MongoDB
  Postgres
❯ MySQL

データベースの名前を入力してください。

Database name: strapi_db 

続いて、Hostやport、UsernameとPasswordを聞かれるのでそれぞれ入力してください。
これでmy-project以下にstrapiがインストールされました。下記でstrapiを起動してみましょう。

cd strapi
strapi start

ブラウザが自動で起動するのでユーザ名、パスワード、メールアドレスを入力し「Ready to start」をクリックしてください。
f:id:astamuse:20190116110820p:plain 下記のようなadmin画面にログインできます。これでstrapiのインストールは完了です。続いてデータを入力します。 f:id:astamuse:20190116110812p:plain

コンテンツタイプ作成から「コンテンツタイプを追加」をクリックします。今回は名前をpostにします。次に「新しいフィールドを追加」を選択し、Stringをクリックして名前を「title」と入力します。続いて、textを選択して「content」、メディアを選択して「cover」と入力します。(「保存」のクリックを忘れないようにしましょう) f:id:astamuse:20190116110800p:plain f:id:astamuse:20190116110753p:plain

次にロールと権限から、Public > Postの権限を変更します。今回はスクリーンショットのようにすべてOKにしてあります。 f:id:astamuse:20190116110750p:plain サイドカラムにあるCONTENT TYPESの箇所にPostがあると思います。そこから、実際のデータを適当に入力してください。
http://localhost:1337/postsにアクセスすると、下記のような先ほど入力したデータがjson形式で表示されるでしょう。これでstrapiの設定は完了です。次にNuxt.jsのインストールです。

[
  {
  "id": 1,
  "title": "タイトル1",
  "content": "テキスト1テキスト1",
  "created_at": "2019-01-15T05:14:40.000Z",
  "updated_at": "2019-01-15T05:14:40.000Z",
  "cover": {
  "id": 1,
  "name": "150x150.png",
  "hash": "cffd30f367994f9788ca31c4ab0018af",
  "sha256": "Lny0dZHead23KZOVGlnYl7e0FTu-FQqzmeokqqZJZxE",
  "ext": ".png",
  "mime": "image/png",
  "size": "0.86",
  "url": "/uploads/cffd30f367994f9788ca31c4ab0018af.png",
  "provider": "local",
  "public_id": null,
  "created_at": "2019-01-15T05:14:40.000Z",
  "updated_at": "2019-01-15T05:14:40.000Z"
  }
  },
  {
  "id": 2,
  "title": "タイトル2",
  "content": "テキスト2テキスト2",
  "created_at": "2019-01-15T05:15:14.000Z",
  "updated_at": "2019-01-15T05:15:14.000Z",
  "cover": {
  "id": 2,
  "name": "150x150.png",
  "hash": "4646c71aaeee40f687cd263cf6afd9f2",
  "sha256": "Lny0dZHead23KZOVGlnYl7e0FTu-FQqzmeokqqZJZxE",
  "ext": ".png",
  "mime": "image/png",
  "size": "0.86",
  "url": "/uploads/4646c71aaeee40f687cd263cf6afd9f2.png",
  "provider": "local",
  "public_id": null,
  "created_at": "2019-01-15T05:15:14.000Z",
  "updated_at": "2019-01-15T05:15:14.000Z"
  }
  },
  {
  "id": 3,
  "title": "タイトル3",
  "content": "テキスト3テキスト3",
  "created_at": "2019-01-15T05:15:31.000Z",
  "updated_at": "2019-01-15T05:15:31.000Z",
  "cover": {
  "id": 3,
  "name": "150x150.png",
  "hash": "436a2f656ea843f8bb1eda6f2b404794",
  "sha256": "Lny0dZHead23KZOVGlnYl7e0FTu-FQqzmeokqqZJZxE",
  "ext": ".png",
  "mime": "image/png",
  "size": "0.86",
  "url": "/uploads/436a2f656ea843f8bb1eda6f2b404794.png",
  "provider": "local",
  "public_id": null,
  "created_at": "2019-01-15T05:15:31.000Z",
  "updated_at": "2019-01-15T05:15:31.000Z"
  }
  }
]

Nuxt.js

先ほど設定したstrapiのjsonデータを、Nuxt.jsで取得して表示させます。 まずmy-projectにNuxt.jsをインストールします。yarnもしくはnpxが必要です。 my-project直下で下記コマンドを実行してください。Use axios moduleはyesにして、それ以外はdefaultでOKです。

yarn create nuxt-app nuxtjs
cd nuxtjs
npm run dev

http://localhost:3000/ にブラウザでアクセスすると下記のように表示されると思います。これでNuxt.jsのインストールは完了です。 f:id:astamuse:20190116110746p:plain

次に、/my-project/nuxtjs/pages にposts.vueファイルを作成して、下記コードを記述してください。 Nuxt.jsは、pages以下のvueファイルとurlが連動するようにルーティングされる規約なので、http://localhost:3000/posts のファイルを作成していることになります。

<template>
  <section class="container">
     <ul>
       <li v-for="post in posts" :key="post.id">{{post.id}}:{{post.title}}{{post.content}}、<img :src="`http://localhost:1337/${post.cover.url}`"></li>
     </ul>
  </section>
</template>

<script>
import axios from 'axios'

export default{

  async asyncData({app}){
    let res = await axios.get('http://localhost:1337/posts')
    return {posts : res.data}
  }

}
</script>

asyncDataは、Nuxt.jsのメソッドでページコンポーネントがロードされる前に呼び出され、ここでgetしたpostsデータはSSR(サーバサイドレンダリング)されます。strapiとnuxt.jsを起動させた状態で、 http://localhost:3000/postsにアクセスすると、先ほど入力したデータが表示されていると思います。(ブラウザでソースを見て頂ければわかりますが、SSRされています)
これだと流石に見た目がアレなので、Bootstrapで少しスタイルを修正します。
/my-project/nuxtjs/nuxt.config.js のlinkにbootstrapのcssを追加します。

  head: {
    title: pkg.name,
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: pkg.description }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
      { rel: 'stylesheet', href: 'https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css' }
    ]
  },

/my-project/nuxtjs/layouts/default.vueのtemplateにnavを追加

<template>
    <div>
        <nav class="navbar navbar-light bg-dark">
            <a class="navbar-brand text-light" href="#">
                strapi + Nuxt.js
            </a>
        </nav>
        <nuxt/>
    </div>
</template>

/my-project/nuxtjs/pages/posts.vueを下記のように変更します。

<template>
    <div class="container">
        <div class="starter-template">
            <div class="card-deck">
                <div class="card" v-for="post in posts" :key="post.id">
                    <img class="card-img-top" :src="`http://localhost:1337/${post.cover.url}`">
                    <div class="card-body">
                        <h5 class="card-title">{{post.title}}</h5>
                        <p class="card-text">{{post.content}}</p>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    import axios from 'axios'

    export default {
        async asyncData({app}) {
            let res = await axios.get('http://localhost:1337/posts')
            return {posts: res.data}
        }
    }
</script>

<style>
    .starter-template {
        padding: 3rem 1.5rem;
        text-align: center;
    }
</style>

strapiはcmsなので、もちろん画像やテキストを先ほどのadmin画面から変更できます。

f:id:astamuse:20190116110739p:plain 手順としては多少長いですが、思いのほか簡単にインストールできたのではないでしょうか。
個人的には、strapiとNuxt.jsで旧来のモノリシックなCMSでは得られない風通しの良さを感じました。CMSとクライアントサイドが疎結合になることで、必要であればCMS側だけを変更したり、クライアントサイドのフレームワークを変更できるようになります。また、Nuxt.jsのようなクライアントサイドのフレームワークを導入することでGoogle的に質の高いサイトを低コストで構築可能で、メリットは大きいでしょう。

アスタミューゼでは、エンジニア・デザイナーを募集中です。ご興味のある方は遠慮なく採用サイトからご応募ください。お待ちしています。

Copyright © astamuse company, ltd. all rights reserved.