大盛況!? 第一回テックフェス 〜テックバトル編〜

はじめに

みなさんこんにちは!現在、内定者インターン中の内藤です。
本日は、先日レバレジーズで行われ予想以上に(失礼)大盛況だった「第一回テックフェスのテックバトル」についてお話しします。

この記事を通して、レバレジーズのエンジニア組織の風通しの良さを少しでも感じてもらえると嬉しいです!

そもそもテックフェスとは?

テックフェスとは何なのか簡単にお話しします。
テックフェスは、レバレジーズのエンジニア組織メンバー全員を対象としたテクノロジーの祭典です。弊社エンジニアの技術力を向上させ、よりよいサービスを世の中に提供できるようにするために企画されました。

今回、テックフェスは記念すべき第一回を迎えました🎊
その記念すべき第一回目は、以下のようなタイムスケジュールで行われました。

テックバトル

テックバトルでは、3人1組チームに分かれ、コーディングの「正確さ」と「速さ」を競うコーディングバトルを行いました。 コーディングバトルってやっぱりエンジニアならではで楽しいですよね!

では、早速今回のテックバトルで扱った問題を見ていきたいと思います!

問題概要

今回のテックバトルの問題は、以下の3つの実装を各人が担当し3人で協力して一つのものを作るという内容でした。

※ 今回、企業数と候補者数は同じ( n=1000)であるとします
※ この安定マッチングとは、企業と候補者、共に現在組んでいるペアよりも希望順位が高いペア(以降では、ブロッキングペアと呼びます)が存在しない状態を指します

テックバトルの問題に挑戦!

上記でも少し触れた通り、この問題の肝はCさんであり、Cさんが如何にこのプログラムを実装するかにチームの命運がかかってます笑

今回、企業(候補者)数  n に対して、全組み合わせを力任せで調べるBrute-Forceアルゴリズムを用いると計算量は  n ! になります。 n が小さい値の時はまだマシですが、 n=1000 ともなると計算量があまりに膨大になってしまうので、このような時はアルゴリズムの知識が重要になりますね!

今回の問題で活躍するのは、Gale-Shapleyアルゴリズムというもので、このアルゴリズムを用いると計算量は  O(n^2)で済ませる事が出来ます。恐ろしいほど強力ですね!😲

Gale-Shapleyアルゴリズム

今回の問題で重要なのは、如何にしてブロッキングペアの存在しない安定マッチングを実現するかという点です。
Gale-Shapleyアルゴリズムは以下のようにして、安定マッチングを実現します。

では、アルゴリズムを確認したところで、実際にコードを書いてみましょう!

実際に書いてみた!

今回は、Aさん, Bさんの実装は置いておいて(ごめんなさい笑)、CさんのGale-Shapleyアルゴリズムに該当する部分のプログラムをpythonで書いてみました。
(pythonにしたのは、自分が日頃業務とは別に使うことが多いからです)

今回使う変数は以下の7つです。

# 初期に与えられている変数
n : 企業(候補者)数  ... 今回、企業数と候補者数は同じ数とします
corps : 企業のリスト ... ['A', 'B', 'C', 'D', 'E'] のような形
candidates : 候補者のリスト ... ['a', 'b', 'c', 'd', 'e'] のような形
corp_desire_order : 企業の希望順位リスト ... {'A': ['b', 'e', 'd', 'c', 'a'], 'B': ['c', 'a', 'e', 'd', 'b'],}...のような形
candidate_desire_order : 候補者の希望順位リスト ... {'a': ['B', 'D', 'A', 'E', 'C'], 'b': ['C', 'A', 'D', 'E', 'B'], ...}のような形
 
# 最終的に出力する変数
corp_to_candidate : 企業と候補者のマッチングリスト ... {'a': 'D', 'b': 'C', ...}のような形
candidate_to_corp : 候補者と企業のマッチングリスト ... {'D': 'a', 'C': 'b', ...}のような形

この7つの変数を基に、プログラムを書くと以下のようになります。

def gale_shapley(corps, candidates, corp_desire_order, candidate_desire_order, corp_to_candidate, candidate_to_corp):
   # 1. 候補者とマッチングしてない企業1社が、希望順位が一番高い候補者にマッチングを申し込む
   for corp in corps:
       if corp not in corp_to_candidate: free_corp = corp
   candidate = corp_desire_order[free_corp][0]
 
   # 2-B. 1で選ばれた候補者がマッチング中であった場合、現在マッチング中の企業とマッチングを申し込まれた企業の希望順位を比較する
   if candidate in candidate_to_corp:
       another_corp = candidate_to_corp[candidate]
      
       # 2-B-b. もし、マッチングを申し込まれた企業の方が希望順位が上なら、候補者は現在のマッチングを解消し、マッチングを申し込まれた企業とマッチングをする
       if candidate_desire_order[candidate].index(free_corp) < candidate_desire_order[candidate].index(another_corp):
           del corp_to_candidate[another_corp]
           corp_to_candidate[free_corp] = candidate
           candidate_to_corp[candidate] = free_corp
           corp_desire_order[another_corp].pop(0)
 
       # 2-B-a. もし、現在マッチング中の企業の方が希望順位が上なら、候補者はマッチングを申し込まれた企業を断る
       else:
           corp_desire_order[free_corp].pop(0)
  
   # 2-A. 1で選ばれた候補者がマッチングしていなかった場合、候補者は企業からの申し込みを承諾する
   else:
       corp_to_candidate[free_corp] = candidate
       candidate_to_corp[candidate] = free_corp

# 3. 1~2を全ての企業と候補者がマッチングするまで繰り返す
while len(corp_to_candidate) < n:
   gale_shapley(corps, candidates, corp_desire_order, candidate_desire_order, corp_to_candidate, candidate_to_corp)

書いてみると意外とシンプルですよね。
皆さんも是非他の言語でチャレンジしてみてください!

最後に

今回、テックバトルを通して、「新しいアルゴリズムを知れたから良かった!」というよりは、エンジニア同士の交流が何よりも貴重で楽しかったです笑

レバレジーズのエンジニア組織は、技術力だけではなく、今回のイベントのようなエンジニア同士の交流も大事にする組織です。日頃からこのようなイベントを行うことで部署間の壁がなく、気軽に悩みを相談出来る環境が常に整っており、とても働きやすいと日々感じています!

皆さんもこんなレバレジーズで一緒に働いてみませんか? レバレジーズに少しでも興味を持っていただけた方は、是非下記リンクを覗いてみてください!


leverages.jp

新卒1年目のエンジニアがUX改善のPDCAを構築し、1年間でCVRを13%改善した話

はじめに

こんにちは。21年新卒の益子です。現在私は、看護業界に特化した人材サービス「看護のお仕事」において、求人サイトのUX改善やそれに伴うシステム開発、ディレクションに携わっています。

看護のお仕事では、サイト改善において指標となる定量データだけでなく、「看護師さんにとっての使いやすさ」などの定性データも重視して改善を進めています。

このようなサイト改善の取り組みに対しては、事業部としても知見が浅く、常に手探りの状況でしたが、私はユーザビリティテスト・デプスインタビューなどのUXリサーチやその結果を元にした改善施策の立案・実装・効果検証までを一貫して担当し、1年間でCVR(※1)を13%改善しました。

(※1). Webサイトへのアクセス数、またはアクセスしたユーザー数のうち、コンバージョンに至った割合を示す指標。転職エージェントサービスが運営する求人サイトの場合、求人応募やサービス登録に至ったユーザーの割合を示す。

この記事ではエンジニアとして新卒入社してからUX改善に取り組み始めたきっかけ、取り組みの中でぶつかった壁、どのように問題を解決しCVR改善という成果に結び付けたかについてまとめたいと思います。


きっかけは「ユーザーが迷いながら求人を探している」現場に遭遇したこと

私が求人サイトのUX改善に取り組み始めたきっかけは、ユーザビリティテストに同席した際に希望する求人を探せずに戸惑っているユーザーの様子を目の当たりにしたことでした。

ユーザビリティテストでは被験者に対し「東京都渋谷区で、希望する条件に一致する求人を検索して表示して下さい」といったタスクをお願いし、モデレーターの目の前でタスクを実行してもらいます。被験者の実際の操作を通して、スムーズに求人を検索できるか、希望条件に一致する求人を絞り込めるかなど、ターゲットユーザーにとってのプロダクトやサイトの使いやすさを評価していきます。

初めて同席したユーザビリティテストで見たものは想像と大きく異なり、「希望条件に対して、サイト上のどの絞り込み項目が適切なのか迷っている」「絞り込み条件を増やしすぎてしまい希望する求人を見つけることができない」というユーザーの様子でした。

私はこの体験を通し「どれだけ努力して実装してもユーザーにとって使いにくいようでは無意味。看護師さんにとって使いやすい求人サイトを作るために開発業務以外もできるようになりたい。」と思うようになりました。


求人サイトのUX改善を進めるため、自ら提案して3つの取り組みを開始

ユーザーにとって使いやすいプロダクトを作るためには、改善施策の全ての議論をユーザー起点で行う必要があると考えました。さらに具体的に言うと、サイト改善に関わるメンバー全員が、架空のユーザー像を元に議論し合うのではなく、実際にユーザーに触れて得た一次情報を元に議論できる状態になる必要があると考えました。

ユーザーの一次情報から議論を行えるチームになるため、以下の3点を提案し、チームで実行しました。

1. デプスインタビューを実施し、看護師転職において求人サイトがどのように使われているのかを調査する

まず看護師転職の課題について解像度を上げるためにデプスインタビューを行いました。

デプスインタビューとは、特定テーマについて対象者と1対1でインタビューを行う定性調査手法です。対象者の表面的な行動・思考だけではなく、行動・思考の背景にある事情や、対象者ですら言語化できていない課題を明らかにすることができます。

デプスインタビューでは、調査したいテーマに合わせた対象者の選定が調査の成果を左右します。私たちは調査結果を求人サイトの改善施策に落とし込みたかったため、「転職検討中であるが、転職エージェントサービスの利用経験がない人」を対象とし、5名に対し調査を実施しました。

転職エージェントサービスの利用経験がある人は、求人サイトの使いやすさよりも、過去の利用体験を検討材料としてサービスを判断する可能性があります。そのため、転職エージェントサービスの利用経験がない人にインタビューを実施することで、転職を通して得たいものに対し、どのように求人サイトを利用するのか・転職サービスに何を期待するのかを明らかにしようと試みました。

2. オープンタスクでユーザビリティテストを行い、現状のサイトを再評価する

デプスインタビューを通して得られた示唆は大きく2点でした。1点目は転職を通して得たいものやその理由、2点目は求人サイトを「単純な求人検索ツールとして使用するのではなく、その求人サイトを運営する転職エージェントサービスを比較するために使用している」という実態です。

その示唆に対し、オープンタスクでユーザビリティテストを行い現状のサイトを再評価しました。単純にタスクをお願いして実行してもらうのではなく、「このページではどのような情報が得られましたか?」「次はどのように操作したいと思いますか」「サイトを離れた後は何をしたいと思いますか?」というオープンタスクでサイトの評価を行いました。

これにより「なぜページ内のコンテンツが読み飛ばされているのか」「なぜ特定のボタンが頻繁にクリックされるのか」など、なぜユーザーはこのように操作するのかというところまで定性データを収集することができました。

3. 改善施策の起点となる仮説を、ユーザーを起点にして言語化する

デプスインタビューやユーザビリティテストでユーザーに対する解像度が高まり、チーム内で改善施策の議論が活発になりました。

その議論を”ユーザー起点”という観点で質のよいものにするため、「タスク管理ツールに改善施策案を起票する際、ユーザーを起点に仮説を言語化する」というルールを作り、徹底しました。

具体例を挙げると「求人応募ページの応募ボタン文言が〇〇であることにより、ユーザーは違和感を覚えて求人応募を完了することができないのではないか」という起票内容です。

このルールを履行することで、改善施策においてユーザーを軸に仮説を立てることが仕組み化され、常にユーザーの一次情報を材料とした議論を行うことをメンバー全員に定着させることができました。


施策リリース後にぶつかった「ABテストで悪化傾向が出た時に仮説が途切れてしまう」という問題

前述の3点の取り組みにより、サイト改善における議論の質が高まりました。しかし、いくら議論を重ねても、すべての施策で改善傾向が出るとは限りません。

実際に、チームメンバー全員で考え抜いた施策をリリースした際、CVRの悪化傾向が出てしまったことがありました。

サイト全体のコンバージョンへのインパクトが大きい求人詳細ページのUIを大幅に変える施策を打ち出した時のことです。プロトタイピングでのユーザビリティテストを何度も実施しながら施策内容のブラッシュアップを重ねていたものの、実際に施策をリリースしてABテストを実施したところ、CVRが大きく悪化する結果が出てしまいました。

インタビューやユーザビリティテストを重ねながらメンバー全員で考え抜いて打ち出した施策だったため、各数値指標の悪化を目の当たりにした時には、次の打ち手を見失う状況に陥りました。


問題を解決するために、リリース直後から定量・定性両方のデータで施策を振り返る

この状況に陥った根本的な原因は、定性データから打ち出された施策効果を検証する仕組みが整っていなかったことでした。

Aパターン・BパターンそれぞれのCVRの結果や各ページの遷移率などの定量的な施策効果の検証に関しては以前から仕組み化されていましたが、「施策内容によってユーザーの操作はどのように変化したか」「なぜ変化したか」といった定性的な施策効果を検証する仕組みはなく、1から構築する必要がありました。

私はこの状況を改善するため、リリースした施策を定量・定性両方のデータで検証することを提案し、チームメンバーで実行に移しました。UI上、施策の要点となるすべての箇所にクリックログを実装してクリック率やその後のコンバージョンを計測しながら、ユーザビリティテストも並行して実施し、操作の背景にあるユーザーの心理を探るという検証方法です。

これにより施策の仮説が途切れることがなくなり、1つの仮説から後続施策・新しい仮説が生まれる状態を作ることができました。

前述した求人詳細ページのUIを大幅に変える施策も、定量・定性両方のデータで検証することで、求人応募ページへ遷移するボタンが致命的な悪化要因となっていたことを発見することができました。その後の追加施策でボタン部分を改修し、98%以上の有意差でマイクロコンバージョンの完了率を29%改善することができ、CVRを向上させることができました。


結果、どのような変化があったか

これらの取り組みを継続した結果、ユーザーを起点とした施策立案・施策のリリース・定量と定性の両方を駆使した効果検証・追加のブラッシュアップ施策の実行まで、UX改善のPDCAサイクルを回すことができるようになりました。

その変化は数値成果に表れ、1年間で最重要指標であるCVRを13%改善することができました。

また、UX改善のPDCAサイクルは途切れず、施策の確度も向上しています。施策立案においてはこれまでに実施していなかった新しい定性調査手法やヒューリスティック評価を取り入れ、施策実行においては開発メンバーが着手優先度を柔軟に調整することでABテストの実行数が増加しています。

サイト改善に関わるメンバー全員が、前年度以上の成果を出そうとモチベーション高く取り組んでいます。


終わりに

昨年度一年間の取り組みは、自分自身の今後にも大きな変化を与えたと思っています。

開発職として入社したものの、「ユーザーが迷いながら操作している」という現場に遭遇したことで、それまで実務で取り組んだことがなかった「UX」という分野に取り組むきっかけができました。

また開発業務の枠を超えて、リサーチ・施策立案・施策の実装とリリース・効果検証の全てのプロセスで提案と実行を行う中で、レバレジーズが顧客体験の改善という観点で優れているところ・未熟なところの両方が見えてきました。

上記の経験を通し「エンジニアでありながらマーケティング分野にも職域を広げたい」という思いが「レバレジーズの顧客体験の改善を自分が牽引できるようになりたい」という思いに変わりました。それを実現するために業務内容も少しずつ変わり、昨年度はマーケターが持っていた「UX改善によるCVRの向上」というミッションが、今年度からは自分のミッションとして与えられました。

レバレジーズには社員一人ひとりが事業や担当業務の課題を見つけ、新しい取り組みを提案する・責任をもって実行するという行動を評価する文化があります。そこには年次はもちろん、職種の枠も関係なく、その文化があったからこそ自分の目標に近づくための働き方ができていると感じています。

レバレジーズには事業やプロダクトの成長に、開発の職域を超えて取り組んでいるエンジニアが多く在籍しています。開発職として技術を磨きつつ、事業やプロダクトの成長に幅広く関わっていきたいという方は、カジュアル面談などでレバレジーズのエンジニアと話してみませんか?きっと面白い発見があると思います。

ご興味がある方は是非こちらからエントリーをお願いします!


leverages.jp

未経験インターンが新規事業に飛び込んで 1年半でリーダーになるまで

はじめに

この記事では、未経験から短期間でリーダーになるためのマインドセットをご紹介したいと思います。
僕のように未経験でエンジニアになる方、リーダー経験を若いうちに積みたい方には、ぜひ読んでいただきたいなと思っております!

自己紹介

初めまして、エンジニアの五十嵐です。
僕は、2020年10月からレバレジーズで内定者インターンを開始し、2021年4月に入社してから現在に至るまでHRテック事業部という新規事業の部署に所属し、SaaSの開発を行っております。

学生時代は、小、中、高、大学とサッカーに青春を費やしてきたスポーツ少年でした。 大学では、情報系や理系の学部に所属していた訳ではなく、文系の商学部に所属していたので、Webエンジニアとは無縁の学生時代を過ごしておりました。
入社前は、自分で家計簿アプリやTwitterの模倣アプリ、シフト管理アプリなどを作ったり、Paizaなどでアルゴリズムを考える問題を解いたりしていた程度でした。

なぜエンジニアに?

きっかけは就職活動でした。
様々な業界の仕事や業種、企業を知りたかったので、金融系や日経大手の企業など幅広く受けていました。
多くの企業や業界を見ている中で、ITのWeb業界に興味をもち、様々な人と会って話を聞いていくうちにエンジニアという道もあることを知りました。
そして、エンジニアの働き方や仕事内容などに興味をもち、実際に自分でプログラミングを学び始めたのがきっかけでした。

新規事業に参画することに

僕は、新しい物事や常に変化する状況に対してモチベーションを高く持って行動するタイプです。レバレジーズ選考時にその旨を会社側に伝えていました。
内定後は、すぐにでも実務の経験を積みたいと考え、内定者インターンの希望を会社に出していました。
内定者インターンをするに当たってどの部署で働きたいかを人事と面談している時に、新規事業が動き出している話を聞き、やってみないか?という提案をもらいました。

僕は、裁量権があり、幅広い経験ができる会社だと思ったからこそレバレジーズの内定を承諾しましたが、まさかこんなにすぐにチャンスがくるとは思わず驚きました。
こんなチャンスはもしかしたら2度と来ないかもしれないと思い、新規事業でインターンをしたい!と伝え、参画することになりました。

業務内容

業務として経験したことは以下になります

  • 要件定義
  • 基本設計・詳細設計
  • 実装
  • テスト
  • UI/UXに対する提案

使用技術は以下になります

  • FE

    • TypeScript
    • React
    • Apollo Client
  • BE

    • TypeScript
    • PHP
    • Laravel
    • Lighthouse
    • NodeJS
    • NestJS
    • GraphQL
    • Apollo Federation
    • PostgreSQL
    • Docker
    • Redis
  • Infra

    • GCP
    • Firebase
  • external-service

    • SendGrid
    • Auth0

0からの開発かつ、初期メンバーは5人だったので、インターン生であっても上流工程から下流工程まで経験させてもらいました。 バックエンドを主に担当し、GraphQLでAPIの開発をマイクロサービスアーキテクチャで行っていました。

設計や開発を行う中で、未経験の僕が意識したことが以下の3つになります。

  • チャレンジしてどんどんアウトプットを出す
  • メンバーからの信頼を得る
  • サービスを俯瞰した提案を心がける

1つずつ順を追って説明していきます。

チャレンジしてどんどんアウトプットを出す

当初はGraphQLを知らず、SQLの仲間かなとか思っていましたし、DB設計の知識も全くありませんでした。 そんな僕ではありますが、個人的に決めていたことがありました。

それが、「わからないことを、すぐに質問をしないこと」です。

僕の先輩が営業からエンジニアにジョブチェンジした方で、「とりあえずチャレンジしよ!」というポジティブでレバレジーズっぽい方でした。
それに習って、僕もわからないことがあった場合でも、上司やメンバー、業務委託の方の設計書やコードをみて学んで、わからないことは調べて、自分なりのアウトプットを出していました。
アウトプットを出すことで自分の考えも整理でき、レビューで指摘された場合でも意見を伝えることができていました。
このやり方は、インターン開始当初はタスク消化の遅延を出してましたが、その時に吸収されたものが数ヶ月後になって活きてくるようになり、徐々にスピード感を持って業務を行えるようになっていきました。

なので、このブログを読んでくださっている新人エンジニアの方はわからない技術や物事は自分で学んでどんな形であってもアウトプットを出すことをオススメします。

メンバーからの信頼を得る

先輩と1on1を行っている時、何か個人的な目標を作ると良いとアドバイスをもらい、1年間の目標として「困った時は五十嵐」という立場になることを設定していました。
そして、その状態はどんな状態を指すのか考えた時に、誰よりもサービス全体の理解と使用技術の理解をしている状態だと考えました。

そのために、僕は任されているタスクを完璧にこなすことはもちろんですが、業務以外の空いた時間や休日などを利用して以下のことを自主的にキャッチアップしていました。

  • サービスに関わるマイクロサービス8つ全ての仕様理解とデータ構造の把握
  • クリーンアーキテクチャの理解やそれに伴うSOLID原則の理解
  • GraphQLやNestJSなどのモダンな技術の理解のために個人的にアプリを作成

これらのことは、「困った時は五十嵐」という立場になるためにやっていたことではありますが、新しい物事を学ぶことが楽しくて、つい時間を忘れてしまうこともしばしばありました。
特にアーキテクチャ周りの勉強をする前と後ではサービスの見え方が変わりましたし、改善点も見えてきたのでスキルアップしたなという実感ができました。

サービスを俯瞰した提案を心がける

僕がレバレジーズを選んだ理由の一つが、早い段階でリーダー業務を経験したかったことが挙げられます。
そのため、与えられた自分のタスクだけこなすのではなく、主体的に行動し関係する影響範囲を広げることで、自らのスキル向上とリーダーからの信頼獲得を意識していました。そしてその後、少しずつリーダー業務を任されるようになっていきました。

具体的には以下の4点です

  • メンバーのサポート
  • タスクの作成、優先度付け
  • リリーススコープを考慮した意思決定
  • スクラムMTGのファシリテート

この中でも、「リリーススコープを考慮した意思決定」の部分は早めに吸収してよかった点だったと思っています。
なぜなら、メンバーの僕が見えている部分とリーダーから見えている部分が違うことがわかり、よりサービス全体が見れるようになったからです。

リーダーは、より高い位置から俯瞰してサービス全体をみており、広い範囲の仕様を把握をして、様々な情報や観点から意思決定をしていました。
なので、僕もリーダーに対して提案を行う時は、担当機能の仕様把握だけでなく、サービス全体の仕様とその影響範囲の把握をしてから提案をするようにしていきました。

より俯瞰してサービス全体を考えた提案をすることでリーダーから信頼を得て、リリーススコープ内の仕様の選定や機能実装の際の意思決定を少しずつ任されるようになっていきました。

読んでよかった本

設計や実装時のわからない時にインプットのために読んでいた本がいくつかあるので紹介します。

リーダブルコード

単にコードを書くだけではなく、わかりやすく、そしてメンテナンス性の高いコードを書くために、気を付けたいことなどが書かれています。
実践的なテクニックや心構えが中心で、例えば「どんな名前付けをした方が良いか」ということ等が紹介されています。

現場で役立つシステム設計の原則

わかりやすいソースコード、設計についてオブジェクト指向設計の特徴を生かしながら具体的に説明してくれる本となっています。
実務で役立つような内容となっており、設計について完全に初心者でも、具体的な説明で設計に何が必要なのかを理解できます。

アジャイルソフトウェア開発の奥義

ソフトウェア開発の原則のSOLID原則などの設計についてやデザインパターンとケーススタディのコードが書いてあります。
新人エンジニアには少々難しい内容ではありますが、知っているのとそうでないのではスキルアップに大きく差が出ると思うのでおすすめです。

Clean Architecture

この本は、いわゆる設計手法としての「クリーンアーキテクチャ」についての話かと思いがちですが、
内容はアーキテクチャとは何か、どういった目的意識があるのかというアーキテクチャ全体の概念で、非常にためになる話が多くいくつも学びがありました。

終わりに

これらのことを意識して、ひたむきに業務やサービスに向き合っていたことで、入社2年目となる現在はリーダーを任せてもらっています。
リーダーになってからは、開発業務以外に、チームのタスクの作成や優先度付け、上層部へのエスカレーション、メンバーのピープルマネジメント等も行っています。
マネジメントの難しさに悩むことも多々ありますが、関連書籍で学んだり、上長などに相談するなどして自分の業務の幅を広げています。

僕の経験でもわかるように、レバレジーズは経験や年齢で判断せずにチャンスを与えてくれる環境です。
若いうちから様々なチャレンジをしたい方にはこれ以上ない環境だと思います。

また、未経験で不安があった僕ですが、今では毎日学びの連続で、責任感はありつつも非常に楽しく働くことができています。

ぜひ、みなさんも一緒にサービスを作っていきませんか?
レバレジーズにご興味ある方は、こちらからエントリーお願いします!

leverages.jp

Atomic Designを導入して開発コストの削減とデザインの統一を実現した話。

はじめに

こんにちは。2021年新卒の田中です。現在私は、フリーター・既卒・第二新卒など、20代を対象とした就職・転職支援サービス「ハタラクティブ」のシステム開発やユーザー体験の改善に携わっています。

ハタラクティブでは、Laravel + jQueryだったフロントエンド環境をReact/TypeScript + Next.jsにリプレイスしました。その際、コンポーネントシステムとしてAtomic Designを採用し、入社1年目ながら選定から設計、導入プロジェクトの管理、運用ルールの構築を任せていただきました。

この記事では、ハタラクティブでどのような課題を抱えていてAtomic Designを採用したのか、導入を進めるにあたってどのような問題が発生し、どう解決したのか、メリットを含めてご紹介します。

課題は開発コストの削減とデザインの統一

ハタラクティブではフロントエンドをReact/Next.jsで開発していますが、当初はコンポーネント管理に関するルールが決まっていない状態でした。そのため、同じ記述が複数箇所にあったり、同じUIパーツが共通化されていなかったり、無駄な記述や重複が多く、開発コストが余計に増えたり保守性が下がる問題や、修正作業が属人化する問題を抱えていました。

当時は、スタイルガイドが機能していなかったため、チーム内でデザインについての共通認識を取れず、作成したUIパーツを使い回す体制も整っていませんでした。その結果、同じUIでもページによってデザインが異なっている状態が発生していました。

以上のことから、ハタラクティブオウンドメディアチームでは開発コストを小さく抑え、デザインが統一された状態を作る課題があったため、導入事例やドキュメントが多いAtomic Designを採用し、コンポーネントシステムを構築することを提案しました。

導入に際しての懸念

チーム内の知見が少なかった

当時は本格的にAtomic Designを使った開発経験があるメンバーがいませんでした。どのように導入すればいいかのイメージがすぐに湧かず、チーム内でコンポーネント化の共通認識が持てない状況でした。

そのため、私が主導して他サービスのAtomic Design導入事例の記事や社内の他チームへのヒアリングを通じて、Atomic Designに関する知見を集めました。自分で試しにテンプレートファイルのAtomic Design化を実装し、そこで得られた知見に基づき、チームで導入の方針や定義、懸念点の解消方法を明確にするための話し合いの場を複数回設けました。

導入コストが大きかった

コンポーネント化を進めることで今までのStyleの記述方法を大きく変える必要があるなど、ハタラクティブへのAtomic Design導入コストの大きさの懸念もありました。

導入コストに関しては、既存コードの整理や将来的な開発コストの削減を目指す方針で認識を合わせ、通常施策とは別のリファクタリングタスクを通じて長期的に進めていきました。Styleに関してはNext.jsでサポートされているCSS Modulesを採用しました。CSS ModulesはCSSをmoduleに分け、JSファイルでimportすることでスタイルを適用する手法です。コンポーネントごとにmoduleファイルを分けることでスタイルをコンポーネントに閉じることができ、class名の干渉を考慮する必要なく記述することができます。

/* Example.module.scss */

.title {
  font-size: 1.6rem;
  color: #222;
}
.box {
  padding: 8px 16px;
  background: #eee;
}
// Example.tsx

import styles from "./Example.module.scss"

const Example: React.FC = () => {
  return (
    <>
    <h2 className={styles.title}>タイトル</h2>
    <div className={styles.box}>
      {/* 中にコンテンツ */}
    </div>
    </>
  )
}

export default Sample

CSS Modulesの導入から記述方法の共有を主導し、デザイナーがコンポーネント単位の書き換えを徐々に進めていくやり方を取りました。

方針とルールを決めて、長期的に導入を進めていく

基本を理解した上でコンポーネント分けルールを作る

Atomic Designの基本を理解し、ハタラクティブに導入する上での解釈をすり合わせ、コンポーネント分けルールを作成しました。ルールの一部を紹介します。

例えば、MoleculesはAtomsやOrganismsと異なり定義が難しかったため、再利用性を判断軸に置き、分類に迷った場合は一旦Organismsにする運用方針に決めました。 データの取得や保持に関しては、統一性と再利用性を高める目的で、API通信に関する機能や各ページで異なるデータはPagesのレイヤーに、固有のデータはOrganismsに持つようにしました。

準備→試験導入→実装の順にスケジューリング

方針とルールが固まったら、どういったスケジュールで進めるのかを決めました。実際のプロジェクトは準備編、試験導入編、実装編の3つのフェーズに分けて進めました。

準備編では「何をやるのか」「どこまでやるのか」が決まっている状態をゴールに据え、既存デザインパターンの洗い出しとコンポーネント設計を行いました。デザイナーと一緒に既存パーツのバリエーションを精査しページ全体のトンマナやデザインを統一しながら、コンポーネント分けルールに沿って分類と設計を進めました。

試験導入編は「チーム全員がやることを理解し、導入方針に納得している状態」をゴールとし、本格的な実装の前に工数を取って全員がAtomic Designを試す週を設けました。複数の視点から見えてくる問題や疑問を事前に解消し、スムーズに実装を進めることを目的としていました。

実装編は「通常施策への影響を小さく抑えながら導入を完了する」ことがゴールで、Atoms→Molecules→Organismsと小さいパーツから順に実装していきました。 スタイルガイドが機能していない問題に対しては、storybookの導入を決めました。 storybook.js.org storybookは、UIコンポーネントの管理やアドオンを使ったテストなどができるツールで、コンポーネント化と並行してスタイルガイド作成を同時に進めることにしました。

こうして約半年にわたるスケジュールを作成し、導入プロジェクトを進めていきました。

実装時にぶつかった問題

準備を重ねて実装編に入りましたが、いくつか問題があがってきました。

Atoms, Molecules, Organismsだけでの管理ができないケースがある

Atoms、 Molecules、 Organismsだけでの管理は、ディレクトリを増やさずに済むのでシンプルさを維持できたり、世の中にリファレンスが多いメリットがあります。しかし、エントリーフォームなど複雑なUIに対しては「Atomsは最小単位で他のコンポーネントを含まない、MoleculesはAtomsを1つ以上含む」などのルールが適用できず、例外が発生してしまうのが避けられない状況でした。

それに対し、ハタラクティブ独自のディレクトリルールを追加するのは、慎重に考える必要がある一方で、ハタラクティブのデザインに合わせて管理しやすい形を実現できるメリットがあります。

そのため、既存のAtomic Designの形を大きく変えないように注意しながら独自のディレクトリルールを作成しました。具体的には、Layouts, Transitions, PageContentsディレクトリなどを追加し、それぞれレイアウトを共通化するコンポーネント、カルーセルやアコーディオンなどのインタラクションを共通化するコンポーネント、ページ全体のStyleやStateに関するコンポーネントを管理するようにしました。

コンポーネントに関する作業が属人化している

導入プロジェクトが始まってしばらく経ったタイミングで作業の属人化が問題となり、2つの問題に分割して対処しました。

1つ目は基準が明確化されていないことです。準備編で作成したルールを見直し、Atomic Designやハタラクティブのデザインパターンに関する知識が少ない人でも理解できるように、コンポーネントの各ディレクトリの役割や実際に分ける際のフローをドキュメント化し共有しました。

2つ目は、慣れの問題です。プロジェクトの進捗を優先していたことで特定の人にAtomic Designタスクが集まっている状態になっていました。限られた人しか出来なければ導入後の維持が困難になります。そのため、なるべく全員が実装タスクを担当するようにし、私がレビューに入るようにしました。

導入した結果

デザインの統一、管理が容易に

導入の過程でデザインを精査したことでデザインの統一が実現し、共通コンポーネントを使うことで微妙に違うデザインが生まれることが防げるようになりました。

storybookを使った管理をすることでデザイナー・エンジニア間のUIパーツの認識合わせや、どんなパーツがあるかの把握が容易になりました。

開発速度の向上と属人化の解消

コンポーネントが共通化されたことにより、変更を加える際の対応箇所が1箇所、ないしは少なくなって開発速度が向上しました。

それまでは修正漏れを避けるために担当者が固定されて作業が属人化していましたが、ファイルが統一されたことで誰でも修正を担当できるようになりました。

大規模なデザイン変更や新規コンテンツ実装にも力を発揮

Atomic Designの導入がほとんど終わってきた段階で、サービスのブランドコンセプト見直しに伴う大規模なデザイン変更がありました。その際、コンポーネント化によって新デザインの反映がスムーズになったり、ルールが作成されていたことで新規UI追加の際も深く悩むことなく実装ができました。

新規コンテンツとして求人検索ページの実装タスクでも力を発揮しました。求人検索ページはボタンなどの既存パーツを使いつつ、検索パネルなど新規のUIも多いコンテンツでしたが、storybookやコンポーネントルールにより作業効率を高めることができたことで2週間という短い時間での実装を可能にしました。

終わりに

Atomic Designの採用によって、開発コストの削減とデザインの統一という課題を解決し、オウンドメディアとして今後の成長を加速させていく基盤を作ることができました。しかし、Atomic Designを導入さえすれば解決できるというものではありません。職種を超えて課題について議論し、常により良い方法がないかを模索できるチームがあったからこそ実現できたと思っています。

配属から2ヶ月足らず、Reactに対する経験も浅い状態で、今回のAtomic Design採用提案から導入まで担当させていただきました。このように、レバレジーズには年次や経験を問わず、主体的に取り組める環境が整っています。

レバレジーズは「主体的に仕事に取り組みたい」「エンジニアの技術を活かしてサービスの課題を解決したい」と思っている方が活躍できる環境です。

ご興味を持たれた方は下記リンクからご応募ください。

leverages.jp

全文検索 MySQL FULLTEXTインデックスからElasticsearchへ切り替えた話

はじめに

こんにちは、レバレジーズ フリーランスHub開発チームです。 現在、弊社が運営するフリーランスHub というサービスでは検索エンジンとしてElasticsearchを採用しています。 この記事では、エンジニアがサービスの成長速度と工数を考慮した上で、Elasticsearchを採用するに至った背景や理由、そして、MySQLからElasticsearchへ移行した結果どれくらいパフォーマンスが改善したか、などを紹介していきたいと思います。

サービス紹介

フリーランスHub は全国のフリーランスエージェントの保有案件から、10万件以上の案件をまとめて掲載しているエンジニア・クリエイター向けのフリーランス求人・案件メディアです。

2021年4月にリリースした新規事業で、サイト内でフリーランスエージェントの案件を集約して掲載しており、フリーランスを検討される方が案件に一括で閲覧・応募することができます。

また、サイト内で開発言語や職種別に案件の検索ができることに加え、エージェントの詳細ページから各エージェントの特徴やサービスを受けるメリットなどを比較し自分にあったフリーランス案件・フリーランスエージェントを探すことが可能です。

f:id:r-nakanoue-lvgs:20220224161518j:plain

検索機能の説明

検索パネルは開発スキルや職種、業界、こだわり条件(週3など)や、都道府県、路線、駅での検索機能があります。

f:id:r-nakanoue-lvgs:20220224161624p:plain

f:id:r-nakanoue-lvgs:20220224161640p:plain

サービス設計フェーズ

見通しを立てる

事業をスタートする際は様々な軸で数年の見通しを立てます、今回のテーマに影響するものはこの辺です。

f:id:r-nakanoue-lvgs:20220224171553p:plain

フリーランスHubは機能数が少ないので小さくスタートし、短期間で拡張と機能追加をしてサービスを成長させていく方針にしました。 その他見通しの説明は記事の最後で補足しておきます。

アーキテクトの選定

f:id:r-nakanoue-lvgs:20220224172926p:plain

AWSを利用している他プロジェクトの導入実績をベースに選定、Elasticsearchでの全文検索は幾つかの事業チームでの運用実績があります。 RDSは2つあり、メインはAurora(MySQL)、データ収集と分析を低スペックのMySQLにしています。 集まってくれた開発メンバーの中にElasticsearchを熟知しているメンバーがおらず、安定性と開発速度の両面で懸念が出てきました。

検討

  • リリース前に準備しておきたいことは山積しているため甘い判断は出来ない
  • BigQueryでの全文検索は社内導入実績が無いためElasticsearchよりハードルが高い
  • 最終的にElasticsearchは必要になる
  • 開発期間中に実際のデータが無い状態で、振る舞いを熟知し安定運用レベルに達するのは難しい
  • 5-10万件ならAurora(MySQL)FULLTEXTインデックスでも良さそう
  • RDB利用のほうがLaravelデフォルト機能の親和性が高く開発が楽で初期はメリットが多い
  • AuroraはMroongaの利用が出来ないが、Elasticsearch移行するなら大きく問題は無い

決断

  • Aurora(MySQL)FULLTEXTインデックスで初期開発を行う
  • 機能・拡大フェーズでElasticsearchへ切り替える際、スピードを損なわないシステム設計を行う

システム処理設計

まずはRDSだけでシステムを構築し、切替前にElasticsearchにデータを同期した後、検索機能を切り替える方法を選択しました。

リリース前 (3万件〜)

f:id:r-nakanoue-lvgs:20220224173001p:plain

機能追加・拡大 (9〜12万件)

f:id:r-nakanoue-lvgs:20220224173017p:plain

処理説明

  • 案件データ作成 : HTMLクロール等で収集した元データから表示用のテーブルを作成
  • 全文検索データ作成 : タイトル・内容・スキルなどの文字列を検索用テーブルの1つカラムへ
  • 件数作成 : 表示する事の多い全案件数などはカウントテーブルへ事前に入れておく

サイトからの検索処理

  • 全ての検索処理は検索テーブル、表示は表示テーブルと役割を分ける
  • ヒットした検索テーブルからidを取り、表示テーブルをidで検索し表示する
  • 例えば検索パネルで PHPで検索した場合
    • PHPという文字列で検索テーブルを検索する
    • スキルテーブルからPHPを探しテーブルidで検索というプロセスは行わない
  • 一覧だけでなくサジェスト処理なども同じ

検索データを上手に作成しておく事が必要となる代わりに、検索SQLでテーブルをジョインする必要が無くシンプルになります。 Elasticsearch利用時も検索テーブルだけのシンプルなクエリに出来ます。

インフラ構築

f:id:r-nakanoue-lvgs:20220224173107p:plain

Elasticsearchは最終的なスペックでビルドし利用するまでスペックを落として運用していました。 AWSはCDKで構築したのでサイズとノード数を変更し実行するだけで切り替わる構成です。

Elasticsearchに限らず初期は不要なケースも最初から作成しておきました。例えば、MQはRDSでスタートし後でSQSに切り替えました。 インフラ変更は影響範囲が広く、後で追加を行う時の苦労が多いため、構成は最初から作っておき利用時に起動する等により、サービスの安定性と成長スピードを損なわないメリットがあります。 無駄なコストが発生してしまうので、サービス成長速度を上げ無駄となる期間を短くします。


比較

実行結果

早速結果から、クエリキャッシュOFFで実行しています。 MySQL long_query_time は 0.25s を閾値にして運用しています。

f:id:r-nakanoue-lvgs:20220224173144p:plain

案件数が5万件ほどの時期にソート順の要件が増えてきました。

  • 優先 : 一部の案件を上位に表示
  • 募集終了 : 募集終了した案件も一覧の後ろの方に追加

指定した検索条件で関連順の場合はMySQL FULLTEXTインデックスでも高速ですが、独自にソートを追加したSQLは速度低下が見られました。 ダミーデータで作成した32万件のテーブルにクエリ実行した結果、ヒットした件数が増えるほどソート処理に大きく時間がかかる事がわかりました。 契約数・掲載数も順調に伸びているため、早めにElasticsearchに切り替えることにしました。

1週間ほどエンジニア1名をElasticsearchに貼り付け32万件のダミーデータでクエリを発行し大きく改善する事を確認、他の追加開発を止める事もなく、その後3週間ほどで切り替えが完了。 トークナイザーやデプロイの最適化など、経験と専門性が必要な領域は切り替え後に作り込みました。

クエリ

テーブル、インデックスは item_search です。

f:id:r-nakanoue-lvgs:20220224173452p:plain

CREATE文は補足に記載しておきます。

Aurora(MySQL)

初期

SELECT
    `item_id`, `status`, `created_at`, `base_value`
FROM
    `item_searches`
WHERE
    MATCH(search_string) AGAINST(
    -- キーワードに「東京」が含まれる案件の中で、「Java」もしくは「PHP」の案件の取得
        ' +( ""東京"") +( ""Java PHP"")' IN BOOLEAN MODE
    );

+優先+募集終了

(
    SELECT
        `item_id`, `status`, `created_at`, `base_value`
    FROM
        `item_searches`
    WHERE
    -- `item_id` = 25000,30000,35000,40000,45000は上位表示する優先データ
        `item_id` IN(25000, 30000, 35000, 40000, 45000)
    ORDER BY
        `updated_at` DESC
)
UNION
(
    SELECT
        `item_id`, `status`, `created_at`, `base_value`
    FROM
        `item_searches`
    WHERE
        `status` = 1 AND
        MATCH(search_string) AGAINST(
            ' +( "東京") +( "Java PHP")' IN BOOLEAN MODE
        )
)
UNION ALL
(
    SELECT
        `item_id`, `status`, `created_at`, `base_value`
    FROM
        `item_searches`
    WHERE
        `status` = 2 AND
        MATCH(search_string) AGAINST(
            ' +( "東京") +( "Java PHP")' IN BOOLEAN MODE
        )
);

上位表示する優先データは別のクエリで先に求めています。 上2つのSQLをUNIONしているのは上位表示したデータの重複を防ぐためです。ヒット件数が多い検索条件であるほどソート処理に時間がかかります。なおUNION ALLにしてもヒット件数が多い限りほぼ改善しません。 また、WHERE句でMATCH()を使用すると自動的に関連度順でソートされますが、SELECTステートメントでもMATCH()を使用する事でORDER BY句で関連度順ソートを指定する事ができ、statusと併せてソートする事も可能です。ただ、前者に比べ後者の方がソートのオーバーヘッドがかかるため後者は採用しませんでした。

Elasticsearch

+優先+募集終了

GET /item_search/_search
{
  "track_total_hits": true,
  -- 表示する案件は1ページあたり30件のためsizeを30で指定
  "size": 30, 
  "query": {
    "bool": {
      "must": [
         {   
          "match": {
            "search_string": {
              "query": "Java PHP",
              "operator": "or"
            }   
          } 
        },
        {   
          "match": {
            "search_string": {
              "query": "東京",
              "operator": "and"
            }   
          } 
        }
      ],
      "should": [
        {
          "terms": {
            "item_id": [
              25000
            ],
            "boost": 10000
          }
        },
       {
          "terms": {
            "item_id": [
              30000
            ],
            "boost": 8000
          }
        },
        -- <中略> --
        {
          "terms": {
            "item_id": [
              45000
            ],
            "boost": 2000
          }
        }
      ]
    }
  },
    "sort" : [
    { 
      "status":{
        "order" : "asc"
      }
    },
    {
    "_score":{
        "order" : "desc"
      }
    }
  ]
}

上位表示する優先データはスコアをブーストする方法で実現しています。


切替時のプログラム変更

データ作成

LaravelのScoutを用いて、Auroraの検索テーブルを追加・更新した場合Elasticsearchに自動的に同期される作りにしました。 そのため、モデルをElasticsearchと自動的に同期させるモデルオブザーバを登録するために、検索可能にしたいモデルにLaravel\Scout\Searchableトレイトを追加しました。

use Laravel\Scout\Searchable;

class Post extends Model
{
    use Searchable;
}

検索

以下のクラスを作成し、Elasticsearchで検索できるように設定しました。

  • app/Providers/ElasticsearchServiceProvider
    • Scoutを使ってElasticsearchを使えるようにする設定を記載
  • app/Scout/ElasticsearchEngine
    • ScoutからElasticsearchを操作する内容を記載

また、SQLを呼び出す側だったRepository層では、SQLからElasticsearchのクエリに書き換えました。

形態素解析

php-mecab

  • 形態素解析はphp-mecabを利用
  • スキルテーブルや駅テーブルの名詞をdictに登録
  • 案件データ作成や検索処理以外でも形態素解析の処理がある
  • ES切り替え後もkuromojiだけに出来ない

Elasticsearch kuromoji

  • mecab登録した内容はkuromojiにも登録

検索用データの工夫

f:id:r-nakanoue-lvgs:20220310183456p:plain

1文字は単語として認識しないため、検索データ・検索クエリ共に変換して処理しています。 認識しない記号も同じように変換しています。 駅名は検索データに「渋谷」「渋谷駅」の2つを登録し、検索パネルの場合「渋谷駅」に変換し「渋谷区」の別の駅にヒットしないようにしています。 これらの文字列はmecab、kuromojiに登録しておきます。


設定

Aurora(MySQL)

1単語認識文字数

デフォルトは最小4文字ですが「DB」「東京」などあるため2で設定します。

ft_min_word_len=2
innodb_ft_min_token_size=2

変更する場合はDB再起動が必要なので必ず先にやっておきましょう。

ngram

MySQL FULLTEXT インデックスはWITH PARSER を指定してINDEXを作ります。

スキル(PHP・Linuxなど)での検索の重要性が高いためngram無しでINDEXを作成しました。

ALTER TABLE `item_searches` ADD FULLTEXT KEY `idx` (`search_string`);

例えば「...業務内容はブリッジSEとして...」を解析しても「ブリッジSE」ではヒットしない挙動となり、 Elasticsearch移行前の大きな課題でした。

Elasticsearch

トークナイザー

modeをnomalに設定することで、辞書登録した文字列を1単語として単語認識させることができます。

"tokenizer" : {
  "kuromoji" : {
    "mode" : "normal",
    "type" : "kuromoji_tokenizer"
  }
}

モードがデフォルトのsearchだと、例えば javascriptを形態素解析した場合、javascriptの他に java script としても分解されるため、検索結果にjavaの案件が含まれる事になります。 今回の事例では辞書登録した単語を1単語として単語認識させることで検索結果の精度を上げています。

Character Filter ※2022/03/10 追記

char_filterで検索文字列の大文字を小文字に統一する設定も行なっています。

"char_filter": {
 "lc_normalizer": {
  "type": "mapping",
  "mappings": [
       "A => a",
       "B => b",
       "C => c",
    --- <中略> --- 
       "Z => z"
    ]
  }
},

Character Filterを使い入力文字列を小文字に正規化することで、大文字・小文字に関係なく(※)辞書登録されている文字列に一致させることができます。

※ ユーザ辞書を作成する際にアルファベットは小文字限定で作成している、という前提があります。

エイリアス

  • 必要な理由
    • インデックスはフィールド追加やKuromojiの内容変更などで作り直す事がある
    • プログラムからインデックスを直接参照すると作り直すタイミングでサービスに悪影響が出てしまう
  • 構造
    • エイリアス item_search
    • インデックス item_search_yyyymmdd
    • 変更時はインデックスを作り直しAliasに紐付け検索データを切り替える

リリース手順の変更

artisanコマンドを作成し、以下を実行しています。

  • スキル・駅などのデータを取得しmecabのdictを作成
  • Elasticsearchインデックスの変更がある場合のみ以下も実行
    • 定義に従いインデックスを作成 item_search_yyyymmdd
    • スキル・駅などのデータをKuromojiに登録
    • Aurora item_search から Elasticsearch item_search_yyyymmdd へデータをコピーするプログラムを実行
    • エイリアスを切り替え

終わりに

簡単ではありましたがElasticsearchを採用するに至った背景や理由、MySQLからElasticsearchへ移行した結果どれくらいパフォーマンスが改善したかを紹介しました。

今回の事例のように、レバレジーズでは事業課題を解決するための技術選定やプロダクト改善をエンジニアが主導して行うことができます。 このような主体的に提案ができる環境で是非一緒に働きませんか?

レバレジーズ フリーランスHub開発チームでは一緒にサービスを作ってくれる仲間を募集中です! ご興味を持たれた方は、下記リンクから是非ご応募ください。

leverages.jp


補足

見通し

  • 案件(求人)数
    • 事業モデルが現状だと数年運用しても100万件には達しない
    • スペックアップが簡単など充分コントロール可能なので過度な予測を行う必要がない
  • リリース前期間の見通し
    • 結果的に3ヶ月+12日
    • システムマネージャー2名と類似サービスを見ながら30分ほど打ち合わせ
    • 楽観でチャレンジングな目標を設定
    • 機能・非機能を洗い出しグルーピングして一覧化
    • ストーリーマッピングの要領でリリース前のラインを引く
    • 最初は楽観でラインを引く
    • 実際の開発進捗を見てライン・スケジュールどちらかを調整
    • どうすれば出来るかを常に考える
  • リリース直後が最も忙しくなる
    • 経験則ですが、例えば以下などが良くあります
    • 初めて発生する事案や運用が多い
    • 発見した不具合を即日で潰す
    • 新規の契約や案件(求人)掲載の集中

CREATE

MySQL FULLTEXT インデックス

CREATE TABLE `item_searches` (
  `id` int(11) NOT NULL,
  `item_id` int(11) NOT NULL,
  `base_value` int(11) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `search_string` text NOT NULL,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `item_searches`
  ADD PRIMARY KEY (`id`),
  ADD KEY `item_searches_item_id_index` (`item_id`) USING BTREE,
  ADD FULLTEXT KEY `idx` (`search_string`),
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

Elasticsearch

PUT /item_search?pretty
{
  "aliases" : {
    "item_search_replace" : { }
  },
  "settings" : {
  "index" : {
    "max_result_window" : "500000",
      "analysis":{
        "char_filter": { -- 2022/03/10追記
          "lc_normalizer": {
            "type": "mapping",
            "mappings": [
                 "A => a",
                 "B => b",
                 "C => c",
             --- <中略> --- 
                 "Z => z"
              ]
          }
        },
        "filter" : {
        "greek_lowercase_filter" : {
          "type" : "lowercase",
            "language" : "greek"
          }
        },
      "tokenizer" : {
        "kuromoji" : {
            "mode" : "normal",
            "type" : "kuromoji_tokenizer",
            "user_dictionary_rules" : [ 
                "Java,Java,Java,スキル", 
                "PHP,PHP,PHP,スキル", 
                -- <中略> --
                "渋谷駅,渋谷駅,渋谷駅,駅名",
                "東京駅,東京駅,東京駅,駅名"
            ]
          }
        },
      "analyzer" : {
          "analyzer" : {
            "type" : "custom",
            "char_filter" : "lc_normalizer", -- 2022/03/10 追記
            "tokenizer" : "kuromoji",
            "filter" : ["kuromoji_baseform", "greek_lowercase_filter", "cjk_width"]
          }
        }
      }
    }
   },
  "mappings" : {
    "properties" : {
      "id" : {
        "type" : "integer"
      },
      "item_id" : {
        "type" : "integer"
      },
      "search_string" : {
        "type" : "text",
        "analyzer": "analyzer",
        "fields" : {
          "keyword" : {
            "type" : "keyword",
            "ignore_above" : 256
          }
        }
      },
      "base_value" : {
        "type" : "integer"
      },
      "status" : {
        "type" : "integer"
      },
      "created_at" : {
        "type" : "date"
      },
      "updated_at" : {
        "type" : "date"
      }
     }
  }
}

レバレジーズのHRTech事業について

はじめに

こんにちは。レバレジーズ株式会社エンジニアの加藤です。 今回は、私が所属しているHRテック事業部における新規SaaSの開発についてご紹介したいと思います。

事業領域について

私たちの事業部が扱う領域はHRTechといい、主に会社の人事業務に対するサービスを展開しています。「人事」と聞くと採用活動をイメージされることも多いですが、ここでは「従業員が入社し、退職するまでに関わる業務の全般」を指します。そのため、採用管理や勤怠管理、タレントマネジメント、給与管理などをはじめとした、さまざまなSaaSが存在しています。勤怠管理サービスなどは使ったことのある方も多いのでイメージしやすいのではないでしょうか。

HRTechの役割

人事業務は人材採用、人材育成、離職対策、人事評価、労務管理など多岐に渡り、従業員が入社から退職するまでなくてはならない業務です。適切に人事業務が提供されない会社では従業員は力を発揮できませんし、当然ながら定着もしません。つまり、人事業務が最適化されているかどうかは「会社の発展や成長」を大きく左右することになります。また、会社規模が大きくなればなるほど、人事業務の複雑性は指数関数的に増していきますので、ますます仕組みとして整っていなければなりません。そのため、手続きや情報の管理を効率的に行い、組織設計、採用、人材育成、離職対策など有効な施策立案に繋げ、人事の価値創出に寄与することがHRTechサービスに求められる役割です。

HR(人事)領域の抱える課題

しかしながら、既存のサービスは機能によって(採用管理・勤怠管理・労務管理といったように)分類化されており、人事業務を包括的にカバーできるものはありません。 例えば、「採用・人材育成・離職対策」といった機能は相互に関係性を持ちますが、それぞれの機能ごとにプラットフォームが分断されていれば、統合された情報を元にした適切な人事オペレーションを提供することは難しくなります。 既存の人事業務を各機能ごとにデジタル化(またはDX化)することはできても、HRTechを利用することで人事の価値創出に繋げることができていないということが、人事業務とHRTechという領域の課題といえます。

私たちのプロダクトの意義

前述の課題に対して、私たちのサービスでは例えば、各人事業務で蓄積された従業員の志向性やエンゲージメント、実績や評価などの情報を元に個別適切な配置転換や離職対策に活用することができます。 昨今の潮流としても、企業は労働者の価値観やワークスタイルニーズの多様化などにより、以前よりもパーソナライズされた労働環境の提供が求められています。 より個に適した形で人事業務を行うには、分断されたプラットフォームではなく、包括的に情報を管理できる環境が必要になります。

チーム体制について

私たちの事業部ではマイクロサービスアーキテクチャを採用しており、開発体制としてはスクラムを採用しています。またスクラムの中でもLeSSと呼ばれるものに近く、4-6人程度を1群としたチームが4チームほどで動いています。それぞれのチームが1つのプロダクトを開発するよう自己組織化されています。

チーム体制のメリット

  1. 意思決定の速さ 事業部としての大きな方針はありますが、チームのための意思決定はチーム独自で行っています。人員構成もエンジニアが主体となっているためエンジニア主導でコトが進みその速度も速いです。少人数ゆえメンバー1人1人の意見がダイレクトにチームに反映されます。

  2. 業務の範囲に制限がない 担当業務は単純な作業に留まることはなく、フロントエンドやバックエンド、インフラを問わず機能の要否や設計から実装まで行います。 作業範囲が広いため、様々な技術に触れながら成長することができますし、事業計画に直接携わるなど、エンジニアに留まらない仕事の仕方ができる可能性もあります。 もちろん作業範囲が広いだけではありません。例えばプロダクトの仕様についても、単に仕様を満たすための部分的な作業ではなく、プロダクト全体、ひいてはサービスの成長を見据えた上で設計を行うため、事業経営の中核に立って開発を進めることができます。

  3. コミュニケーションの取りやすさ 少人数のチーム構成のため、メンバーと自分の作業関係が把握しやすく、開発に関するやりとりも齟齬なく行うことができます。 開発業務はハードですが、他のチームも含めて非常に安心感のある組織だと自信を持って言えます。

使用技術について

TypeScript

フロントエンドとバックエンドの使用言語を揃えることで、同じ文法で開発でき、実装者の学習コストを下げ、ソースコードの流用もしやすくなります。 実装者もフロントエンド、バックエンドを問わず実装に参加しやすくなり、フロントエンドもバックエンドも実装したい開発者にとっては魅力だと思います。(ごく一部PHP, pythonもありますがほとんどはTypeScriptです。)

GraphQL

オーバーフェッチングやエンドポイントの複雑化などREST APIの課題を解決するために生まれました。 AirbnbやNetflix、Shopifyなどをはじめとした多くの企業で使用されています。 クライアントとしてApollo GraphQLを利用しており型安全なAPI開発、利用が実現できています。

Google Cloud Platform

私たちのチームでは、外部サービスの利用を除きプロダクトのバックエンドインフラをGoogle Cloud Platform(以下GCP)の利用に振り切って開発しています。 Cloud RunやCloud SQLをはじめとしたマネージドサービスを利用することで、開発者はコンテナやコンテナ内部のアプリケーションの開発に専念することができます。 Cloud PubsubやCloud Build、Cloud Monitoringなども利用しており、GCPインフラでのDevOpsやSREに興味のある方にとっても魅力の高い環境かと思います。 下記に簡単に仕様技術の一覧をまとめています。

  • FE
    • React
    • Apollo Client
    • Material-UI
  • BE
    • GraphQL
    • Express/NestJS
    • TypeORM
    • Docker
  • infra
    • GCP
    • Cloud Run
    • Cloud SQL
    • Cloud Storage
    • Cloud Pub/Sub
    • Cloud Build
    • Cloud Memorystore(redis)
    • Cloud Monitoring
    • App Engine
    • Firebase
  • CI/CD
    • Github Actions
    • Cloud Build
  • external-service
    • SendGrid(email delivery)
    • Auth0(IdaaS)
    • Stripe(payment)

おわりに

いかがでしたでしょうか。

私の所属するHRテック事業部は発足してまだ日が浅く、プロダクトの状況も0-1フェーズにあります。しかし、だからこそ今回ご紹介したような魅力を強く感じられる組織であると思っています。私自身もレバレジーズに入社してまだ1年未満ですが、事業内容や使用技術、働き方など、非常に挑戦的で魅力のある環境だと実感しています。この記事をご覧になり、この事業部を一緒に盛り上げてみたいなと思う方が1人でも増えれば嬉しく思います!

マイクロサービス化を中心においた技術刷新とその狙い

本文

はじめに

 レバレジーズ株式会社 テクノロジー戦略室室長の竹下です。レバレジーズは創業以来事業の成長が続いていますが、ここ最近は事業の拡大にシステムの開発が追いつけていない状態でした。そのため、システム開発が事業の拡大に追いつき、更には加速させるためにマイクロサービス化を主体においた技術スタックの刷新を進めています。この記事では、導入したまたは導入しようとしている主な技術の紹介と、選定した技術が目先の開発だけではなくエンジニア組織全体の課題をどのように解決するかを紹介していきたいと思います。

導入技術紹介

 まず、現在導入したまたは導入しようとしている主な技術のご紹介から始めます。

  • マイクロサービス化
  • レイヤードアーキテクチャ(クリーンアーキテクチャ、ヘキサゴナルアーキテクチャ)
  • DDD
  • TypeScript
  • gRPC
  • gRPC-web
  • GraphQL
  • Github actions
  • BDD/ATDD(cucumber)
  • コード生成
  • CDK
  • Docker
  • ECS Fargate
  • Service Mesh
  • DataDog

列挙した技術がざっくりどの領域に関係するかの図はこちらになります。

 こちらに紹介しているのは主要な技術のみで、他にも様々な技術検証、導入を進めています。それらはこれからの弊社の技術者ブログを楽しみにしていただければと思います。また、個別の技術の紹介もこちらの記事では詳しく行いませんので、あしからず。

事業成長にシステムが追いつくための課題

 それでは、本題の技術導入で解決したい課題を説明していきます。
 まず弊社の開発体制ですが、基本的には1サービス毎にチームが付き、サービス毎にコードベースを分けて開発を行ってきました。
 レバレジーズ主力事業である『レバテック』は、サービスを使ってくださるエンジニアの方や、企業様が年々増えており順調に成長を続けてきました。また、『レバテックダイレクト』や『レバテックカレッジ』をはじめとするレバテックシリーズの新サービスや、看護師転職支援サービス『看護のお仕事』、ITエンジニア向けQAサイト『teratail』などのサービスも提供をしてきました。しかし、開発サイドでは、長年機能とビジネス範囲の拡張を続けたことによるコードベースの肥大化や、サービス間のデータ連携のために本来独立しているはずのチームが密に結合してしまい、人を増やすだけでは事業成長に追いつけなくなってしまいました。特に

  • 相互依存する、複数の巨大モノリシックサービス
  • 標準化の欠乏
  • 自動化の欠乏

の3つによって、組織としての開発効率が下がってしまっていました。

相互依存する、複数の巨大モノリシックサービス

 モノリシックサービスとは、一つのプログラムで一つのサービスを作ることを指します。 弊社もこれまではモノリシックにサービスを開発してきましたが、長年機能拡張を続けてきた結果、PHPで開発してきたことも相まって、コードを一部修正した場合に影響範囲がどこまで及ぶがわかない状態となり、影響の確認のために多大な工数がかかるようになってしまいました。また、サービス間でのデータ連携も行われているため、影響範囲が1つのサービスだけに留まらず他のサービスを巻き込んでバグを出すということも頻発するようになってしまっていました。
 さらに、一つのプログラムが受け持つビジネス領域も拡大してきた結果、必要な業務知識も膨らみ、新しいエンジニアがまともに開発できるようになるまでにも多くの時間がかかるようになっていました。

標準化の欠乏

 弊社では基本的に1サービス1チームが割り当てられ、コードベースも分けて開発を進めています。しかし、これまではコード規約ぐらいしか標準化されたものが無く、開発スタイルや設計、インフラ構築方法もチームでバラバラでした。そのため、スポットで他チームへヘルプに入ったり、ノウハウの共有が困難でした。
 新規サービス開発の際も、機能の流用ができる作りになっていないので、各サービスで同じような機能をバラバラに作るということも多く発生していました。 そのため、チームが増えても相乗効果が発生せず、組織としての開発効率がいまいち上がらない状態になっていました。

自動化の欠乏

 CI/CDが入っていないサービスも多々あり、デプロイやインフラ構築もシェルを手動実行するものが数多く残っていました。そのため、開発面ではチーム毎、開発者ごとにコードの品質が揃わなかったり、運用ではデプロイが限られた人しか実行できなかったり、新しいサービスを作る際にもインフラ構築に工数が多くかかる上に、インフラの監視やログ監視も抜け漏れが発生するなど、開発効率の低下や品質の低下を招いていました。

導入技術がどのように課題を解決するか

 上記の課題を解決するのは一つの技術だけでは解決できないため、マイクロサービス化を中心においた技術スタックの刷新を行い、技術の組み合わせによって解決を目指しました。

マイクロサービス化とレイヤードアーキテクチャを主体とした分割統治

 プログラムの設計において分割統治という考え方があります。大きな問題を小さな問題に分割して、小さな問題をすべて解決することで全体を解決するという考え方です。  サービスのフロント、BFF、バックエンドの3層への分割、ビジネスドメインを再整理しドメインに分割しマイクロサービス化、一つのマイクロサービス内でのレイヤードアーキテクチャによる分割を行っています。それにより、

  • プログラム変更の影響範囲が限定される
  • 開発者が知る必要があるドメイン知識が減る
  • ユニットテストが容易になる

などのメリットを享受でき、結果、長期に渡り開発効率を高い状態で維持することが期待できます。

TypeScript、gRPC, GraphQLなど型システム技術導入によるチーム間コミュニケーションの円滑化

 マイクロサービス化、レイヤードアーキテクチャ化することでチームは細分化されることになるので、チーム間のコミュニケーションが重要になってきます。そのため、フロントエンド、バックエンドともにTypeScriptへ切り替え、マイクロサービス間はgRPC、フロントエンドとバックエンド間はgRPC-webまたはGraphQLを導入しました。gRPC、gRPC-web, GraphQLのIDL(interface definition language)が、そのままAPI仕様書となり、IDLからコードを自動生成しコンパイルを行うことで変更点をコンパイルエラーとして検出できるため、仕様変更した際の伝達が容易かつ安全に行えるようになります。

CI/CD、コード生成、BDD/ATDDの活用による品質の担保

 レイヤードアーキテクチャ化によるユニットテストの容易化、TypeScript化によるコンパイルエラーによるエラー検知を活用するためCI/CDとしてgithub actionsを導入しソースコードのPush時に自動で検証できるようにしています。
 TS化、CI/CDでエラー検知が容易なったことでコード生成も最大限活用できるようになりました。gRPCのIDLからのコード生成の拡張によりレイヤードアーキテクチャのためのボイラープレートの生成や、DBアクセスオブジェクトからの安全なSQL発行コードの生成などを導入することで、開発速度の向上に加えコード品質の安定化も行っています。
 一部のチームではユニットテストに加えてcucumberを導入して、マイクロサービスのAPIをBDD(振る舞い駆動開発)、フロントエンドをATDD(受け入れテスト駆動開発)しています。実装前にエンジニア同士やディレクター-エンジニア間で仕様のすり合わせと検討が可能になり、手戻りが減った上に設計品質の向上にも繋がりました。

IaCによるインフラの既製化とDevOps推進

 弊社はAWSをメインに使用しているため、インフラの構築はCDKを導入しました。CDKはTypeScriptやJavaなどプログラミング言語を使ってインフラを構築できる技術です。アプリサーバーの構成がすべて、フロント、BFF、バックエンドの3層化しDocker+ECS Fargateでサーバーレス化したことで、同じインフラ構築のコードをどのチームでも使い回せるようになりました。CDKをラップした社内ライブラリを用意したことで、アプリエンジニアがインフラ構築から行えるようになり、かつ、サーバーの構築が共通化されたことで監視の漏れや設定のミスもなくなりました。
 現在は更にDataDogの導入も行い、インフラ監視、ログ監視などを強化しています。
 また、マイクロサービス化が進み、サーバーも増えてきているため、ServiceMeshも導入を進めており、より緻密で効率的なサーバー管理を目指しています。

便利さによる標準化の浸透

 技術選定したはいいものの浸透しないということもあると思います。大体は新しい技術を覚えたり切り替えることに対する不安が原因なことが多いです。今回選定した技術に関してほぼ全て社内ライブラリを用意し弊社の開発に特化させることで、

新しいことを覚えるコスト <<< 選定された技術を使うメリット

という状況を作り出しました。一つの技術選定だけでは実現は難しいですが、複数の技術をうまく組み合わせその状況を作り出せれば、あとは自己増殖的に浸透してくれます。

おわりに

 レバレジーズは「関係者全員の幸福を追求する」ミッションを達成するため、今回ご紹介したように、開発効率を高め、事業実現を素早く行えるエンジニア組織への変革をすすめています。そのため技術選定も技術的好奇心や目下の開発だけにとらわれない選定を心がけて行っています。しかし、まだまだ改善できるところは多々あります。もし「技術選定を行って組織を変えていきたい」と思っている方がいましたら、ぜひ一緒に働きませんか?エキサイティングな仕事ができると思います。 ご興味ある方は、こちらからエントリーをお願いします! https://recruit.jobcan.jp/leverages/recruit.jobcan.jp