本記事は、22卒1年目の成長シリーズ3日目の記事です。
はじめに
AI事業本部協業メディアリテールディビジョンでバックエンドエンジニアをしている岩見彰太(@B_Sardine)です。
2022年にサイバーエージェントに入社、5月に協業メディアリテールディビジョンのアプリ運用センターに配属され業務を行ってきました。
この記事では、サイバーエージェントのDXという領域での新卒1年目の働き方について紹介します。
1年目の業務内容
入社後全体研修、その後AI事業本部に配属されてからは事業部での研修を行いました。特にAI事業本部の研修は、前半は毎年コンテンツの変わる座学、後半はチーム開発で広告配信サーバーを実装するなど充実した内容でとても学びの多いものでした。
その後、協業リテールメディアディビジョンのアプリ運用センターに配属されました。
協業リテールメディアディビジョンは主に協業先とともに事業をグロースすることを目的としており、その中でアプリ運用センターは小売業界におけるDX事業を行っています。
顧客管理基盤・アプリ運用基盤の新規開発
1年を通して、顧客管理基盤とアプリ運用基盤の開発を行ってきました。
顧客管理基盤(CRM:Customer Relationship Management)とは、その名のとおり顧客に関する情報の管理システムです。顧客の名前や電話番号などの情報、また購買履歴などの管理を行うシステムのことを指します。
アプリ運用基盤は、Webアプリ・ネイティブアプリ向けのコンテンツ提供などを指します。例えばクーポンの提供やニュース・コラム・チラシといったクリエイティブの表示などです。
自分が参画したプロジェクトは、数百万以上のユーザーを抱える大規模な小売事業でした。実店舗とECの両方が展開されており、さまざまなデバイスやプラットフォームに対応していたため、複数のフロントサイドとの連携が重要でした。
要件定義・詳細設計
プロジェクトは基本的にウォーターフォール型開発を行っていたため、最初に要件定義をがっつりを行いました。この時期はほとんどコードを書くことはなく、細かい要件のすり合わせを行っていました。想定されうるユースケースを洗い出し、どのように対応するべきか・実装すべきかの検討を行いました。
また、今回のサービスには弊社のサービスだけではなく、複数の社外のサービスとの連携も必要でした。そのため、各システム担当者とのミーティングなどで細かい実装のすり合わせや提供されているAPIドキュメントなどから要件の実現方法などを模索しました。
実装
自分は主に会員基盤周りとアプリケーション基盤、外部サービス連携の実装を担当しました。
技術スタックは以下です。
- 言語:Go
- サービスインフラ:Cloud Run
- DB:Cloud Spanner
- 通信:gRPC, REST
ここではその実装の際に直面した課題や苦労した点について少し触れます。
外部APIの自社サービスへの組み込み
この作業が最も時間がかかり苦労しました。特に、アプリケーションに対して外部APIから取得した情報と顧客管理基盤に連携された情報を組み合わせなければならない箇所が多数存在し、外部APIには変更を加えることができないため以下のような点に注意しながら調査・設計・実装を行いました。
- エンドポイントの叩き方、適切なパラメータの指定の仕方
- APIのマッピング
- 顧客管理基盤の情報との連携方法
- 外部サービスに負荷を与えすぎないようにするためのキャッシュ戦略
外部APIは必ずしも顧客管理基盤及びアプリケーション基盤に対して最適化されたAPIではないため、叩き方を工夫する必要があります。例として、要件上とある情報の一覧が必要であるが現状の外部APIの一覧取得のエンドポイントでは実現が不可能であり、どうしてもIDから情報を一件取得するAPI複数回叩かざるを得ない状況がありました。
今回の場合、この機能はアプリのコア機能でもあったためなんとか実現するために模索しました。結果的にデータ更新頻度がそこまで高くなく、変更の反映速度についてもあまり求められている機能ではなかったため、キャッシュと並行処理を導入した上で想定される負荷で試験を行い、なんとか要件と性能を担保することができました。
他にも、外部APIと顧客管理基盤の情報の連携をどうしても行うために、時には愚直に正規表現を書いてマッピングすることなどもありました。
また、別の例としては外部サービスが返すJSONレスポンスのフィールドが int, string の2パターンがあるケースが存在しました。この場合何も考えずに json.Unmarshal
をすると失敗するので、独自型を定義し UnmarshalJSON
を実装することで解決しました。以下は、stirngで来る場合は、intの0に強制する場合の実装例です。(補足:空文字の場合は、json.Number
は使用できませんでした)
type (
Response struct {
A string `json:"a"`
B int `json:"b"`
C customeType `json:"c"`
}
customeType int
)
func (c *customeType) UnmarshalJSON(b []byte) error {
var i int
var s string
if err := json.Unmarshal(b, &i); err != nil {
if err := json.Unmarshal(b, &s); err != nil {
return err
}
*c = 0
}
return nil
}
以上はほんの一例ですが、こういった泥臭い実装を地道に行っていくことで要件を徐々にクリアしていきました。
シナリオテストの導入
先述の通り多くの外部APIが存在すること、また今回開発した顧客管理基盤及びアプリケーション基盤の性質上ドメインロジックが複雑であり、いざAPIを実装してもその後の動作確認が非常に大変であるという問題がありました。例えば「特定の条件を満たした会員が行なった特定の購買に対してクーポンを発行し、そのクーポンが表示できる」というロジックが存在した場合、この動作確認をするには以下のような前準備が必要となります。
- 会員の作成(特定の条件を満たしていない場合に発行されないことを確認する用の会員も作成)
- 特定の購買を含む購買履歴の作成(発行されていないことを確認する用の特定の購買を含まない購買履歴も作成)
- 特定の購買に対して発行するクーポンの作成
このような複雑なシナリオがプロジェクト内に多く存在しました。APIを新しく作成または更新するたびに、検証用データを作成するSQLを実行し、そこに対して検証するといったことを手動で行なっていましたが、
- 数が多すぎる
- 検証用のSQLがgit管理されていない
- レビューなどで再現するには、レビュアーも同様の作業を愚直に実行するしかない
などから疲弊してしまっていました。そこで、Go製のシナリオテストツールである runn を導入しました。
runnの採用
runn を採用した理由は以下です。
- 活発に開発が行われている
- http, gRPCが対応しているだけでなく、DBに対するクエリの実行や Chrome DevTools Protocol を使用した E2E テストまで可能
- YAML で記述できる
- go test のヘルパーとしてシナリオを実行できる
特に、今回開発していたサービスでは都合上 gRPC-gateway による gRPC と REST の両対応をしていたこともあり、これらが両方使えることのメリットは大きいものでした。また、検証用データ作成用の SQL をシナリオの中で実行できるため、「検証用データを SQL で作成 → API 検証 → SQL でデータ掃除 」といった操作も可能になりました。
ただ、一点だけ問題がありました。それは runn が開発でDBとして使用していた Cloud Spanner に対応してなかったことです。開発当初 MySQL, PosrgreSQL, SQLite3 にしかクエリの実行は対応していませんでした。
今回、どうしてもプロダクトで使用してみたかったため、runn を Spanner に対応する PR を出し、無事対応させることができました。
導入は、Cloud Spanner の databese/sql 向けのdriver 2022年の秋ごろにGAしたこともありスムーズに行うことができました。
Cloud Spanner’s Go database/sql driver is now Generally Available | Google Cloud Blog
以上より、runn によって記述されたlocal検証用シナリオ例はこんな感じになります。
例として、ステータスコードのチェックしかしていませんが、レスポンスの中身の検証ももちろん可能です。
desc: create member
vars:
firebase_key: ${FIREBASE_API_KEY}
runners:
greq:
addr: localhost:8090
tls: false
db: spanner://test-project/test-instance/test-database
req: https://identitytoolkit.googleapis.com
steps:
create_firebase_user:
req:
/v1/accounts:signUp?key={{ vars.firebase_key }}:
post:
body:
application/json:
email: "test-user@example.com"
password: Paaaass@123
returnSecureToken: true
test: |
current.res.status == 200
create_user:
greq:
hoge.v1.HogeService/CreateMember:
headers:
Authorization: bearer {{ steps.create_firebase_user.res.body.idToken }}
message:
name: "test"
...
test: |
current.res.status == 0
get_member:
greq:
hoge.v1.HogeService/GetMember:
headers:
Authorization: bearer {{ steps.create_firebase_user.res.body.idToken }}
message:
test: |
current.res.status == 0
delete_firebase_user:
req:
/v1/accounts:delete?key={{ vars.firebase_key }}:
post:
body:
application/json:
idToken: "{{ steps.create_firebase_user.res.body.idToken }}"
test: |
current.res.status == 200
delete_member:
db:
query: |
DELETE FROM members WHERE id = '{{ steps.get_member.res.message.message.member.id }}';
このシナリオの例では以下の操作を行なっています。
- Firebase Auth REST APIを使用したユーザー作成と削除
- gRPCのエンドポイントを使用した会員の作成
- Spanner に対するクエリの実行
こららの内容をYAMLを書くことによって実行できるようになりました。これにより、複雑なユースケースにおける動作確認が再現可能になりました。またこれらを CI で各環境に対して実行することで、環境間の差異なども検証することができるようになりました。
プロジェクト業務外でやったこと
プロジェクト以外にも色々とやっていました。
Google Cloud の Professional Cloud Architectの資格を取得した
業務では主にGoogle Cloudを使用していましたが、より体系的な知識を身につけたいと思い取得しました。この資格はArchitectなだけあって、オンプレミス環境からの移行や大容量データの転送などが多く、昨今の初期段階からクライドネイティブな環境で開発を進めているようなプロジェクトではあまり遭遇しないような内容がやや多い印象でした。しかしDXという文脈ではこの辺りのユースケースはまだ多く存在するため非常に勉強になりました。また、適切なインフラの選定にあたって見逃しがちであった項目なども改めて復習することができました。
社内勉強会や横断施策、インターンの手伝い
サイバーエージェント内では日々様々な勉強会が開催されています。これは部署内に限らず全社的な勉強会ももちろん存在します。代表的なところだと、先日開催された CA.go などがあります。こういった勉強会にも積極的に参加して色々とキャッチアップしました。また、社内勉強会への登壇も行いました。ちょっとしたアドカレにも参加しました。
【Go】ジェネリクスと双方向マップを使った enum の実装
自社プロダクト開発とDX領域の開発における違い
自分は自社開発のプロダクトにも関わってきたことがあるため、その時の経験と比べて感じた違いについて述べます。
泥臭い実装が多い
先述した外部APIとの繋ぎ込みなどのように、外部サービスのAPI仕様書を読み込んだり、時にはリバースエンジニアリング的に解析するなどして、なんとかサービスで使えないかどうかを模索することが多かったです。しかしこれらを通して、今まで触れたことがなかった言語の深い仕様まで読み込んだり、サービスの組み合わせによって新しい価値を提供したりなど学ぶことも非常に多かったです。現在は2つ目の別プロジェクトにも参画していますが、やはりDXという領域ではこれらの実装は避けて通ることはできないということを実感しています。しかし逆にいうと、こういった普通なら匙を投げてしまうような領域にこそ、DXの価値というものが存在するのではないかと感じました。
スピード感のある開発が必要
昨今DXは様々な業界で叫ばれ、各社が必死になって取り組んでいます。このような状況下ではいかに効率良くスピード感を持って開発できるかどうかがビジネスインパクトに大きく影響してきます。また各クライアント企業が実現したい内容は、ある一定の領域までは実装したい機能などが似通っているということも分かりました。こうした実装をいかに早くかつ工数をかけずに行い、最終的に注力したいデータを活用したマーケティングや施策などに開発時間を使えるかどうかがDXにおいて重要だと感じました。
デザイナー・クライアントエンジニアがやりたいことを対話しながら汲み取ることが必要
デザイナーやクライアントサイドのエンジニアは、出来るだけユーザに近い目線でUIUXを良くするための模索しています。しかし、サーバーサイド側では外部連携やプロジェクトの都合上、どうしてもそれらが制約上実現できないことがありました。そういった場合、「理想状態を実現したいデザイナー・クライアント側 vs. 既存のサービスでは実現できないサーバー側」の対立が発生してしまうことがありました。普通の自社プロダクトであれば、サーバーサイド側での実装を頑張れば実現できるかもしれませんが、関係者が多くいる場合はそう簡単にはいきません。そういった場合、「UIUXを一定レベル担保するには、どのようなものを必要なのか」をより細かく詰めていく必要があると感じました。最もサービスとしていいのはデザイナー・クライアント側が実現したい理想状態であることは変わらないため、ここを分解していくことでどのようにすればサーバーサイドが実現することができるのかを考えました。この部分を詰めるにあたって対話は必要不可欠で、DX領域における一枚岩でのコミュニケーションの大切さを改めて痛感しました。
最後に
ここまで読んでいただきありがとうございます。
自分の好きな言葉に「枯れた技術の水平思考」というものがあります。
これは、任天堂でゲーム&ウォッチやゲームボーイなどの開発に携わり、十字キーの生みの親としても知られる 故・横井軍平 さんの有名な言葉です。
枯れた技術とは、最先端ではないもののすでに広く使われているような技術のことを指し、水平思考とはそれらを用いて全く新しい使い道や発想を考えることです。
ゲーム&ウオッチは、5年早く出そうと思ったら10万円の機械になっていた。量産効果でどんどん安くなって、3800円になった。それでヒットしたわけです。
これを、私は “枯れた技術の水平思考” と呼んでいます。
技術者というのは自分の技術をひけらかしたいものだから、最先端技術を使うということを夢に描いてしまい、売れない商品、高い商品ができてしまう。
値段が下がるまで、待つ。つまり、その技術が枯れるのを待つ。枯れた技術を水平に考えていく。
垂直に考えたら、電卓、電卓のまま終わってしまう。そこを水平に考えたら何ができるか。
そういう利用方法を考えれば、いろいろアイディアというものが出てくるのではないか。
横井軍平・著 『横井軍平ゲーム館』
これをDXという領域に当てはめると、既存のすでに使用されているようなサービスを組み合わせることによって、従来では実現できなかったような新たな価値を提供できるように思考することだと捉えました。今回のような例でいうと、プロジェクトに関係するすべてのサービスを提供したい価値に合わせてすべて再構築・再実装などするともちろん実現できることはたくさんあると思います。レガシーなサービスを全て捨てて、全てを最新技術で作り変えることが理想なのかもしません。しかし、それには費用もかかれば実装にも多大な時間がかかります。これら全て完了を待っていれば、それこそ変化の激しい昨今では乗り遅れてしまいます。
そこで、既存のサービスを組み合わせることによって、全く新しい価値をスピード感を持って提供することが必要であると思いますし、それこそが今求められているDXというものの価値なんだろうと僕は考えています。
2年目もスピード感を持ってこの領域に切り込んでいきたいと思います。
一緒に働くエンジニアを募集しています
25卒以降の学生を対象としたプレエントリー受付中。
選考情報などをいち早く受け取れます。
24卒も一部職種は現在も受け付けておりますので、こちらよりご確認ください。
また、社会人向け新卒採用制度「Re:Career」や、キャリア採用の求人一覧、カジュアル面談もございます。
インターンシップも!
サイバーエージェントでは、現在インターンシップの募集をしております。
どのインターンシップも共通している特徴は4つ!
- 社員が全力
- 社員との接点が多い
- 視座の高い仲間に出会える
- 交通費支給・宿泊場所手配
内容も数日で終わるものから2週間のもの、職種や難易度もさまざまなので、
自分に合うものを見つけてぜひエントリーをお待ちしております。