はじめに
初めまして!
神戸大学工学部3年の三村雄斗です。
9月5日から9月30日までの約1ヶ月間、CA Tech Jobという就業型のインターンシップに参加させてもらいました。
今回、私は Tapple のバックエンドエンジニアとして参加させてもらいました。
インターン期間を通じて大規模サービスならではの技術的な課題に挑戦して、その中でも特にマイクロサービス間に存在していた依存関係を排除する課題に取り組みました。
このブログでは私が本インターンシップに参加するのに至った経緯とインターンを通して経験したことや得た学びについてまとめていければと思います。
Tech Job参加までの経緯
私は今年の2〜3月にかけて行われたサイバーエージェントの1ヶ月の育成型のインターンである Go College に参加していました。
恥ずかしながらその時が私にとって初めての本格的なサーバーサイドの開発の経験でした。Go言語を用いたAPIの実装やCRUDなどの実装を通じて、やっとソフトウェアサービスのバックエンドとはどういうものなのか、RESTとはなんなのか、どんなコードが書かれているのかということを理解できました。
一方で、周りのインターン生のレベルの高さに衝撃を受け自分との差を鮮明に感じました。そこからAPI開発の勉強などを続けて知識や技術などを身につけました。その後、半年弱でご縁があって就業型のTech Jobに参加させていただくことになりました。
また、Go Collegeで出会ったメンターの方々や人事の方々の人柄に惹かれたことも今回のインターンに応募したきっかけでした。メンターの方々からそれぞれの会社の話を聞いているうちに、こんな環境でいろんなことにもっと挑戦したいと思ったため、夏休みの1ヶ月をサイバーエージェントで過ごすことを決意しました。
インターン期間
実際に私が体験したインターン期間の流れに沿って振り返っていきます。
インターンの初日からオンボーディングが始まり、オンボーディングが完了したタイミングで実際のタスクに取り掛かるという流れで進んでいきました。
オンボーディング(ハンズオン以外)
オンボーディングとしてTappleで開発を行う上で必要な知識をインプットしました。
およそ1.5営業日程度をかけて現在のTappleが抱えている技術的な課題やソフトウェアアーキテクチャなど開発を進める上で必要な知識をインプットしました。
オンボーディング(DDDハンズオン)
Tappleのサーバーサイドでは DDD × クリーンアーキテクチャ を採用しています。
これらの概念を理解するためにオンボーディング課題としてメッセージルームの設計から実装を行いました。実際のアプリケーションにも存在する機能を題材とした課題で、DDDの複雑な概念を理解するのには非常にいい設定だと感じました。
“`
DDD(ドメイン駆動開発)とはドメインや事業の複雑なルールなどの知識をソフトウェアのコードにそのまま落とし込む設計方法であり、開発者と業務の専門家がユビキタス言語で会話することで開発を進める手法です。
クリーンアーキテクチャとは、ソフトウェアの設計手法で、関心の分離を行うことでデータベースやフレームワークなどに依存しないシステムの設計手法です。
“`
ハンズオンのオンボーディングでは以下のような流れで進めました。
- Figmaによるドメインモデリング
- 実装
- チームレビュー
この過程を通して何人ものチームのエンジニアの方からレビューをいただき、私自身のアウトプットを振り返ることができたのがすごくありがたかったです。
Tappleのサーバーチームでは新規機能の実装の際などには日常的にドメインモデリングをFigmaで行っています。作成したFigmaを元にコードの実装を行うと言う手法をとっています。Job期間中にもオフィスのスタンディングスペースでFigmaで作成したモデリングをレビューし合っており、非常にいい雰囲気で設計を進め、設計の修正をできていると感じました。
今回のインターン参加前から少しDDDとクリーンアーキテクチャに関する知識とTypeScriptに関する知識があったのでスムーズにモデリングから実装まで進めることができました。
タスク
タスクの概要
インターン期間中に取り組んだタスクは大きく分けて以下の2つです。
- monolith-service通信排除による処理系の変更(メインタスク)
- ログ送信の実装
今回のブログではmonolith-service通信排除の課題に焦点をあてて書いていきます。
タスクの背景
Tappleのユーザー検索機能では将来的に BFF(Backend for Frontend)とsearch-serviceのみで完結する構成 を目指しています。これは検索に関わる処理をできるだけシンプルにして、サービス間の複雑さを取り除くことやコードの保守性を高める狙いがあります。また、通信を排除することでレイテンシーの向上も期待されています。
しかし、現状ではsearch-serviceのみでは必要な情報を全て返却することができないため、BFFからmonolith-serviceなどの他のサービスにリクエストを送信する実装を行うしかない状態でした。具体的には以下のような形で実装がなされていました。(今回のタスクに関連してくる部分のみを抽出してエンドポイント名などを省略して書き出しています。)
(主体ユーザー: 検索をリクエストした側のユーザー、客体ユーザー: 検索結果としてアプリケーションからレコメンドするユーザー)
このような構成では以下のような課題がありました。
- マイクロサービス間の依存が高まり、BFFの処理が複雑化してしまう
- 通信の回数が一回分増加することに起因するレイテンシーの悪化
そこで私に与えられたタスクは、BFFとmonolith-service間の通信を排除して、search-service単体のみで検索の処理が完結するように改善することでした。これは不要なサービス間の通信を削除し、理想的な処理系を実装するための改善の一端を担った形です。
タスクの具体的なステップ
上記タスクを達成するために以下のようなステップで進めました。
- 既存の処理フローの理解
- OpenSearchへのデータ追加
- 非同期データ基盤修正
- 既存の全ユーザーへのbackfill処理
- monolith-service通信排除
1. 処理フローの理解
まずは既存の処理のフローを整理しました。BFF → search-service → monolith-service におけるそれぞれの中身で行なっている処理を確認し、どのような条件でどのような情報を取得しているのかを確認しました。今回の目的はBFFの関数への入力と出力は変更せずに内容のみを変更する必要があったので、どうやったらmonolith-serviceとの通信を削除できるのかを考えました。
2. OpenSearchへのデータの集約
search-serviceから返却している全てのデータはOpenSearch(検索用DB)から取得しています。レコメンドするユーザーの位置情報(に関連する情報)を全てOpenSearchから取得する方針を採用しました。つまり今回はユーザーに紐づくユーザーの市町村情報をOpenSearchから取得することができればmonolith-serviceとの通信は排除できる状態になることが判明しました。
3. 非同期データ基盤
非同期データ基盤関係が概念の理解、実装まで難易度が高く最も苦戦した部分でした。
それぞれ詳細なアーキテクチャ図を示すことはできないのですが、TappleのバックエンドではMongoDB、 Snowflake、 Kafka、 OpenSearchなどのサービスを使用しています。それぞれでどのような処理を行って非同期的な中でデータの整合性を保っているか確認しながら進めました。
普段、小規模なwebアプリを実装している中では触れることができないような大規模サービスならではの概念などに触れて、その内部構造まで理解を深めることができました。
4. 既存の全ユーザーへの更新処理
これまでの実装でアプリケーション側から変更されたユーザーの情報はOpenSearchに入れ込むことができるような実装ができていました。つまり、アプリケーション側から変更されない大多数のユーザーに紐づく市区町村名の情報はOpenSearchには送信されることなく今までのアプリケーションとは異なる挙動をしてしまうためバッチ処理を行い、更新をする必要がありました。
今回はSnowflake上から手動でSQLのコマンドを実行して全てのアクティブユーザーへの処理を実行しました。なんとそのレコード数は百万単位のレコードでした!全てのアクティブユーザーに自分が作成されたクエリが実行された時はかなり緊張しましたが正確に実行ができたため良かったと感じています。
5. monolith-service通信排除
ここまでの実装によりBFFとmonolith-serviceの通信によって取得していた位置情報などがsearch-serviceから取得することができるようになりました。
最後に通信のリクエストを行っている部分のコードを削除して処理をレスポンスに合わせて実装し直すことでクライアントから見たエンドポイントの挙動は変わらない状態でmonolith-serviceとの通信を排除することができました。
結果としてsearchのエンドポイントにおいてBFFとmonolith-serviceの通信が一個分削除されて理想的なアーキテクチャに近づけることができました。
インターンを通して得た学び
幅広いインフラ技術
今回のインターンでは非常に広い技術領域に触れさせていただく経験ができました。
今まで経験してきた技術スタックとしてはRDB(Relational Database)で全てのデータを同期的に扱う開発にしか携わったことがありませんでした。
しかし今回のインターンではアプリケーションを使用しているユーザーには処理が正常に実行されたと表示したあとの非同期的なデータの基盤を使用して効率的な検索を実現し、適切な形にコードをリファクタリングすることができました。
DDD × クリーンアーキテクチャ × サーバーサイドTypeScript
Tappleで採用しているソフトウェアアーキテクチャのコードベースを読むことで、自分の中で少し理解できていたDDDやクリーンアーキテクチャに対する理解度を格段に向上させることができました。Expressのような非常に薄いフレームワークを使用していることで、柔軟な実装ができていると感じました。
私が普段Go言語を使用して開発をしているためオブジェクト指向言語の柔軟性だったり、TypeScriptの三項演算子などによる空の変数を作成しないコーディングなどができる点が非常に魅力的だと感じました。それぞれの言語にあるメリットデメリットを意識しながらコーディングができたことは良かったと感じました。
大規模だからこその工夫
大規模なアプリケーションだからこそ気をつけないといけないことも経験できました。Tappleでは幸せ退会という機能もあります。これはカップルとして成立したためアプリを退会することを示しています。先述の全ユーザーへのバッチ処理では非アクティブユーザーを除くことで実行対象のレコード数を格段に少なくすることができました。普段はあまりレコード数などを意識しないで実行していますが、このような経験は大規模なアプリだからこそ気をつけないといけない点だと感じました。
複数実装におけるリリース戦略
ここまで触れてきた通り、今回のインターンでは幅広いインフラサービスに触れながら実装を行ってきました。具体的には3つのGitHubリポジトリと3つのサービスのGUIに対して、dev, stg, prdの環境が用意されていました。そのためリポジトリの修正の順番は問題ないか、それぞれの環境への修正は反映できているのか、順番を誤って環境を壊さないかなど多くのことを考慮しながら作業を進める必要がありました。インターン生として参加する中でここまで多くのことを任せていただくことができるとは思っていませんでした。なかなか難しい部分もありましたがトレーナーの方のサポートやドキュメントに残して作業の進捗を管理することで抜け漏れなく作業を完了することができました。
最後まで気を抜かないこと
Tappleのサーバーサイドエンジニアチームでは毎週メトリクス確認を行っています。サーバーのレイテンシーや、CPUの使用率やエラー件数などをDatadog上で確認しています。最後の最後に気づいたのですが、今回の私の機能リリースに伴って500系のエラーが発生してしまいました。本来ならば機能リリース直後にDatadog上で確認するべきだったのですが、本番環境の対象画面での動作を確認して問題がないか確認するだけで終わらせてしまっていました。結果としてはリリースの順番を工夫したのですが、production環境へのリリースのタイムラグに起因してBFFが未定義の変数を参照してしまっていたことが原因でした。production環境では少し影響が発生してしまいました。このエラーを確認した時に発生原因が特定できるレベルにまでチーム内での情報の共有を進めていた点も良かったと感じています。
意思決定の難しさ
今回のタスクをやり抜く上で非常に多くのことを決断する必要がありました。
- コードレベルだとどのエンティティに情報を持たせるのか?
- どのレイヤーで計算のロジックを実装するか?
- このvalue-objectは全てのマイクロサービスから使用されるvalue-objectとして保持しても問題ないのか?
などなど多くの場面でそれが正しい実装なのか、トレードオフを意識しながら実装を行いました。その中で自分自身の意見を持って提案すること、相手に適切なコンテキストを与えて話し合いを進めることの重要性を感じました。
最後に
今回のインターンは1ヶ月という非常に短い期間の中で非常に多くの経験を得ることができました。今まで触れたことのなかった技術スタックに挑戦し、限られた期間内にタスクをやり切ることができて安心しています。また、サイバーエージェントの社内の雰囲気や他職種、他事業部の方といろいろなお話ができた点も本当にいい経験だったと感じています。
インターン生でここまで技術的な裁量の広さを与えてくださる会社はなかなかないかと思います。
Tappleのサーバーサイドチームの皆さんをはじめ、事業部全体の皆さんが暖かく迎え入れてくださったおかげで1ヶ月が非常に充実したものになりました。
これからもエンジニアとして成長していければと思います。
ありがとうございました。