技術的負債を徹底的に解消した話 - オミカレのシステムフル刷新のためにやったことを全部教える

技術的負債、デザイン面での課題など、サービスを構成するシステムを全面にわたってリニューアルしたプロセスを、オミカレの高橋一騎さんが克明に伝えます。

技術的負債を徹底的に解消した話 - オミカレのシステムフル刷新のためにやったことを全部教える

株式会社オミカレでテックリードをしております、高橋一騎(たかはし・いっき/ 1@ikkitangです。私たちが提供する婚活メディアサービス「オミカレ」は、2019年3月にシステムのフルリニューアルに踏み切りました。本稿では、このリニューアルのプロセスをできるだけ詳細にお伝えしたいと思います。

さて、「技術的負債」という言葉を耳にすることがあります。なぜ負債が生まれるのか。「品質を犠牲にしてでも早々にサービスをリリースし、短期的にビジネスの速度を上げる」という判断はその理由の一つに挙げられるでしょう。エンドユーザーへの価値提供スピードを得るための見返りとして、あえて技術的品質を犠牲にする、という判断です。オミカレもまさに同じ判断により多くの技術的負債が生まれてきました。その結果、想定外の開発工数の増加、思わぬバグの発生といったネガティブな現象が頻発してきたのです。

業績をもっと伸ばしたいが技術的負債により開発速度が出ない。こうしたジレンマにぶつかったオミカレは新しい価値をエンドユーザーに早く届ける事を目標に、システムフルリニューアルを決断しました。

我々がいかにフルリニューアルを決断し、そして大きなプロジェクトをチームで乗り越えリリースにこぎつけたのか、その経験を余すことなくお伝えします。

旧システムの課題

オミカレとは

まずは簡単にオミカレのサービスについて説明をします。オミカレは、婚活パーティーを主催するイベント事業者の方に開催情報を掲載していただき、会員の方が掲載情報を見て、好みのパーティに参加申し込みをする、というメディアサービスであり、そのポータルサイトがオミカレです。

2015年7月に会員機能のサービスをローンチの後、11ヶ月で会員数が3万人を突破し、2020年03月時点では45万人以上のユーザーの方にご利用頂いています。

2

サービス要件の進化

上記のリリースは、あくまで「会員サービスのローンチ」であり、オミカレ自体のサービスローンチはさらにさかのぼります。現在では婚活パーティーへの参加申し込みは会員登録が前提となっていますが、以前はゲスト会員でも参加申し込みが可能だったのです。

会員機能のリリース後も契約プランの増加などがあり、サービス要件は大きく膨れあがっていきます。また今でこそオミカレのエンジニアは6名体制ですが、当時は社内のエンジニアは1名で、足りないリソースは外注のエンジニアによって補われていました。

こうした経緯から、スピード優先のリリースが実施されることが多く、売上のアップと引き換えにコードのメンテナンス性は確実に犠牲になっていきました。技術的負債の発生要因としては「よくある」事例でしょう。

また、犠牲となった品質はプログラミングコードだけではありません。弊社では当時専属のデザイナーやマークアップエンジニアがおらず、その場その場でデザインとマークアップエンジニアを外注し開発が進められていました。そのため、プロダクトデザインとしても一貫性が保たれておらず、スマートフォン利用が大半であるにもかかわらず、スマートフォン向けのデザインになってなかったり、情報がどこにあるか分かりにくい設計になってしまっていました。

以下では弊社のサービスで見られた負債の実例を紹介します。

グロースを妨げる技術的負債の例

検索しにくいデザイン

3

旧デザインはこのような感じでした。オミカレのコーポレートカラーともいえるピンクを基調にしていますが、「公共の場でピンクを基調としたサイトを開くのはちょっと恥ずかしい」といった意見を男性ユーザーからいただいていました。

また、我々が大きな課題ととらえていたものに、検索性の悪さが挙げられます。旧デザインでは「週末開催のパーティ」や「人気のパーティから選ぶ」といったように、ユーザーからすると受動的にパーティーを選ぶようなつくりになっていました。オミカレは「日本最大級のパーティー掲載数」と謳っていながら、実際にはユーザーが本当に自分の行きたいパーティーを見つけにくい、という大きな課題があったのです。

また、会員専用ページでも、スマートフォンで見ると「おすすめパーティー」が縦に並び、会員が自分の予約履歴を見るのにスクロールを繰り返さないといけない、という問題もありました。その他にもスマートフォンユーザーの利用が想定れていないデザインのページも多くあったのです。

遅すぎるページ表示

もちろん、デザインだけではなく技術面でも複数の課題がありました。

例えば、ページ表示の遅さです。Dockerで動かしてるlocalhost上の各ページの描画には5~6秒もの時間が必要でした。本番環境では多少のスピードアップはありましたが、それでも表示までかなり時間がかかっていました。ユーザー体験の面でもSEOの面でも、表示速度の遅さは我々が抱えていた大きな課題でした。根本解決のためには、バックエンド、フロントエンド、DB設計など広範にわたる改修が求められる課題です。

なぜ、表示に多くの時間がかかるのか。バックエンドを例にすると、不必要なDBアクセスが非常に多い、といった要因が挙げられます。当時のシステムでは、例えば、メディアTopページに各都道府県のパーティー掲載件数を表示していますが、そのページでは常に47都道府県の各パーティー掲載件数のSUMクエリが流れていたのです。しかも、WHEREに「満席のパーティは除外」や「中止となったパーティーは除外」などの複数条件が付いたうえで、です。当然フルスキャンでクエリは実行され、これが遅くなる原因になっていたのです。

フロントエンドに目を向けると、圧縮されてないリソースが読み込まれていたり、jQueryやBootstrapなどのモジュールが使用されていて、軽量化にも限界がありました。また、PHPのView内にJavaScriptがそのまま書かれていたり、style.cssというグローバルのCSSが存在し、わずかなデザイン調整作業が全ページに影響する、という事態も頻発していたのです。

深すぎるifのネスト

継ぎ足しを繰り返して機能追加されたことで生まれた最も大きな課題は、if文の複雑化とそれに伴うControllerのメソッド肥大化です。

以前は会員登録しなくてもパーティーの参加予約ができましたが、この機能を司るコードは以下のようになっていました。これは一例なので、省略や改変などを含みます。

public function reserve_example()
{
    /* 補足コメント: sessionキーにuser_idがあれば取得 */
    if ($this->session->has_key('user_id')) {
        $user_id = $this->session->get('user_id');
    } else {
        $user_id = 0;
    }
    
    if ($this->db->get(['user.id' => $user_id])) {
        $user = $this->db->get(['id' => $user_id], 'user_table');
    } else {
        $user = null;
    }

    $company = $this->db->get(['id' => $company_id], 'company_table');

    $this->validation->set_rules(~~);
    /* 契約区分に応じてバリデーションを変更する */
    if ($company['keiyaku_type'] == 1) {
        $this->validation->set_rules(~~);
    } else if ($company['keiyaku_type'] == 2) {
        $this->validation->set_rules(~~);
    }

    if ($this->validation->run()) {
        /* Validation成功時のコードを書く */

        /* 契約区分に応じて登録する情報を変更する */
        if ($company['keiyaku_type'] == 1) {
            $this->db->insert([~~~], 'reserve_table');
        } else if ($company['keiyaku_type'] == 2) {
            $this->db->insert([~~~], 'reserve_table');
        }

        /* その他、登録処理 */
    } else {
        /* 例外処理 */
    }
}

上記のコードはほんの一例ですが、多いものではコード量が700行を超えるメソッドも存在しました。それもControllerのメソッドだけで、です。Modelのメソッドとして外に書き出されているものもあり、正常な動作系を見るのが非常に難しく、改修によって大小さまざまなバグが発生した時期もありました。

また、複雑になってたのはControllerのコードだけではありません。以下の例のように、Viewも非常に複雑になっていました。

<?php

<div>
    <h1><?= $party['title'] ?></h1>

    <?php if($credit_party && $user['credit']): ?>
        <div>
            クレジットカードの登録は必須です
            <a href='credit/form'>登録フォーム</a>
        </div>
    <?php else: ?>
        <form>
            <div><input type="text" name="name"></div>
            <div><input type="text" name="tel"></div>
            <?php if($credit_party && $credit_type == 2): ?>
                <div><input type="radio" name="credit_type" value=1>クレカ決済</div>
                <div><input type="radio" name="credit_type" value=2>現金決済</div>
            <?php else if ($credit_party && $credit_type == 1): ?>
                <div><input type="radio" name="credit_type" value=1>クレカ決済</div>
            <?php endif;>
            ...
        </form>
    <?php endif;>
</div>

ユーザーの契約状況に応じて、適切なformや情報を出す必要があり、Viewにif文が入ることが多く、改修箇所の特定が難しい、という問題がありました。

レジェンドコードという考え方

このように、さまざまな面で旧システムは技術的負債を抱えていましたが、その背景にある、「スピード優先で事業を進める」という判断が間違っていたとは思いません。たとえ、よろしくないコードであったとしても、それがオミカレのサービスとビジネスの土台を作ってきたことは確かですし、当初1名しかいなかったエンジニアから6名のチームとなるまでグロースを導いてきたコードなのですから。私は、以下の資料に示された「レジェンドコード」という考え方が非常に好きです。

技術的負債の存在は事実ですが、負債を生み出したコードによってサービスが支えられてきたのもまた事実です。失敗から学び、次に生かせばいいのです。次章からはオミカレが過去からなにを学び、技術的負債を解消するべく新たなアーキテクチャを作り出したかを説明します。

なぜ、私たちはフルリニューアルを選んだのか

さて、リニューアル実施前、我々が抱えていたのは、デザイン、プロダクトコードに加えて、検索順位が落ちてきている、という課題でした。

変化の早い商環境を生き抜いていくためには、強い基盤を素早く作り、サービスの新たな価値を素早くユーザーに届ける必要があります。そのため、私たちが選んだのは、デザイン、フロントエンド、バックエンド、インフラの全てをいちから作り直す、フルリニューアルという手段でした。

もちろん、デザインリニューアルとリファクタリングを別々に実施する、という選択肢もありました。ですが、デザイン変更のみを行おうにも、複雑なコードに阻まれスムーズにタスクを進めるのは難しく、リファクタリングだけを行おうにも、結果的にフロントは作り直しになるので、再度フロントは書き直しが必要になってきます。であれば、すべてを一緒にリニューアルしてしまおう、と決断しました。

なお、私たちは「フルリニューアルによって、すぐに業績が上がる」と考えていたわけではありません。今後、業績を上げるための投資としてフルリニューアルという選択をとったのです。

課題を解決するための新アーキテクチャ

では、課題解決のために私たちが選んだアーキテクチャとは。まずは旧システムを図示すると以下のようになります。

新たなシステムは以下のようなアーキテクチャを採用しています。

リニューアル内容について、いくつかを具体的にご説明しましょう。 まずはサーバーサイドでは、DBへの読み書きをAPI経由で実施する、つまりCodeIgniter上からDBに直接アクセスできないように構成を変更したのです。{$annotation_1}

ただし、この構成を実現するためには、開発環境ではWeb(PHP)とAPI(Python)の2つの環境が必要になります。こうした開発環境の複雑化に手軽に対応するべく、インフラでは開発環境を完全Docker化し、本番環境ではAWS Fargateを使用したコンテナ化を行いました。

Fargateの採用によって、オートスケールなどの対応がTerraformで完全コード化され、コンソールを使用する必要がなくなります。このことから、属人化の解消とサイトの可用性向上にも寄与するのです。また、Amazon ECRによるイメージ管理を導入したしたことで、リリースしてバグが起こった際のロールバックも非常に楽になりました。

Fargate採用にあわせ、セッション管理やHTMLキャッシュをmemcachedに書くように変更していますが、CodeIgniter2ではmemcachedにセッションを書けないという問題があったため、CodeIgniterは3系にバージョンアップしています。

フロントエンドのリニューアル内容

エンジニアHubに会員登録すると
続きをお読みいただけます(無料)。
登録のメリット
  • すべての過去記事を読める
  • 過去のウェビナー動画を
    視聴できる
  • 企業やエージェントから
    スカウトが届く