はじめに
初めまして!エン・ジャパン株式会社で23新卒エンジニアとして入社しました古賀です。
エン・ジャパン株式会社では2023年度から新卒のエンジニア採用を開始し、その第1期生として私たちは入社しました。そのため、弊社のエンジニアの新入社員研修は初めてのことで、まだ前例のない中で23新卒エンジニアは新たな門出を迎えることになりました。
そして私たちはその研修として約1ヶ月の期間でチーム開発を行い、サービス開発を行いました。この記事は筆者の所属チームにおいて、筆者から見たチーム開発の挑戦記です。この記事が社内のエンジニアはもちろん、これから毎年入社する後輩たち、さらに多くの方々にとって何か有益な記事となれば幸いです。
また、この記事はエン・ジャパンのエンジニアグループにとって初めての技術記事となりました。今、エン・ジャパンのエンジニアグループは急拡大期で多くの仲間が続々と入社しております。これから弊社のエンジニアがさまざまな技術記事を投稿することになると思います。どうぞよろしくお願いします。
プロジェクトの目的とビジョン
このプロジェクトは弊社の23新卒エンジニアの新卒研修として4月上旬から1ヶ月間行われたものです。お仕事としての開発業務ではチームで開発を行っていくことになるわけですが、その前練習という形でのサービス開発を目的に実施されました。
プロジェクト開始に先立って部署内で、2年目以降の企画職社員からコンテスト形式でHRサービスの企画案を募り、チーム開発のためのお題として選定されていました。私たち新卒エンジニアは2チームに分かれ、その勝ち抜いた企画案の中からそれぞれ一つずつをチーム開発のお題として割り振られて、開発を行うことになりました。ですので、企画メンバーにとっても、エンジニアと協調しながらサービス開発を行うとはどういうことなのか、実体験をもって学ぶ良い機会となりました。
我々が達成しようとしたこと
私たちが作ることになったのは自己分析支援サービスです。モチベーショングラフと呼ばれる、自分の年齢や過去の大きな出来事を軸としてモチベーションの動きを可視化するグラフがあります。私たちはこのグラフを自動で作成する機能を開発することになりました。この機能を用いることで、求職者は自己分析を深めることができるとともに、企業側は求職者のグラフからより最適な人材を発見することができます。また、過去の出来事に対して深掘り質問を行い、求職者が簡単に自己PRを作ることができる機能も開発することになりました。
自己分析は就職や転職の際に非常に重要です。しかしながら、世の中の求職者と企業のミスマッチの原因の一つに、求職者の自己分析が不足しているというデータと現実があります。私たちはその課題を直視し、魅力的で実用的な解決策を作ることを目指しました。
また、その課題を達成する上で、私たちのチームはできるだけモダンで本番を想定できるような技術的な挑戦を行いました。ただシステムが画面上でうまく機能するだけでなく、いわゆるベストプラクティスな開発方法を体現できるよう意識して、本格的な構成を追求することをゴールとしていました。
チームの構成と役割分担
私たちのチームでは企画メンバーが3人と、開発メンバー5人の計8人体制で業務を行いました。サービスの企画やデザインについては企画メンバーが行い、開発メンバーはそれを具体的に実現するエンジニアリングを行うことで、力を合わせました。
私たちの開発チームの各メンバーは、それぞれが異なる得意分野をもっています。
- Sさん:学生の頃からさまざまなハッカソンや開発で活躍してきた。フロントエンド開発とマネジメントが得意。
- Aさん:学生の頃からIT企業のアルバイトでWeb開発をしてきた。フロントエンド開発が得意。
- Mさん:情報系出身。自然言語処理と競プロ(と麻雀!)が得意。
- Hさん:ネットワーク系のお話に強い。学生の頃からネットワークを専攻してきた。
- Kさん:筆者
私たちは開発の過程できちんと役割分担をすることを目指していました。そして、実際の開発ではきっちりと役割に従ってそれぞれ自分の役割を全うしました。最初に色々話し合った結果、次のような役割分担で開発を進めることになりました。
フロントエンド:AさんとSさん
バックエンド:MさんとKさん
インフラ:Hさん
私たちのチームにはフロントエンドに強い方が2人いました。そのため、フロントエンドとバックエンドの開発を分担する戦略を採りました。また、ネットワークの知識に長けたHさんにはAWSを用いたインフラの構築を任せることにしました。これが、一方でチームの特性を最大限に生かすことができ、他方である程度の属人化を生じさせ、それがある意味で失敗に繋がった経験も含めて、私たちの貴重な学びとなりました。
開発プロセスと方法
使用した技術スタック
私たちが開発したサービスでは、次のような構成で設計を行いました。フロントエンド開発では、SSRを用いてモダンなフロントエンドの開発ができるNext.jsを用いました。また、バックエンド開発ではDjango REST Frameworkを用いてAPIを構築しました。それらをnginxを用いたリバースプロキシによって一緒に組み合わせ、データベースシステムとしてはMySQLやRedisを用いた構成としました。
また、バックエンド側でLangChainを用いたチャットボットの処理を実装しました。そして、その処理を非同期で行うために、タスクキューライブラリであるCeleryを用いて実装を行いました。
一方で、AWSの構築についても本格的なインフラ構築に挑戦しました。GitHub Actionsを用いてCI/CDパイプラインの確立をするとともに、マルチAZ構成で冗長性を確保しつつ、ECSを用いたデプロイメントを行いました。また、データベースにはRDSを導入し、こちらも冗長性を確保することで、システム全体の信頼性が高められることを目指しました。
開発の流れ
私たちの開発プロセスでは、まず役割分担やその他さまざまな細かい話し合いを行って、開発の方向決めをしました。企画メンバーとも話し合いを重ねて、企画の疑問点や実装上の懸念点や難点などを一つ一つ洗い出しました。私たちはそういった話し合いで得た知識やガントチャートによる今後の開発進行予定など、さまざまな情報をNotionでドキュメント化しました。そして、開発期間全体を通して常に情報の共有やドキュメントの更新を行えるように、開発が円滑に進むような仕組みを最初に作りました。
続いて、私たちは開発チームでシステムの全体構成やデータベース設計、API設計を行いました。この辺りはバックエンドとフロントエンドの通信に直に関わってくるポイントだったので、ある程度開発陣全員で事前に話し合って方針を決めました。
一連の計画が完了すると、いよいよ具体的な開発作業が始まりました。実際の開発では、事前に決めた役割構成に従って、各々が自分の力を出し切って開発を行いました。フロントエンド開発では、さらにドキュメンテーションやコーディング規約などの開発に必要な事項を決めた後に、一気に開発を進めました。バックエンド開発では、RESTfulなAPIを心掛けながらAPIの開発を行いました。また、認証や認可についても開発を行いました。AWSを用いたインフラ構築では、まず基礎知識から学びつつ、バックエンドチームと都度話し合いながら構築を進めました。
フロントエンド開発とバックエンド開発がそれぞれ煮詰まってきた段階で、実際にそれらをつなぎ合わせるための調整を行いました。実際にアプリケーションがスムーズに動作するためのバグの修正などの作業も同時に行いました。
そして最後には、それらの開発成果を実際に本番環境に適用するための作業を行い、発表時にきちんとデモを行うための最終調整に向けて努力しました。AWSを用いたインフラ構築ではかなり挑戦的なことをしていたので、作業は難航しました。
最終段階での発表は、私たちが開発したサービスをより魅力的に示すための重要な場面でした。発表は筆者が担当したのですが、プレゼンには自信があったので、製品発表のような形にしてやろうと思い、それに向けて準備しました。
主要な成果と挑戦
プロジェクトの主要な成果
私たちが達成した成果は次のとおりです。
- モチベーショングラフの自動作成機能の実現
- チャットボット機能の実現
- チャット内容の自動要約機能の実現
モチベーショングラフの自動作成機能の実現
モチベーショングラフは通常、PowerPointやdraw.ioのようなツールを用いて、画面上で一つ一つ図形を配置して作成する方法で行っていました。しかし、このやり方では時間と労力を奪い非常に効率が悪く、皮肉なことにモチベーショングラフを作るのに、モチベーションが必要となっていました。私たちが今回実装したモチベーショングラフの自動作成機能は、このハードルを大幅に下げ、誰でも気軽にモチベーショングラフを作成することができる点で大きな成果を上げることができました。
チャットボット機能の実現
モチベーショングラフを作るためのハードルは下げることに成功した一方で、もう一つの課題が待っていました。それは、自分の過去のモチベーションを左右した出来事について、その出来事の具体的な深掘りを行うことに多大な労力がかかることでした。しかし、最終的にこちらの課題も解決することができました。私たちが今回の開発で実装したチャットボットの力を借りることによって、AIと対話しながら自然に自己分析を深めることが可能になりました。この機能により、自己分析のハードルも大幅に下げることができました。
チャット内容の自動要約機能の実現
私たちはAIとの対話によって簡単に自己分析を深める機能を作ることができました。最終的には会話の内容を文章化することが必要です。しかし、自分ではゼロから書きたくありません。そのようなとき、私たちが開発した自動要約機能を使用することによって、会話の要約が自動的に生成され、対話を繰り返すだけでエッセンスを捉えた文章を作り出すことができるようになりました。
遭遇した課題とそれらの克服方法
私たちが遭遇した課題は次のとおりです。
- フロントエンドとバックエンドの通信
- 認証方式
- 本番環境の構築
フロントエンドとバックエンドの通信
フロントエンドとバックエンドの通信では数々の問題が発生しました。クロスオリジンで動作しているのでCORSの設定が必要だったり、Dockerのネットワーク設定に関する事柄だったり、セキュリティやそれに伴うnginxでのヘッダ周りに関する話だったり、CSRFトークンや認証の話だったり。私たちはそれを一つずつ解決して、最終的には正常に通信させることができました。この経験を経て、フロントエンドとバックエンドを分離して実装する上での開発上のポイントを押さえるとともに、非常に多くの学びを得ることができました。
認証方式
私たちは当初、JWT認証で認証周りを開発していました。その理由は、GoogleでDjango REST Frameworkで認証の実装方法を検索した際、JWT認証に関する記事が多かったからです。しかし、実際にフロントエンドとバックエンドをつなぐ作業を進める中で、JWT認証では問題が生じることに気づきました。その具体的な問題について、少しだけ詳しく説明します。(JWT認証についての説明は省略します。こちらのページが詳しいです。)
JWT認証はステートレスなトークン認証として知られており、サーバー側でアクセストークンを保持せずに認証が行えることから、セッション認証でも使用されることがあります。サーバー側でトークンを保持しないなんて、なんて魅力的なんでしょう!
しかし、JWT認証を実際にセッション認証で用いる上での大きなセキュリティ上の問題があります。それは、ユーザーがログアウトした際にアクセストークンの即時無効化ができないことです。
JWT認証によるアクセストークンは、一度発行されると有効期限が来るまでは無効化することができません。これは、ログアウト処理を行なったにも関わらず、そのアクセストークンが一定期間有効である事態を招く可能性があるということです。ですので、もし何らかの理由でアクセストークンが悪意ある第三者に漏れてしまった時、ログアウト後もそのトークンは第三者によって悪用される可能性があります。
この事態を解決するために、サーバー側でブラックリストを保持するという手が考えられますが、この方法ではJWT認証のステートレス性が失われてしまい、JWT認証を使う意味が薄れます。
また、クライアント側でトークンをlocalStorageに保存することによって、XSSによる攻撃の対象になるという議論があります。JWTをCookieで保持するという手もありますが、そこまでしてステートレスな認証にこだわる理由がありませんでした。
(JWT認証のセキュリティ上のリスクやその使用の是非について、PHP Conferenceで徳丸浩さんが詳しく説明しています。詳しくはこちら)
結局、発表まで残り1週間で期限が迫る中で、全てを書き換えてCookieを用いた伝統的なセッション認証方式に切り替えました。最終的に実現することはできましたが、知識不足から大きな手戻りが発生してしまいました。このように仕様変更をギリギリで行う形になりましたが、この経験を通じて認証方式についての知識を深め、失敗から学ぶことができたと思います。
本番環境の構築
私たちのチームにはAWS経験者がほとんどおらず、それにもかかわらず挑戦的な構成で構築を目指していました。しかし、いくらネットワークに強いとはいえ、こんなに重要な部分をHさん一人に任せていたことが3つ目の大きな失敗でした。未経験者揃いのインフラ構築では、一人に任せるのではなく全員で取り組むのが良かったと思います。また、インフラ担当だと本格的なコーディングからは外れてしまうので、やはりそういった意味でも良くなかったと思っています。結果として、私たちは目指していた構成で本番環境の構築をすることには至らず、発表時にはEC2に上げる形で対応しました。これもまた、次に活かすべき学びだったと思います。
想定外の結果や発見
フロントエンドとバックエンドに分離させて開発を行いましたが、結果的にはどちらの仕組みにもある程度関わることができたことで、両方の知識がついたことです。フロントエンド担当であれば、バックエンド側の認証の仕組みや通信の方法について深い学びを得ることができました。他方で、バックエンド担当はNext.jsのSSRの仕組みやfetchAPIの使い方などについて、学びを得ることができました。
フィードバックと反省
プロジェクトの成功と改善点
私たちのチームは企画メンバーと開発メンバーが力を合わせながら方向修正を行いつつ、最終的に目指していた企画案を実現することができました。それに加え、チャットボットとその会話内容の自動要約機能を実装することで、元々の企画以上のユーザー体験を実現することができました。
一方で、このプロジェクト全体を通しての大きな反省点として、開発のスケジュールに全く余裕がなかったことが挙げられます。開発を行なっていると自然発生的に仕様変更が生じることがあり、想像以上に余裕をもったスケジュールで開発を行う必要があることを痛感しました。そして、知識不足の面もありますが、もう少し詳細な事前設計ができていれば、余計な手戻りは避けられたなと思っています。これらの学びを通して、次のステップに進んでいきたいです。
チームの協力についての学び
開発プロセスでは、毎日の朝会でタスクの確認と進捗の共有を行うことで、メンバーがそれぞれ何をしているのか、見通しよく開発を進めることができました。また、Notionを用いたタスク管理についても非常に効果的でした。企画メンバーが単独で企画案を決めるのではなく、開発メンバーと協調しながら、企画として譲れないことを尊重しつつも実装難易度を踏まえた上で、お互いの妥協点を見つけることができました。企画メンバーの提案が難しかった場合でも、開発メンバーが代替案を提示して、お互いに力を合わせて完成させることができました。
エンジニアではない企画メンバーを交えながら、チーム開発を行う機会というのは今までありませんでした。ですので、学生時代に経験した開発とは異なった開発のやり方でサービス開発を行う良い経験になりました。私たちはこの経験を踏まえて、これからエン・ジャパンの業務に取り組んでいきたいです。
これからのビジョン
プロジェクトの今後の進展予定
プロジェクトとしてはこれで以上です。私たちはここから、それぞれの配属先のチームで開発業務に取り組んでいきます。
新たな目標やビジョン
開発の中でチャットボットや会話内容の自動要約機能を作成しましたが、開発を通して、やはりLLMの可能性はすごいと思いました。その大きな理由は、今まで学術界で積み上がってきた自然言語処理の技術がユーザーに身近なインターフェースとして提供されて、一気に日常生活に降りてきたことです。ChatGPTの出現やそのAPIの公開は、多くの企業やサービスでのチャットボットの実装を可能にし、多くの付加価値を生み出しています。私たちのチーム開発でもLangChainを活用し、この新しい流れに乗ってサービスを開発してみました。この開発を通して、うまく実装すればユーザーの利便性を大幅に向上させられるということを感じました。エン・ジャパンのサービスは自然言語処理との相性がとても高いので、最新技術を常に追いつつ、自社開発の強みを活かして、エンジニアが主体的にサービス開発を行えるように頑張りたいです。
終わりに
弊社にとって初めての新卒エンジニアということもあり、前例のない中でのチーム開発でした。全体の成果を振り返ると、うまく開発することができて良かったと思います。後輩たちがこれから研修を行なっていく際にも、私たちのやり方を参考にしながらさらに良い開発を行うことができたら、とても嬉しいです。