下町柚子黄昏記 by @yuzutas0

したまち発・ゆずたそ作・試行錯誤の瓦礫の記録

WEBサービスを作りました(人生でやりたい100のリスト)

概要

My100Tales(マイ・ハンドレッド・テイルズ)というサービスをリリースしました。

f:id:yuzutas0:20170301194905p:plain

もくじ

  1. どんなサービスか
  2. どうやって使うのか
  3. なぜ作ったのか
  4. どうやって作ったのか
  5. 思ったこと

1. どんなサービスか

  • 人生でやりたい100のリストを書き込み、育て、実現するためのサービスです。
  • 機能・UIの実態としてはお一人様向けのQiita:Teamクローンとなります。
  • 名前の由来・コンセプトは以下の通りです。和風シリーズです。

サービス名は「私だけの百物語」という意味です。

百物語では、百の怪談を語り終えて、最後の蝋燭の火を消したとき、本当に怪奇現象が起きると言われています。 また、"tale"は「怪談」だけでなく「夢物語」「おとぎ話」の意味を掛けています。

このサービスは利用者が自分だけの「夢物語」を書き出す場所です。 やりたいことを百個書き出す頃には、きっと何か1つは実現しているのではないでしょうか。

2. どうやって使うのか

マークダウンエディタで投稿したり

f:id:yuzutas0:20170301195228j:plain

投稿内容を読み返したり

f:id:yuzutas0:20170301195248j:plain

一覧から投稿を探したり

f:id:yuzutas0:20170301195307j:plain

キーワードやタグで絞り込んだり、スコアで優先順位付けたり、検索条件を保存したり

f:id:yuzutas0:20170301195323j:plain

追記コメントを添えたり

f:id:yuzutas0:20170301195336j:plain

バックアップファイルを出力したり

f:id:yuzutas0:20170301195349j:plain

そんな感じです。

3. なぜ作ったのか

  • プロトタイプとして
    • グループウェアの企画・開発に関心があったので、そのプロトタイプがてら実際に使えるものを作ってみました。
  • 管理ツールとして
    • 自分自身がアイデアの管理ツールを必要としたからです。
    • 1日1つ新規事業のアイデアをメモに書き続けたところ、アイデアが数百を超えてしまい、ツールが必要になりました。
    • 某開発チームにて、案件管理にQiita:TeamとGoogleSpreadSheetを併用しているのを見て、両方の特性を兼ね備えたツールが欲しいと考えました。
      • そこでは、SpreadSheetは案件リストを管理するために使っていました。列の追加・絞り込み・ソート機能を活用し「工数は小さく、効果見立ては大きい」といった条件で優先度を判断していました。
      • 一方、Qiita:Teamは個々の案件の要件を管理するために使っていました。気軽に更新できるのが魅力です。案件リストのURLリンクから各案件詳細に遷移していました。
      • また、別のチームでは、JIRAとConfluenceの組み合わせで同様の使い方を試みています。

4. どうやって作ったのか

基本的には過去のサービス開発と同じ流れです。

yuzutas0.hatenablog.com

yuzutas0.hatenablog.com

4-1. 概要

4-1-1. 以下の手順を踏みました(後述)

  • 企画:ペルソナ、課題、解決策
  • 開発:要件、設計、実装、テスト
  • リリース:環境構築・スクリプト整備、バグ修正

4-1-2. システム構成は以下の通りです

4-1-3. その他の開発ツールは以下の通りです

ちなみに、トップの背景写真は、夜灯(よとぼし)というお祭りにて、子供たちが手作りした和紙灯籠を撮影したものです。 サイトカラー・用途に合うように色味・明るさ・ぼかしといった加工をしています。

4-2. プロセス

4-2-1. 企画

どちらかというと自分向けの側面が強いので、軽く想定を置いただけです。

  • 分析を突き詰めるようなことはしていません。
  • 事前検証として、周囲にインタビューをしたり、ダミースクショで反応を見たりはしました。

4-2-1-1. ペルソナ

どんな利用者を想定しているか。

  • 属性としては広告・ソフトウェア系企業に勤務する20代半ば〜30代前半の若手会社員を想定。
  • プライベートではEvernoteなどのツールにアイデアを書き込んでいる。
  • 業務ではQiita:TeamやConfluenceといったグループウェアを活用している。
  • 仕事を終える時間が遅いので、プライベート向けツールよりも、業務ツールに接する時間のほうが長い。
  • 少しずつアイデアを育て、優先順位を付けることで、やりたいことを1つ1つ実行していきたいと考えている。

4-2-1-2. 課題

どんな痛みを抱えているのか。

  • グループウェアのUI・機能で情報を整理することに慣れてしまっている。
  • プライベートでのメモツールでの情報管理に違和感を覚えている。
  • 1つのツールで一覧管理・詳細メモの両方を満たしたいと考えている。

4-2-1-3. 解決策

なぜ既存の製品では駄目なのか。

  • グループウェアを1人で利用しているブログ記事を読むと魅力的に思える(ので既存の製品は駄目ではない)。
  • ただ、30日間無料のあとにお金を払って使い続ける自分を想像できずに二の足を踏んでいる。
    • 特にバックアップ機能が明記されていないときの尻込みたるや!
  • 各種グループウェアは検索→一覧→詳細の画面導線が十分に磨き込まれているとは言い難いのでツール併用が必要。
    • 案件管理ツールとして使うこと自体が間違い、という使い手側の問題もありますが。
    • とはいえ、検索機能の強化をPRする新興グループウェアがいくつかあるのも事実かなと。

※偉そうに書いていますが、自分でサービスを作った後に、改めて既存サービスを見返すと、いかに洗練されているかがよく分かります。

4-2-2. 開発

4-2-2-1. 要件定義

最初に、機能要件を洗い出し。

  • ユースケースをもとに「必須の要件」と「あると嬉しい要件」を整理しながら列挙しました。
  • 例えば、個人サービスは基本的に信用できないものです。少しでも安心できるよう、利用者がバックアップを保存できる機能は必須です。
  • また、外出中や就寝時にもアイデアは浮かびます。どこでも書き留められるように、スマートフォンから気軽に投稿・追記できるようにしました。

次に、画面要件を作成。

  • 検索→一覧→詳細の画面導線については、類似構成のサイトを参考にしました。
    • 特に参考にしたのがHOME'SとSUUMOです。たまたま利用していたこともあり。
    • 投稿の「探しやすさ」という観点では、ToB向けのグループウェアよりも、ToC向けのWEBサイトの方が、機能・UIともに磨き込まれているように思います。
  • カラースキーマについては、白をベースにして、一部アレンジカラーを加えることにしました。
    • 各種グループウェアが白ベースでデザインされているためです。
    • グループウェアに慣れている人が使うので、インターフェースは似通ったものが望ましいとの意図です。

そのあとは、データ要件。

  • 基本的にはRailsのシンプルな生成に任せる形としました。
  • 必要な項目を素直に正規化してER仕様を策定しました。

非機能要件は簡易なポリシー・ルールのみ策定。

  • セキュリティ:個人サービスとして無理のない範囲で対策する。
  • レスポンスタイム:開発時にパフォーマンスを測定してすぐ直せそうなら直す。
  • キャパシティ:各種リソース使用量は40%を許容上限とする。
  • インフラ:これまでの個人サービスと同様に最小構成のVPS1台とする。

詰め込みすぎると永遠に終わらないので、無理のない範囲でゆるく定義しました。

4-2-2-2. システム設計

モジュール構成は以下の通りです。

f:id:yuzutas0:20170301202844p:plain

作図にはPlantUMLを利用しています。テキストでバージョン管理できるのが嬉しい。

4-2-2-2-1. フロントエンド

技術要素

  • 再掲: Sass(libsass), CoffeeScript + jQuery + Vue.js
  • いくつか最近のフレームワークやライブラリを軽く触ってみました。
  • 趣味開発ならSassとjQuery(せいぜいVue.js)で十分だという結論に至りました。

構成

  • HTML(ERB)は、各DOMを一意にできるよう、BEMライクなclass/idを指定しています。
  • CSS/JSともに「DOMと一対一で対応する層」から「共通ロジックを扱う層」を呼び出す2層構造としています(下図参照)。
  • 最初から細かく分け過ぎるようなことはせず、必要に応じて後から分割を検討できる程度に留めています。

f:id:yuzutas0:20170301205858p:plain

こちらは某所のLT向けにyuml.meを使ったので資料を流用しています。

4-2-2-2-1-1. CSS

f:id:yuzutas0:20170301210200p:plain

SMACSSライクな構成としています。

  • Base:フォントサイズのように横断で利用する変数の設定など。
  • Module:共通部品(例:ボタン)を定義する。
  • Layout:
    • .erbファイルと対応する形で.css.sassファイルを作成する。
    • 一意となる.classに対応する形でスタイルを指定する。

なお、Bootstrap拡張ライブラリであるHonokaを利用していますが、HTMLから直接クラス名を指定するのではなく、上記ルールに寄せています。

こうではなく

<div class="btn btn-primary">ボタンだよ</div>

こうします。

<div class="module__button">ボタンだよ</div>
.module__button {
  @extend .btn;
  @extend .btn-primary;
}

ボタンのデザインを変更したいときはmoduleファイルを変更するだけで済みます。HTML上に散らばる.btn .btn-primaryを全て書き換える必要はありません。

また、「HTMLは文書構造」「CSSはスタイル」と責務を明確に分離することで、「色を変えたいときには、クラス名ではなくCSSをいじる」と保守判断できるようになります。

# 4-2-2-2-1-2. JS

f:id:yuzutas0:20170301210258p:plain

CSSと同様に二層構造(+α)としています。

  • Main:
    • .erbと同じ単位で.coffeeを作成する。
    • 一意となる#idに対応する形でスクリプトを実行する。
  • Util:
    • Mainから呼ばれるロジックを定義する(例:モーダルの生成やタブの切り替え)。
    • 任意のDOMに対して処理を実行する。
  • Template:動的なHTMLレンダリングで用いるテンプレートを切り出したもの。

なお、Vue.jsはデータバインディングにのみ利用しています。

@previewMarkdown = (editorDom, previewDom, vueDom) ->
  new Vue(
    el: vueDom
    data:
      content: $(editorDom).val()
    filters:
      preview: (content) ->
        markdownToHtml(content)
  )
4-2-2-2-2. バックエンド

趣味開発・スモールスタートのデファクトスタンダードという認識のもとでRailsを採択しました。 大体のことは無難に実現できるので十分かなと思っています。

最初はデフォルトのMVCで実装を開始しましたが、最終的には以下の形になりました。

f:id:yuzutas0:20170301210457p:plain

  • View:HTMLテンプレート。
  • Controller:リクエスト・レスポンスの窓口。
    • Decorator:画面出力のための整形処理。ControlleからViewへの受け渡しで利用。
    • Helper:画面出力のための整形処理。Viewから呼び出して利用。
    • Handler:例外処理を担うInterceptor。
  • Service:Controllerからコールされる業務ロジック。
    • Factory:レコードのCreate処理をModelに指示する。
    • Repository:レコードのRead,Update,Delete処理をModelに指示する。
  • Model:ER構成と密結合なEntity。永続化層とのやり取りを担うDAO。
    • Finder:検索処理。Elasticsearchとの繋ぎこみ。
    • Searchable:検索設定。Elasticsearchとの繋ぎこみ。
  • Form:検索や投稿のフォーム。ERと連動せず、横断して使うDTO

デグレが頻発したタイミングが2度あり、その度にクラスを分割する形で再設計しました。 なお、命名は便宜上のもので、正式な用語通りの意味・実装ではありません。

4-2-2-3. 実装

コツコツとコーディング。以下の点を意識しました。

  • なるべくライブラリを使って自前実装はシンプルに抑えました。そのためのRailsjQueryです。
  • 静的解析(Rubocop, Rails Best Practice)を用いて、なるべく綺麗にするように心掛けました。
    • 特に考えず初期設定で検査→可能な範囲で通せるようにリファクタリング
    • 守るのが難しい箇所はルール違反を許容。例えば、メソッドをXX行以内に抑えましょう!といったルールです。
  • 『パーフェクトRuby』を毎日コツコツと読み、「こう書けば良かったのか!」の気付きがあれば都度コードを修正しました。
  • ActiveRecordが向かない箇所は生SQLに寄せました。
    • 具体的には、無駄なクエリが発生する箇所、Arelを使わざるを得ない箇所です。
    • トリガーとしては、BulletでN+1が出たとき、動作確認で挙動が遅いと感じたとき、クエリの実行ログを見て違和感を覚えたときです。
# get hash about tag's name and how many tales the tag is attached to
# => { name: size, ... }
# => e.g. { 'testOne': 21, 'test2': 15, 'test_three': 23 }
def self.name_and_attached_count(user_id)
  # query
  query = <<-'SQL'.freeze
    SELECT
      T.name,
      COUNT(R.id)
    FROM
      tags T
    LEFT OUTER JOIN -- count for zero attached record
      tale_tag_relationships R
    ON
      T.id = R.tag_id
    WHERE
      T.user_id = ?
    GROUP BY
      T.id
  SQL

  # execute
  CommonRepository.select_hash_with_user_id(user_id, query)
end

4-2-2-4. テスト

最小工数で最低限の品質を担保するために、探索的テストのみ実施しました。

探索的テストとは

  • 最初に確認観点のみを洗い出して、手動でアプリケーションをいじります。
  • 途中で「あれ、この場合ってどうなるんだっけ」と疑問が出てきたら、その挙動を試します。
  • このときにバグが見つかることが最も多いという研究があるそうです。
  • その気付きを期待したテストだそうです。

※ただし実施者のスキル・経験に依存するとのこと。そりゃそうだ。

自動テストは書いていないです。以下、言い訳。

  • 仕様がコロコロ変わるので保守しきれる自身がなかったためです。
  • デグレが発生したら設計で担保する形にしました。「なぜデグレが起きるか=実装時に影響範囲がわかりにくいから」という考え方です。

うーん。苦しい。

4-2-3. リリース

4-2-3-1. 環境構築

いつも作業手順メモが読み返し辛いので、今回は .sh ファイルに書き込みながら実施しています。 ポート番号やパスワードは環境変数として隠蔽・別管理としました。

4-2-3-2. デプロイ

本番環境でのバグ検知→修正反映のリードタイムを短縮するため、コマンド1つで再リリースできるように準備しました。

  • Capistranoスクリプトを作成。
  • ビルドジョブをチューニング。
    • timeout対策:ローカルで assets precompile するように修正。
    • コンパイル時間短縮:Sassからlibsassに変更。

4-2-3-3. テスト利用

友人や同僚にお試し利用を依頼しました。事前に作成途中のものをPRしたり、デモ会を開いたりと涙ぐましい努力をしました。 その際に指摘いただいた不具合、誤表記、機能不備は修正しています。

引き続きご指摘は絶賛募集中なのでお気軽にお声がけください。

5. 思ったこと

5-1. Keep

良かったこと。

  • 人に使ってもらうための基準を決めたこと
    • 特にバックアップ機能。個人サービスなので、バックアップできないと胸を張れないと判断しました。
    • 他には利用規約・プライバシーポリシーの明記。ほとんどテンプレートの転用ですが、それでもサービスとして最低限必要だろうと。
  • 過去の反省点を踏まえて改善していること
    • クラス分割、静的解析、クエリ改善、デプロイ自動化など、個人開発でサボりがちな部分を実施したのは良かったです。
    • トップページにスクリーンショットを添えたり、安心してもらうための文言を添えたり。
    • 念願のモバイル対応です。過去に作った非対応サイトは直したいなぁ。
  • やり抜いたこと(1番大事)
    • やりたいことや直したい点は山ほどあるのですが、永遠に終わらないので泣く泣くスコープアウトしました。
    • 色々とアイデアはあるわけですが、何事も実現してこそですからね。

5-2. Problem

反省点は多々あります。

5-2-1. プロダクト

やりたいことだらけです。

  • 登録後の最初のアクションを促すガイダンスが欲しい。
  • 各種機能を解説するページが欲しい(特にスコア機能の分かりにくさ)。
  • 検索条件の履歴がクエリなので分かりにくい(自然言語に翻訳したい)。
  • 絵文字・テーブル補助・タスクリストなどマークダウンにあってほしい機能がない。
  • テンプレート機能が欲しい。
  • カラーテーマを入れ替えたい(黒ベースを選べるようにしたい)。
    • 目に優しいから。
    • プライバシーフィルター利用時に背景が黒だと他の人にいっそう見えにくくなるから。
    • エンジニアは黒い画面の方が慣れているであろうから。
    • 夏の夜が当初のコンセプトイメージだったから。

5-2-2. システム

直したい点だらけです。

  • deprecatedなツールを利用したり(例:bower)、最新バージョンに追従できていなかったり(例:Railsのバージョン)、個人開発とは思えない体たらく。
  • 投稿内容が増えれば増えるほど負荷が過度に上がる仕様になっているので直したいです。特にモーダルの表示ロジック。スケーラブルではありません。
  • ElasticsearchやRedisをもっと活用したいです。特に検索処理はフリーワード機能だけでなく全般的にElasticsearchに寄せたい。
  • Controller -> View にインスタンス変数を大量に受け渡しているので、Value Objectのような層を設けたいです。
  • 再現・計測できていないのですが、やたら処理が重いという声があるので、チューニングした方が良さそうです。
  • サーバセットアップの完全自動化。せめてローカルで使いたい人はコマンド1つで立てられるようにしたいです。
  • やはりテストコードは書きたい。色々と言い訳はしましたが。

5-2-3. プロセス

作りたいものの割に時間を掛けすぎました。

  • 実稼働は約1人月程度ですが、工期は1年弱です。さすがに趣味開発にしては長すぎたように思います。
  • いろんな工夫ができたはず。サーバセットアップをせずにPaaSを使えたのでは、KVSや検索エンジンを使わなくても良かったのでは。
  • 回り道を楽しむのも一興ではあるけど、最短ルートを走る努力も必要だと反省しています。
  • プロダクトオーナーの1番の仕事は「やらないこと」を決めることだと言いますし。

また、個人開発のブログではあるのですが、1人での活動に限界を覚え始めています。

  • 仕事が炎上してもプライベートプロジェクトを「常に誰かが進歩させる」状態になっていると心強いなぁと思いました。
  • 人に見せて「こうしたら面白そう」と盛り上がった場面があって、一緒に形にできたら楽しいだろうなぁと思いました。

チームや組織の重要性・可能性を強く感じています。

5-3. Try もとい 感想まとめ

  • 良い点も改善点も試したい点も山ほどありますが、何よりもやっぱり、自分のアイデアを形にするのは本当に楽しいです。
  • もっともっと楽しめるような、そして可能なら周りを楽しませられるような、そんな企画を100個でも1000個でも実現していきたいなぁと改めて思いました。

最後に

  • 長くなってしまいましたが最後まで読んでいただき本当にありがとうございます。
  • 改善点やご意見など、何かあればコメント欄やGithubTwitterでご指摘いただけると嬉しいです。
  • 指摘された点や自分で使ってみて気になった点はIsuueに挙げていこうと思います。