1. アプリケーション

1.1. 全般構成

アーキテクチャーの章でもふれている通り、主要な機能の開発に集中するために世の中に存在するよいものを積極的に採用し、疎結合かつスケーラブルな構成を実現しました。この章ではサービスを構成する重要な要素である Ruby on Railsをはじめ、クラウド電話API、クレジットカード決済サービスなど外部サービスを導入した背景や選定方法、活用方法について紹介します。

1.2. アプリケーションフレームワーク

サービス開発がしやすいと評判のRuby on Railsに注目し、体系的な学習や綿密な調査を重ねて採用にいたりました。エクメルンでのRailsの調査方法とサービス開発を得て積み重ねた知見を紹介します。

社内背景

当社のメインサービスを担当する開発部署は、Railsを使うことに対して当初難色気味だった。メインサービスが別のフレームワークで作られているため、知見が豊富なフレームワークを使ってリスクを回避したほうがよいという意見が多く出た。

→ 「流行っていて使ってみたいからRailsがいいです!」といった安易な考えでは採用は難しい状況だった。。

Rails選択への決意

最終的には以下の理由(想い)からRailsを使うことを決意。

  • 開発者として新しいフレームワークを習得したかった
  • 実際にさわってみて使いたいと感じた
    • 動的型付け言語特有のデバッグ、実装のしやすさ
    • フレームワークバージョンアップの頻度(進化の速さ)
  • 「Railsでのサービス開発」というナレッジを会社の新たな価値として生み出したかった

調査時にしたこと

社内の賛同を得るために以下を実施。

  • セミナー、書籍基礎知識を体系的に学ぶ。
  • 社内の知見収集社内の別プロジェクトや案件などでRailsに携わっている人からノウハウを吸収。
  • 事前検証 DB操作、エラーハンドリング、ログ出力、非同期処理などの主要処理を重点的に調査、検証。

社内の異論反論を乗り越えて採用に漕ぎ着けるためには相応の調査と覚悟が必要。エクメルンのサービス規模(大きすぎず、継続的に改善する)ではRuby on Railsがマッチしたが、大量データの取り扱いやリアルタイム性が求められるようなサービスでは別の選択肢もありえたと思います。

参考情報

1.3. Railsのプラクティス

"アイデアとは既存の要素の新しい組み合わせ以外の何ものでもない"
-- ジェームス・W・ヤング

アプリケーションを作る上で大切なのは、アプリケーションが提供する価値にフォーカスしてものづくりを行うことです。まず、これから作ろうとしているものがどのようなものなのかある程度道筋を立ててから手を動かし始めるのが良いでしょう。既存のものは作らず、世の中にあるものは活用していく。そのための設計のコツから、gemライブラリの選定方法、コーディングのコツ、気をつけたいセキュリティ、運用について記述しました。

  • 1.3.1. 設計

    • ER図を書いてアプリケーションのデータ構造をイメージする
      • DB設計に失敗すると全体的に歪んだアプリケーションになってしまう
      • モデルの名前には後々公開のない名詞を選ぶ
      • 関連モデルの結合も考慮しておくこと
      • ER図の時点で作ろうとしているサービスのデータに矛盾が発生しないことを確認しておく
      • おすすめツール
    • 実装しようとしていることが既存のライブラリや、外部サービスに置き換えられないか調べる自分たちが時間やコストをかけて作らなくても、既にいいものがある場合は(コスト的に見合うものならば)積極的に利用した方がよい。そして時間を作り出し自分たちが本当に作りたいものだけに注力し生産性を上げる
      • 車輪の再発明をしない
      • めんどくさいなと思ったらライブラリがないか調べる→だいたいある
      • gemの選び方
        • 探し方
          • 定石的なものはググればすぐ出てくる
          • イケてるプロダクトのGemfileを観察
            • 使い方も参考にするとよい
        • 知名度・利用状況の確認することで安定したライブラリを選定し、突然のサポート終了を未然に防ぐ
          • rubygems.orgruby-toolboxで把握
          • DL数、バージョンの更新頻度、ライセンス形態
          • 利用しているRailsのバージョンに一致しているか
          • ググって情報がある程度あるか
        • ドキュメントの質と量を見極める
          • ドキュメントが少ないこともあるが、利用方法が明快であればよい
          • ドキュメントまで気を配れているライブラリは良いライブラリの傾向
        • 次に、ザクッとコードを見る
          • コードを見るとライブラリの品質が何となくわかり、思いがけない便利な使い方を見つけることができる
        • 長年放置されているようなgemは、たとえ導入数が多くても見直したほうが良い
          • 代替手段が提供されている可能性がある
          • 枯れたgemはRailsのバージョンに依存せずに使えることもあるが要確認
        • 活発なプロジェクトは、頻繁にgemが更新されていたり、Issueが頻繁に投稿されているので、問題が発生した際も不具合の修正が早い傾向がある
      • そうこうしているうちに、よく使うgemセットが出来上がる→たまに知識のUpdate必要
    • 未来のことはわからない。あれこれ考えすぎない。 YAGNI
  • 1.3.2. 実装

    • generatorを活用
      • テスト系やView系のGemはgeneratorでテンプレート生成する前に入れておいたほうが、生成されるコードを変換する手間が省ける。→RSpec, FactoryGirl, Hamlなど
      • gemeratorで生成されたコードでも不要なものはコメントも含めバッサリ消す
    • DRYKISSなコードを保つ
    • レールに乗る→オレオレ実装を始めるととたんに面倒なことになる

    • コントローラを薄く、モデルを厚く、Viewを薄く、ヘルパーを厚く

    • コメントは無駄に書かない(見ればわかる処理には書かない)

    • ルーティング

      • RESTfulなURL設計を目指す
      • データ構造をRoutingに反映させる
        • has_many = resorces、has_one = resorce
        • has_manyの関係は入れ子構造を積極的に使う
        • rails routesコマンドで、ルーティングの定義を適宜
      • 極力個別のルーティングを定義しない
        • scaffoldで生成されたCRUDで乗り切る
        • 公開しないアクションは onlyなどを書いて、アクセス出来ないようにしておく
    • コントローラ

      • 記述を少なく、ほぼScaffoldで生成されたコードを保つ気持ちで実装
        • 無駄なコメント、respond_toの記述は削除
        • アクションは極力増やさない
          • 例:csvダウンロードなどは、ダウンロードアクションを新たに作らなくともrespond_toで拡張子を判断して返してやれば良い。 users_path(format: :csv)
      • パラメータやセッション状況によって、取得するモデルを切り替えるような処理はコントローラに書く
        • それ以外のゴニョゴニョしたコードは、モデルに移す
      • ネストしたルーティングは親レコードを基準としてモデルを取得する
        • データ取得のための無駄なコードの排除
        • データ取得時の想定外のパラメータの排除
      • インスタンス変数はむやみに使わない。 Viewに渡す変数はせいぜい2, 3個
    • モデル

      • 実装をモデルに寄せる
      • メソッド名は動詞 or 名詞 + 動詞に
      • 関連をうまく使う
        • 子レコードは親オブジェクトから取得
      • default_scopeは使わない
    • ビュー

      • 極力ViewからはFindメソッドを呼ばない
      • partialを利用する際は再利用を考えインスタンス変数は利用しない
        • インスタンス変数があると再利用が難しくなる
      • partialとcontent_forとHelperを活用して、Viewをシンプルに保つ
  • 1.3.3. セキュリティ

  • 1.3.4. 運用

    • コンテンツの配信状況を確認し、体感速度を向上させる
      • CDNを活用し、静的ファイルを素早く返す
      • 読み込み時間のチューニング
      • JSのminifyとCommonsChunkPluginでファイルサイズをかなり減らせた
      • gzip圧縮も忘れずに
    • アプリケーションエラー・アプリの売上速報をSlackで通知
    • マネージメントシステムを作り込んで、本番作業は画面上から完結するように(DBなるべくさわらない)

      • DBを直接触るとデータのリレーションを破壊するおそれがあるので、rails console経由でオブジェクトを操作
    • その他

      • たまにログを見ることで、ポトルネックを事前に潰す
        • 無駄なSQLが発行されていないか bullet
        • 遅いクエリがないか
          • indexの貼り忘れ
        • 有益な情報がログに出力されているか確認
          • 保守のときに助かる
      • メソッド名は意図が伝わる名前をつける
      • メソッドはシンプルに、複数の仕事をさせない
        • コードが読みやすくなり、テストが行いやすくなる
      • ライブラリはわけも分からず使わない。少しはコード見る。
        • RubyMineのCode Jump機能はおすすめ
      • コーディングルールの準拠チェックを自動化 rubocop
      • コードレビューではお互いに厳しいことを言い合う。なあなあにしない

      • テストはある程度網羅しておくと幸せになれる

        • Ver2で思い切ってリファクタリングが出来た
          • API部分全部作り直し
          • 冗長なロジックの排除など
        • Railsのバージョンアップ時に助かる
        • カバレッジ生成 rcov、CI実行時に生成しておくと良い

1.4. Railsのバージョンアップ

1.4.1. 大前提

Railsのバージョンアップは、なるべくこまめに行う方が絶対良いです。バージョンアップのためにもテストを書いておくこと!

1.4.2. 背景

エクメルンのv1では4系を使っていたのですが、マイナーバージョンの追従しかしておらずv5へのバージョンアップは見送っていました。機能開発優先で後手後手に回されていたバージョンアップですが、いざ実施してみるとv1の時にテストをしっかり書いていた(カバレッジ率100%近くだった)ので、安定してバージョンアップを行うことが出来ました。

バージョンアップを行ったことで、5系から採用された新機能を試すことが出来たことや、今後のバージョンアップへの心理的抵抗が下がりました。

  • Rails4.2.6 => 5.0.1にアップグレード
  • 今回テストコードが充実していたので、安心してアップグレードを行うことが出来た
    • DBのスキーマを切り替えるライブラリに問題があったが検知することが出来た
  • 5系になることでDEPRECATION WARNINGが大量に出たため修正が大変だった
    • 4系 get :show, { id: 1 }
    • 5系 get :show, params: { id: 1 }

1.4.3. コツ

  • バージョンが変わるときには、リリースノートに目を通すこと
    • 便利な機能や、非推奨・廃止になったメソッドなどあるので、自分の知識もUpdate
  • アップデートの心得、手順は以下が詳しい
  • バージョンアップをするには、バージョンアップ用のブランチを作成する
  • Gem のUpdateを行うと設定ファイルに差分が出るので、一旦上書きしてしまい慣れたツール(RubyMineの差分機能)などを使って設定を反映させるのがよい
  • 利用しているGemについてUpdateを行う
    • Gemが対応していない場合、Forkして自分のリポジトリで未対応部分の対応を行う
  • テストを実行し発生したErrorやWarningを1つずつ潰していく
  • 十分動作確認が取れた時点でマージを行う

1.5. ワーカー

非同期処理、バッチ処理、外部サービスとの通信処理にはSQSを積極利用します。 SQSの標準キューは以下の特徴を考慮する必要があります。

  • 順不同
  • 複数回エンキューする可能性

また、処理の待機やメンテナンスや障害による一時中断、リトライができる必要があり、すなわちデータに「次に何の処理を行えばよいのか」というステータスを持たせる必要があります。

エクメルンではSQS周りのアプリケーションアーキテクチャーを「アドベンチャーゲーム」に見たてて設計しました。(かまいたちの夜とか、シュタインズゲートとか、ゲームユーザーが選んだ選択肢によって話の展開が変わるジャンルのゲームのことです。)

データのステータスは以下のように置き換えました。

  • ストーリーは一本道なストーリーがあれば、途中の選択で分岐やループする
  • ストーリーは最終的にハッピーエンド1 ...n、バッドエンド1...nと様々な終わりを迎える
  • ストーリーは章節や途中の選択時にクイックセーブされて、再開するとそのセーブポイントから再開できる

これを設計に落とし込み、実装しました。 (具体的な内容は長くなるので割愛します)

1.6. マルチテナント

マルチテナントとは、1つのシステムの中に複数の企業(アカウント)を同居させ、リソースや運用コストを大幅に低減する方式のことです。マルチテナントを実現する方式は主に以下の3つの方式があります。

  • データベース分離方式
  • 単一データベース・マルチスキーマ方式
  • 単一データベース・共有スキーマ方式

エクメルンのv1.0では単一データベース・マルチスキーマ方式Apertment というgemを用いて実現していました。マルチスキーマ方式のメリットデメリットは以下のとおりです。

  • メリット
    • アカウントが退会した際にスキーマごと削除することで、データが残らない
  • デメリット
    • アカウント情報にアクセスする度にスキーマ切り替え処理が面倒
    • publicスキーマ、ローカルスキーマ間のリレーションを定義しづらい
    • スキーマ切り替えが正しく行えるか、アプリケーションサーバやコネクションプーリングの挙動をチェックがしんどかった
    • テーブル構造に変更があった際に全スキーマに変更を反映する必要がある
    • Usageデータを参照するため各スキーマにアクセスしてデータを取得する必要があった

v2.0では以下の理由から単一データベース・共有スキーマ方式に切り替えました。

  • マルチスキーマ方式を用いることでアカウントごとに異なるテーブル構造を持つことが可能になるが、エクメルンでは全アカウント同じテーブル構造であるためメリットにはならなかった
  • ベンチマークではスキーマ数が数千を超えた辺りから、スキーマ切り替えによるパフォーマンス低下が起きることが分かっていた
  • リリース時にテーブル構造に変更がある場合、全スキーマに変更を反映するのが時間がかかっていた
  • 万一反映に失敗した場合、リカバリーに時間がかかる恐れがあった

共有スキーマ方式に切り替えるにあたり、全ローカルスキーマのデータをデータ移行するのには細心の注意を払い、移行の検算を行なうために、ローカルスキーマで発行されたIDも保持するように移行しました。単一データベース・共有スキーマ方式に切り替えたことにより以下のメリットが生まれました。

  • リリース時のデーブル構造の変更が一瞬で終わるようになった
  • Railsのリレーションを持ちいたデータアクセスが行えるようになった
  • スキーマ切り替えによるパフォーマンス低下の恐怖から逃れられた

共通スキーマ方式では、テーブルのデータ量が増える傾向があるので、データを定期的に消す仕組みや、サマライズを行なうなどの対応が必要になってきます。あとシーケンスIDはBigIntにしておきましょう。マルチテナント方式の決定は、プロジェクトの開始段階から検討を行い、プロジェクトに合った方式を採用されることをオススメします。

1.7. メール配信

メイン機能となるメール配信は、もともとメール配信機能が搭載されているSynergy!の基盤を活用して実現しました。内輪事情ではありますが、これからサービスを開発していく環境によっては参考になるポイントもあると思います。

  • 会社の技術資産の有効活用外部サービスだけでなく社内の強みも活かすことで実装時間の短縮と車輪の再発明を防止。
  • よいサービスづくりのための協力体制フレームワーク選定でのいきさつに関係なく、Synergy!の開発チームに協力を仰ぐことでサービス完成にたどり着くことができた。必要な場面では素直に協力をお願いする姿勢も大切。 (会社の価値を高めることを念頭に)

1.8. 電話番号認証

電話番号認証機能にはクラウド電話APIサービスのTwilioを利用。有名なサービスのため選定ポイントなどは割愛し、構築、実装において工夫したポイントを紹介します。

1.8.1. APIコール制限

Twilioには1アカウントごとに 1APIコール/1秒 という制限があります。この制限をかからないようにするため連携処理は以下のように実装しました。

  • TwilioAPIをコールする処理は1プロセスのワーカー(非同期処理)のみに制限
  • ワーカーの実行間隔を1秒/1回に調整

DBで最終APIコール時間を管理 & ロジックでAPIコール間隔を調整、、といった実装方式ではなく、1秒1回間隔で実行される専用処理を設ける方式で実装。複雑なロジックや状態管理の手間を省略できた。
(この実装方法は決済サービスなど、他のAPIコールでも採用している)

1.8.2. 開発環境でのコールバックアクセス

Twilioの一部APIではコールバック受付処理をサービス側で実装する必要があります。(エクメルンでは電話番号認証の読み上げ文章を生成する処理がこれに該当)
開発環境上でこれらの動作確認をするためにBrowsersyncを活用しました。※ Browsersyncについてはフロントエンドの章にて詳しく紹介。
(フロントエンド開発にも重宝するツールです)

1.9. クレジットカード決済

クレジットカード決済にはStripeを導入。
決済というお金に関わるサービスとなるため慎重に選定しました。ここでは選定ポイントについて紹介します。

1.9.1. 選定候補

  • Stripe
  • PAY.JP
  • Omise
  • Yahoo!ウォレット FastPay

いずれもトークン決済機能があり、カード情報をサービス側で保持しなくてもよいというメリットがある。

1.9.2. 選定ポイント

料金

サービスによってはボリュームディスカウントもあるため、売上額やサービス規模に応じて判断するのがよいです。

決済サービスにかかる主な料金
  • 初期費用
  • 月額費用
  • 決済ごとの手数料(固定額)
  • 決済ごとの手数料(パーセンテージ)
  • 振込手数料
  • 返金手数料
  • チャージバック手数料

エクメルンは1配信500円という薄利多売な料金体系のため、決済毎の手数料(パーセンテージ)のみのサービスが最適と判断。

機能

機能だけでなく開発のしやすさ、使いやすさも確認するのがよいです。

決済サービスに備わっている主な機能
  • 決済機能(与信確保、決済確定)
  • 顧客管理機能
  • 継続課金機能
  • トークン決済
  • 対応しているカードブランド

エクメルンでは決済機能と顧客管理機能を使って決済周りを実装。カード情報非保持の方針としたためトークン決済機能は必須だった。売上情報確認などをバックオフィスにしてもらうことも想定し、管理画面の使いやすさについても気に留めておく。

信頼性

クレジットカード情報という超重要な情報を扱うため、サービス運営会社及びサービス全体のことについても確認したほうがよいです。エクメルンでは信頼性の証としてカード情報非保持の方針をサイトで公開しています。

主な判断要素
  • 会社規模
  • 導入事例
  • PCI-DSS準拠
  • 加盟店契約方式
  • メンテナンス頻度
  • 不正利用対策
  • サポート体制
  • 直接お話しをしてみての印象

results matching ""

    No results matching ""