TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

576

本記事は BASEアドベントカレンダー2024 の14日目の記事です。 はじめに BASE の Product Dev Division でエンジニアリングマネージャー(EM)をしている @tanden です。 Product Dev Division では、ここ 1 年ほどサービスレベルマネジメントの取り組みを有志メンバーを中心としたチームで進めてきました。この記事では、サービスレベルマネジメントの取り組みの一貫として行ったアラート品質改善についてまとめています。 SLI/SLO設定のその前に サービスレベルマネジメント(SLM)というと SLI/SLO について最初に思い浮かぶ方も多いのではないでしょうか。ただ、SLI/SLO の設定にトライされたことがある方はご理解いただけると思いますが、組織として納得感を持ってかつ効果的な形で設定するのは非常に難しい作業になります。また一度設定して終わりではなく、設定した SLI/SLO を見直しながらサービスの信頼性を継続的に高めていく取り組みが求められるので、長期目線で継続できるような体制を作っていく必要があります。 一方で、長期戦となる SLM の取り組みを進めるうえで、短期的な成果を上げることも重要です。初期の成果はチームに勢いをもたらすだけでなく、周囲の信頼を得るきっかけにもなります。逆に成果の見えにくい状態が続くと、チームのモチベーションも低下してしまい、最終的には取り組みが形骸化するリスクもあります。 そこで、今回の SLM の取り組みでは、最初の成果として開発組織の困り事として挙がることが多かった「アラート品質の向上」に狙いを定めることにしました。アラート自体はサービスレベルマネジメントのコアとなるようなプラクティスではありません。ですが、例えば適切なアラート通知によってエラーバジェットの枯渇を未然に防ぐなど、SLO を達成するための重要なツールの 1 つとして位置づけられるはずです。 アラートの品質管理については、New Relic のドキュメントやブログでもアラートクオリティマネジメント(AQM)として紹介されています。ぜひ一度ご覧ください。 docs.newrelic.com 課題と取り組み ヒアリングや自身の開発体験をもとに課題の整理をまずは行いました。その結果、アラートにおける課題を以下の 3 つに絞り込むことができました。 どのアラート通知チャンネルを見るべきかわからない アラートが多すぎてどれが重要かわからない アラートが出たとしても何をしていいのかわからない 全ての課題に共通して言えるのは「アラートが発生しても、次のアクションに素早くつなげにくい」ということです。そこでこれらの課題を反転させるために、以下のコンセプトをもとにアラートの品質改善を進めることにしました。 「アラートが通知されたときにすぐに気づいて次のアクションを起こせるか」 以下でそれぞれの課題と解決に向けての具体の取り組みについてまとめていきます。 どのアラート通知チャンネルを見るべきかわからない システムアラートに迅速に気づけるよう、Slack などのチャットツールに通知する設定をしていることが多いと思います。 BASE でも Slack にアラートを通知しています。しかし、これまでの運用の中で過去の組織体制に合わせて作成されたものや開発プロジェクト単位で作られたもの、さらには内容の重複するチャンネルなどが乱立した状態でした。そのため、「どのチャンネルを見ればよいのか」がとても分かりにくい状態に陥っていました。 この課題を解決するため、まずはチャンネルの役割を整理し、以下に挙げる命名規則の策定を含めた再編を進めました。 チャンネルのprefix, suffix チャンネルの検索性と視認性を向上させるため、prefix と suffix の命名規則を定めました。これにより、Slack 上で関連チャンネルがまとまって表示されるようになりました。 命名規則 役割 チャンネル名の prefix notif- チャンネル名の suffix 環境を表す -prd, -stg, -dev 緊急度でチャンネルを分ける 通知の緊急度に応じてチャンネルを分けることで、即時対応が必要なアラートに集中しやすい構成としました。分類の基準は syslog の重大度レベルを参考にしています。 チャンネルの prefix 役割 notif-alert- 即時対応が必要なもの notif-warn- 即時対応は不要だが念の為通知しておきたいもの notif-info- プログラムの動作確認やデバッグのための情報を通知する(利用後の通知削除を推奨) サービスを重要度で分ける サービスの重要度は、「BASE」の主要機能(決済やショップ管理画面)と社内向けの管理画面ではやはり異なります。そこで、サービスごとの重要度を Tier1 から Tier3 に分類し、重要なサービスの異常検知の確度を高められるようにしました。 重要度 チャンネル作成方針 Tier1 サービス毎に alert レベルのチャンネルを作り通知する。warn, info レベルではチャンネルをまとめる。 Tier2 alert レベルのチャンネルをまとめる。warn, info レベルのチャンネルは必要に応じて作成する。 Tier3 alert レベルのチャンネルをまとめる。warn, info レベルのチャンネルは必要に応じて作成する。 監視ツール毎に通知チャンネルを分けない BASE のアプリケーション開発チームでは、監視ツールとして主に Sentry と New Relic を利用しています。監視ツールごとに個別のチャンネルを設けると、確認すべきチャンネルが増え過ぎてしまい、重要な通知を見落とすリスクが高まります。そのため、全ての監視ツールからの通知を単一のチャンネルに集約し、通知の確認漏れを防ぐ工夫をしています。 再編の結果 これらの整理により、プロダクション環境向けの通知チャンネルを 21 から 8 に削減できました。チャンネル数はまだ減らせる余地がありつつも、アラートの確認先が明確になったことで、エンジニアの認知負荷を軽減できています。 通知が多すぎてどれが重要なアラートかわからない チャンネルの役割や命名規則を整えたことでチャンネルを集約し見るべきチャンネルがわかりやすくなりました。その次に取り組んだのが、アラート数の削減です。チャンネルを集約したことも要因の 1 つですが、それ以前からアラートの数自体がそもそも多く、どれが重要なアラートかわからない状態が続いていました。 といってもアラート通知数を減らすには根本解決をするか、アラート通知設定を見直し通知されないようにするかしかありません。そこで以下の順番で取り組みを進めました。 エラーを一気に減らした 週 1 回 1 時間の定例でアラート設定の見直しとエラー解消を進めた エラーを一気に減らした 下の画像は、Tier1 に分類される重要サービスの Sentry のイシューの数を示しています。970 件ものイシューが積み重なっていて、チャンネルへのアラート通知も 1 日あたり 5〜15 件と頻発していました。さらに、同じエラーが繰り返し通知されることも多く、新規の重要アラートの識別が困難な状況でした。 この課題に対し、チームの有志メンバーが約 1 ヶ月間で 100 件近くのプルリクエストを作成し、エラーの根本的な解決とアラート設定の見直しを集中的に行いました。その結果、蓄積されていた Sentry の Issue を 970 件→0 件に削減できました。これにより、日々のアラート通知数も 1 日 2-3 件程度(ときには 0 件)に落ち着いています(進めてくれたメンバーの方には感謝しかありません)。このようにノイズが減ったことで、アラート検知をきっかけに緊急性が高い不具合を見つけることにもつながっています。 週1回1時間の定例でアラート設定の見直しとエラー解消を進めた アラートを一気に減らすことができたため、次のステップとして日々新たに発生するアラートへの対応プロセスを確立させる必要がありました。緊急性の高いアラートについては発生時点で即座に確認・判断されるものの、緊急性が低いアラートはどうしても放置されがちです。これらは時間とともに蓄積され、さらに再発を繰り返すことでチャンネルのノイズとなってしまいます。 そこで取り組み始めたのが、週 1 回 1 時間のアラートの棚卸し会です。棚卸し会では Sentry や New Relic で未対応となっているアラートを確認し、解決方針を定めて開発チームのバックログに登録しています。また時には、この会の中でエラーの修正まで行う場合もあります。棚卸し会の取り組みを始めて約半年ほど経過しましたが、平均して週に 1-2 件のペースでエラーとアラート通知の削減を実現できています。 アラートの定期的な棚卸しについては以下のブログを参考にしました。ありがとうございました。 zenn.dev 通知を受けたとしても何をしていいのかわからない 取り組みの最後は、アラート対応の効率化を目的とした 対応手順書(runbook) の整備です。 せっかくアラートを設定しているのですが、その意図や対応方法が不明確だったり、時間の経過で設定者も内容を忘れ迅速な対応が難しいケースも見られました。 そこで、アラート設定者含む誰もが素早く状況を把握し対応できるように、runbookの作成と運用を始めました。特に New Relic のアラートには runbook のリンクを登録できる機能があり、アラートから直接 runbook にアクセスできて便利です。 runbook には以下の項目を最低限含めるようにしています。 アラートが発生したらまず最初にやること 詳しい調査手順 調査後の対応手順 困ったときにの連絡先 過去の対応履歴(Slack リンクなど) ちなみに、runbook は GitHub に専用のリポジトリを作りその中に置いています。Notion などのドキュメント管理ツールよりも GitHub はリプレイスされることを想像しにくく、相対的に URL の寿命が長いと判断したためです。 まとめ BASE のプロダクト開発チームでは、アラート品質を向上させるために以下の取り組みを 1 年かけて行ってきました。 アラート通知チャンネルの役割整理と統廃合 アラート通知数の削減 runbook の作成 通知チャンネルの再編などはわかりやすい形で改善できた部分もあり、チームに勢いをつけるという意味でも一定の成果を出せたのではないかと考えています。 一方でアラート削減の取り組みについては改善が進んでいるのは一部のサービスに限られるなど、全体の進捗としては 4 合目くらいの感覚です。 まだまだ道半ばですがこれからも泥臭くアラート品質の向上に取り組んでいくことで、サービスの信頼性の向上につなげていけたらと考えています。 おわりに BASE におけるサービスレベルマネジメントの取り組みは始まったばかりです。来年度も継続的に取り組んでいく予定です。 なお、BASE では現在エンジニアリングマネージャーの採用も積極的に行っております。エンジニアリングマネージャーとして一緒に頭を悩ませながらプロダクトを成長させていく仲間を募集しています。もし興味をもっていただいた方はぜひ 1 度カジュアルにお話させてください。 A-1.BASE_エンジニアリングマネージャー / BASE株式会社 最後まで読んでいただきありがとうございました。 明日は @miyachin_87 さんの記事です。お楽しみに〜
アバター
この記事は BASE アドベントカレンダー 14日目の記事です。 はじめに BASEのProduct Divにてバックエンドエンジニアをしている オリバ です。 当該記事では、所属しているチームメンバーで「ドメイン駆動設計をはじめよう」の輪読会を実施したので、印象に残った内容やチーム内で議論したことを紹介しようと思います。 (画像は https://www.oreilly.co.jp/books/9784814400737/ より引用) 背景と目的 2024年10月29日、私の所属するチームは「かんたん発送(日本郵便連携)App」(以下、かんたん発送App)をリリースしました。(※ かんたん発送Appとは ) 現在BASEでは、新規Appの開発プロジェクトにおいてDDD(ドメイン駆動設計)を推奨しており、かんたん発送Appもこのアプローチを採用しています。 DDDを採用するにあたって、チームメンバーでDDDの知見があるエンジニアがほとんどいなかったため(かくいう私も半年ぐらい)、「ドメイン駆動設計をはじめよう」でDDDに関してインプットをし、開発プロジェクトにアウトプットしていく目的で、この本の輪読会を実施することになりました。 事業活動を分析する DDDを取り入れるべきか判断するため、事業活動の分析の重要性を説いており、3つの重要なワードが出てきます。 1. 中核の業務領域 競合他社との差別化を図る事業戦略の核心で、業務ロジックが複雑なものです。ここを外部に委託するのは、賢い選択ではありません。 2. 一般的な業務領域 競合他社と同じやり方で課題を解決する領域で、競争優位を生み出さないため、外部サービスを積極的に使います。業務ロジックの複雑さは小さいです。 3. 補完的な業務領域 中核の業務領域を補完する業務領域で、業務ロジックの複雑さは小さいです。一般的な業務領域との違いは、自社で開発するか外部サービスを使用するかどうかです。 事業分析で大事な視点は、競合他社との優位性と業務ロジックの複雑性です。 そこで、輪読会でかんたん発送Appがどの業務領域にあたるか話し合いました。 BASEでは、複数の機能をAppsという形で提供しており、かんたん発送Appを使用することで、日本郵便を使用した配送を行う際に、送り状の宛名書きが不要で、ショップオーナー様の発送時の負担を軽減することができ、かつ配送料金も全国一律利用しやすい価格帯となっております。 かんたん発送Appは、競合他社との優位性という観点では中核の業務領域に位置づけられます。しかし、業務ロジックの複雑性については、一部に複雑なロジックは含まれるものの、他の機能と比較すると比較的シンプルな構造となっています。そのため、中核業務とも補完業務とも位置づけらるという結論に至りました。 業務ロジックの複雑性は、他機能との比較やエンジニアの経験値によるものでもあるので、判断するのは難しいと感じました。 業務知識を発見し、事業の複雑さに立ち向かう 同じ言葉 ソフトウェア技術者がソフトウェアを設計、組み立てるために、「同じ言葉」を用いて意図の伝達と知識の共有を行います。 ソフトウェアが対象としている業務を表現するときに、プロジェクトメンバー全員が共通して使用する言葉を指します。本書のxxiページの訳語表を見ると、同じ言葉は従来の訳語で「ユビキタス言語」と呼ばれています。 かんたん発送Appでも同じ言葉を定義しドキュメントを用意しておりましたが、あまり利用されず最終的には形骸化されてしまいました。 原因としては、以下の2つが考えれられます。 同じ言葉は、事業活動を表現すると同時に、業務エキスパート(業務領域に最も詳しい人でドメインエキスパートともいう)の考えや発想を表現する際に使うものですが、かんたん発送Appでは業務エキスパートが不在でした。 業務エキスパートが不在だったので、ドメインモデリングをやる際も、エンジニア内で閉じてしまいました。 区切られた文脈 業務エキスパートによって業務内容の捉え方が異なる場合があり、同じ言葉を区切られた文脈で分解する必要があります。 ECサイトの例で考えると、エンジニアが販売部とやり取りする際に使用する「商品」という言葉と、配送部とやり取りする際に使用する「商品」という言葉が、それぞれ異なる意味を持っている場合、コミュニケーションの中で誤解が生じる可能性があります。販売部では「商品」が売値や在庫を指すのに対し、配送部では配送先や配送状況を指すことがあります。このように関係者ごとに異なる意味の「商品」を扱う必要があり、意味のズレが生じやすくなります。 そこで「区切られた文脈」を定義し、それぞれの文脈内で「商品」という言葉を統一することで、誤解を防ぎます。本書の xxi ページの訳語表を見ると、「区切られた文脈」は従来の訳語で「境界づけられたコンテキスト」と呼ばれています。 ※「区切られた文脈」の詳細については、 こちらの記事 をご参照ください。 本書では「業務領域は発見で、区切られた文脈は設計」と記されています。これは非常に納得感のある表現です。区切られた文脈はアーキテクチャと密接に関連しており、例えば以下のように設計判断に結びつきます。 モジュラーモノリス を採用している場合:区切られた文脈ごとにモジュールを分割 マイクロサービス を採用している場合:区切られた文脈ごとにサービスを分割 業務ロジックを実装する 業務ロジックを実装する方法として以下の3つがあります。 1. トランザクションスクリプト トランザクションスクリプトは、データ操作の手続きを手順に沿って順次処理するスクリプトとして記述する設計手法です。主にデータのETLなど、業務ロジックが比較的単純で手続き的な操作を伴う処理に適しています。このアプローチは、業務ロジックが複雑でない場合に有効で、補完的な業務領域で使用されるケースが多いといえます。 この設計手法をイメージする例として、私は真っ先にAWS Lambdaを使用したコードが思い浮かびました。イベントハンドラーから入力値を受け取り、それを順次処理し、出力するという流れが、トランザクションスクリプトに非常に近い印象を受けます。(もちろん、AWS Lambdaを使用してオブジェクト指向設計やDDDを実現することも可能ですが、ここでは単純なトランザクションスクリプトのイメージとして取り上げています) 2. アクティブレコード アクティブレコードは、トランザクションスクリプトと同様に単純な業務ロジックを実装する方法ですが、より複雑なデータ構造を扱える点が特徴です。この設計手法では、ORM(Object-Relational Mapping)を使用してデータベースとオブジェクトを直接対応させ、データの操作を効率的に行います。 例えば、Ruby on RailsのActive RecordやLaravelのEloquentなど、多くのWebフレームワークが標準機能としてアクティブレコードの仕組みを提供しています。これらを活用することで、開発者はデータベース操作を簡素化しつつ、業務ロジックを実装することが可能です。 BASEでは、社内向けにデータを管理するためのAdmin機能を提供しており、これらの業務領域ではアクティブレコードが利用されています。具体的には、Admin機能はCakePHPで構築されており、シンプルな補完業務領域に適した設計となっています。 3. ドメインモデル ドメインモデルは複雑なロジックを実装する手段で、必要な部品は以下の3つです。 値オブジェクト 集約 業務サービス かんたん発送Appは、業務領域が配送業務なのでBASEにおける中核業務に近く、またBASEの配送領域が今後「区切られた文脈」として新たに切り出される予定という背景から、ドメインモデルを採用しました。 また、チームでは集約の境界線の設定が難しいという話がよく上がっていました。本書によると、境界線の基準は「一貫性の強制」にあり、集約の状態変更は集約内部の業務ロジックのみが行えるとされています。そのため、複数の集約をまたぐトランザクションは実行できないことになります。 たとえば、バッチ処理では、複数の集約をまたいで1つのトランザクション内で状態を変更することがあります。結果整合性のトランザクションであれば、ドメインイベントを使用して集約外部に変更を伝播できます。しかし、強整合性が必要で集約をまたぐ場合は集約の設計自体を見直す必要があり、これは非常に難しい課題だと感じました。 設計を改善する 事業は常に進化していくため、一般業務が中核業務へ、または補完業務が中核業務へと変化することがあります。もちろん、その逆のパターンも起こり得ます。 アクティブレコードで実装されていた業務ロジックを、ドメインモデルに変化する場合どのように行えば良いか以下の手順が示されています。 値オブジェクトを見つける 値オブジェクトに関連する業務ロジックを見つけ、値オブジェクトのメソッドに移す トランザクションの境界(集約)を探す 状態を変更するメソッドを明確にするために、setterはprivateにする 集約ルートとなるオブジェクト(エンティティ)を探す 興味深いのは、エンティティを探すのが最後のステップとなっている点です。新規のドメインモデリングでは通常、集約ルートを先に決めてトップダウン式で進めますが、設計変更の場合は上記の手順に従って、まず値オブジェクトを特定し、最後に集約ルートを探すようボトムアップ式で進めていきます。 集約ルートを決めることは、外部に公開する唯一のエントリーポイントを決めることを意味します。そのため、まず値オブジェクトを見つけていき、最後に集約ルートを決めるというボトムアップ式が採用されているのだと推察します。 イベント駆動型アーキテクチャ イベント駆動型アーキテクチャとは、システムのコンポーネント間でイベントメッセージを非同期でやり取りする技術方式です。 イベントには「イベント」(すでに起こった変化)と「コマンド」(これから実行すべき操作)の2種類があります。BASEでは主にイベントを使用しており、ドメインの状態変化やユースケースの実行時にイベントを発行します。 第6章で述べられているように、ドメインイベントは実際に発生した出来事を表現するため、その名前は必ず過去形で記述します。この原則を学んだことで、チーム内でもドメインイベントの命名は必ず過去形にするという意識が定着しました。 また、15章p.279 イベント通知にて、「イベント通知の内容は必要最低限です。イベントの購読者にイベントの発生を伝えることが唯一の目的です。イベントに反応するために必要な全ての情報をイベント通知に含めてはいけません。」という記述があります。この理由として、以下の2つが上げられています。 保護すべき情報を、メッセージ通信基盤に漏洩させないため メッセージが届いた時には、その内容が最新では無くなっている可能性がある 例えば、イベントでエンティティの状態を渡す際、エンティティ全体を渡すべきか、IDのみを渡すべきかについてチーム内で議論が生まれました。本書のルールに従うと、解決策は明確です。最新の状態が必要な場合はエンティティIDを渡し、受信側で最新のエンティティを取得すれば良く、特定時点の状態(スナップショット)が必要な場合はエンティティ全体を渡せば良いという結論に至りました。 おわりに 本書は、前半でDDDの戦略的な内容を扱い、後半で戦術的な内容を詳しく解説しており、非常に充実した内容でした。また、業務領域(中核、一般、補完)を常に意識するようになり、良い意識改革となりました。このタイミングで輪読会を実施できて本当に良かったです。 明日のBASEアドベントカレンダーは @miyachin の記事です、お楽しみに! また、BASE では Web アプリケーションエンジニアを積極採用しています。興味持っていただいたら、ぜひご応募ください! 採用情報 | BASE, Inc. - BASE, Inc.
アバター
本記事は BASEアドベントカレンダー2024 の13日目の記事です。 はじめに BASE株式会社のなかの Pay ID という事業チームでエンジニアリングマネージャーをしている北川です。 Pay ID では、BASE で作られたショップでのより良いお買い物体験を提供するため、ショッピングアプリ「Pay ID」やあと払い決済「Pay ID あと払い」といったプロダクトを開発しています。 本日公開のPay IDテックリードの記事もぜひご覧ください。 devblog.thebase.in 今年もこれまでより良いプロダクトをユーザーに届けるべく、リリースを積み重ねてきましたが、とりわけモバイルアプリのリリースにおける自動化の取り組みについて紹介したいと思います。 1. リリース用の一時チャンネルを作ってもらう 現在 Pay IDアプリでは、明確なルールはないものの2週間に1度程度の頻度でリリースをしています。 リリースの際にはリリース時に必要な作業(動作確認やストア申請)の状況共有やチーム内外のコミュニケーションのためにそのリリース専用の一時的なチャンネルを作成しています。 その際に Slack のワークフローを使って、リリースのチャンネルを作るようにしています。 実際には Slack ワークフローで完結しておらず、Slack から Zapier に送信し、Zapier 経由でチャンネルを作成しています。 Zapier 経由で実行しているのは、ワークフロー作成当時には単独でチャンネルを作成することが難しかったことと、チャンネル作成と同じタイミングでリリース用の GitHub Pull Request を作成しているためです。 2. リリース用のPull Requestを作ってもらう Pay IDアプリでは iOS・Android ともに CI には Circle CI をメインで利用していて、補助的に一部 GitHub Actions も併用しています。 iOS と Android で多少の違いはありますが基本的には素朴な GitFlow をベースにしており、何かしらのフィーチャをリリースする際は開発ブランチからリリースブランチを作成し、リリースブランチからメインブランチと開発ブランチに向けて Pull Request を作成します。 チャンネルを作成するタイミングでリリース用の PR も作って欲しいため、 Zapier 経由で Circle CIのワークフローをキックしています。 Zapier には Webhooks by zapier という機能が用意されており、オーソドックスなWeb API リクエストを実行することができます。 これを使って、 Circle CI の pipeline API に POST リクエストを送信しワークフローを開始します。 Circle CI の設定例としては、以下のように pipeline API にリクエストされたパラメータを取得して各ジョブに渡すようにすることで、任意のバージョンのリリース PR を作成することができます。 prepare_release : # API経由で実行する場合にだけ workflow_dispatch パラメータを付与するようにする when : << pipeline.parameters.workflow_dispatch >> jobs : - make_release_branch : version_name : << pipeline.parameters.release_version_name >> filters : branches : only : - development 3. テスト用のアプリを配布してもらう Pay IDアプリでは社内向けの配布に DeployGate を利用しています。 配布操作に関するAPIが提供されており、 fastlane 向けの action や Android 向けの gradle プラグインも用意されているため、リリースのワークフローへの組み込みも容易です。 リリース時にしていることとしては、以下の2つです。 リリースブランチに push された場合にアプリを配布する 任意のタイミングでアプリを配布する 前者については、配布ページという特定のバージョンのアプリがインストール可能な配布URLを作ることができる機能があり、「今回のリリース用の配布ページ」を作ることでリリース前の動作確認などに活用しています。 後者は先の例と同じように Circle CI のAPIを利用し、CI の中で DeployGate にアップロードしています(Android の例)。 Circle CI の中で渡されたパラメータを組み合わせてテストアプリをアップロードします。 deploygate : description : "DeployGateにアップロードする" parameters : deployment : type : string default : "Production" deployment_note : type : string default : "" steps : - attach_workspace : at : ~/project - run : *install_keystore - run : name : DeployGateへのアップロード command : | DEPLOYGATE_MESSAGE="<<parameters.deployment_note>> $COMMIT_MESSAGE / $(echo $CIRCLE_SHA1 | cut -c -7)" \ ./gradlew uploadDeployGateAab<<parameters.deployment>> 4. Androidで段階リリース中の場合は教えてもらう こちらはまだ試験導入のステータスのものです。 Pay ID Androidアプリでは、Google Play Storeの段階的な公開機能を利用して、公開直後のアプリに問題が発覚した場合に公開ステータスを変更できるよう運用しています。 具体的には、99.9999% 状態で公開することで実質全ユーザーを対象にアップデートを公開することができます。Google Play Storeの現在の仕様として、公開の割合が 100% ではない段階的な公開の場合は、公開を停止することができます。もし問題が発覚し公開を停止した場合、現在完全に公開されているバージョンが最新となるため、新たに問題のあるバージョンをインストールするユーザーを抑制することが可能です。 この運用をする場合、次のリリースをする際には現在公開しているバージョンを99.9999%から100%にする必要があるのですが、その確認がメンバーの努力によるところとなっていました。 上記の問題に直面していた際、同じ課題への対処をされている下記の記事を見つけ感銘を受けました。 https://hack.nikkei.com/blog/app_release_status_bot/ この記事を参考に、アプリをGoogle Play StoreにアップロードするためのCIで公開ステータスを確認するようにし、まだ段階リリース中のものがあれば検知できるようにすることで仕組み化することができました。 今回の場合は CI のなかに組み込みたかったので、 GAS ではなく fastlane 経由で Google Play Publisher API を利用するようにしました。 元々、 fastlane には supply という Play Store へアップロードしたり、track の情報を参照するためのクライアントは存在するのですが、リリースステータスを参照するというピンポイントの action は用意されていません。そこで module を拡張しリリースとそのステータスを取得するようにしています。 まずは以下のように supply module を拡張します。 module Supply class Reader def track_release_statuses track = Supply .config[ :track ] client.begin_edit( package_name : Supply .config[ :package_name ]) # retrieve the track name, status, and fraction release_statuses = client.track_releases(track).map do |release| { name : release.name, status : release.status, fraction : release.user_fraction } end client.abort_current_edit if release_statuses.empty? UI .important( " No release found in track ' #{ track } ' " ) else UI .success( " Found ' #{ release_statuses.join( ' , ' ) } ' release statuses in track ' #{ track } ' " ) end release_statuses end end end 次に新規の action を定義します。ここでは仮に GooglePlayTrackReleaseStatusesAction と名づけ、 fastlane/actions/google_play_track_release_statuses.rb に配置します。 module Fastlane module Actions class GooglePlayTrackReleaseStatusesAction < Action # Supply::Options.available_options keys that apply to this action. OPTIONS = [ :package_name , :track , :key , :issuer , :json_key , :json_key_data , :root_url , :timeout ] def self . run (params) require ' supply ' require ' supply/options ' require ' supply/reader ' Supply .config = params release_statuses = Supply :: Reader .new.track_release_statuses || [] return release_statuses.compact end def self . available_options require ' supply ' require ' supply/options ' Supply :: Options .available_options.select do |option| OPTIONS .include?(option.key) end end def self . is_supported? (platform) platform == :android end # そのほかドキュメントなどは割愛 end end end あとはこれを Fastfile から呼び出すだけで、100%に満たないリリースが存在するときに CI で検知することができるようになります。 desc " Check release statuses " lane :check_statuses do |params| release_statuses = google_play_track_release_statuses( track : ' production ' , ) # 一つでもリリースが完了していない場合は失敗とする if release_statuses.any? { |status| status[ :status ] != ' completed ' } UI .user_error!( ' リリースが完了していないトラックがあります ' ) end end 5. リリースから数日後にクラッシュレポートのリマインドをしてもらう これはただのリマインダーの設定ですが、リリース直後には問題として発生しなくとも、リリース後数日にクラッシュなどが増えている場合もあるため導入しています。 iOS・Android ともにアプリを公開するとアプリストアからメールが届くため、そのメールを起点に Zapier 経由でリマインダーを Slack に投稿します。 ただしこの仕組みは App Store Connect や Google Play Console の仕様変更によって比較的壊れやすいので注意が必要です。 まとめ Pay IDアプリでは Zapier や Circle CI などを組み合わせて、アプリのリリース時のオペレーションやその後の確認などを自動化しています。 これらは日々の課題や気づきから少しづつ改良を加えているもので、今後もより良い仕組みに改善しつつユーザーに価値を届けていきたいと思います。 最後に、Pay ID では一緒にプロダクトを作るメンバーを募集しています。 興味をお持ちいただけた方はぜひこちらの採用情報をご確認ください。 募集一覧 / BASE株式会社 明日は tanden さんと oliver さんの記事になります。お楽しみに。
アバター
本記事は BASEアドベントカレンダー2024 の13日目の記事です。 はじめに Pay IDのテックリードの大木( @roothybrid7 )です。 9/30に、PAY株式会社(以下PAY社)が保有していた「Pay ID」のシステム移管を行い、リニューアルしました。 カートやショップ管理画面、Pay IDアプリとは別の独立したシステムとなっており、アプリケーションの技術選定やアプリケーションアーキテクチャの設計に携わりましたので、その辺の話をしたいと思います。 移管の背景 2021年に 購入者向けショッピングサービスとして「Pay ID」の提供を開始 しましたが、サービスの運用は引き継いだ一方で、決済機能は、PAY社がシステムの開発・運用管理を行うという、長らく分断された状態にありました。 また、クレジットカード会員情報を扱っていることから、移管するにしても、どのデータを移管すればいいか精査しないといけないという課題がありました。 詳細は省きますが、クレジットカードと会員の持つログイン・住所情報を分離することにより、その課題を解決できそうだということと、人員確保できそうという目処が立ったため、移管を決行することとなりました。 技術選定について システム移管にあたり、元の技術スタックをそのまま踏襲する必要はなかったため、改めてそれを考えることになりました。 まず、プログラミング言語としては、 go を採用することにしました。採用理由としては以下の通りです。 BASE BANKに運用ノウハウあり 静的型付けの安心感と文法のシンプルさ gofmt等開発者体験を向上させるツールが揃っている View周りをフロントエンドに寄せ、APIサーバとしてだけ実装 移管のフロントエンドに関する内容は、 システムリニューアルでNext.jsのApp Router/Server Actionを使って便利だと思ったところ - BASEプロダクトチームブログ をご覧ください。 goを使ったアプリケーション開発は初めてではないですが、触っていたのは go1.7 の頃であり、module管理がサポートされたり、ジェネリクスがサポートされたり、goの進化を感じました。 次に、システムに関して、機能としては大きくアカウント管理、クレジットカード情報を扱うためにPAY.JP APIを利用したプロキシとしての機能があります。さらに、Pay IDを利用するクライアントの種類が多いため、認証方式や利用するデータの違いで、それぞれのAPIエンドポイントを実装しなければなりません。 また、トラフィック量にも違いがあり、例えば、カートでは決済する際にアカウントやカード情報を毎回参照するので多いとか、それ以外だと、ログインする時やアカウント編集する時にしかアクセスしないとかあるため、それらを考慮してAPIサーバをそれぞれ用意しました。 APIサーバを分けたとしても、認証方式が異なるだけで、結局全く同じ処理フローを辿ることもあります。また、購入者のための処理と社内管理業務と異なる処理であっても、アカウントの設定変更や退会処理など、同じ機能を利用するといったこともあります。 そのため、コード再利用可能なように、処理単位を適切に分けたり、レイヤー化したりということを意識しました。 これを実現するための技術スタックは以下の通りです。 Go AWS(ECS/Fargate, ALB, CloudFront, WAF, RDS, ElastiCache, SES) oapi-codegen(OpenAPI) gqlgen(GraphQL) SQLBoiler(ORM) Uber Fx(DI) chi(Router) oso(RBAC, ACL) 今回は、PoC(Proof of Value、価値実証)やPoV(Proof of Concept、概念実証)のような実証実験フェーズではなくシステム移管なため、すでに運用されているシステムを、互換性をできる限り保ちつつ取捨選択しながら実装する必要があります。 様々な議論があるとは思いますが、こういったある程度の規模のアプリケーションのコードベースを整理した状態とするには、レイヤ化されたアーキテクチャの導入は必要かなと個人的には思います。 今回、アプリケーションアーキテクチャとしては、クリーンアーキテクチャをベースに採用しています。 クリーンアーキテクチャ クリーンアーキテクチャは、 Clean Coder Blog - The Clean Architecture や 一般的な Web アプリケーションアーキテクチャ - クリーン アーキテクチャ を参考にしていただくとして、主に、ビジネスロジックやモデルを中心に配置し、特定のフレームワークやデータベース、外部APIに依存させないようにインタフェースを定義し、それを通じて利用できるようにします。フレームワークを利用したインタフェースの実装は、以下のInterface Adaptersと呼ばれる層に配置します。それにより単体テストのモックを実装することもできますし、ORM(Object-Relational Mapping)を別のものに差し替えることも可能となります。 さらに、重要なのが図の右下で、Interface AdaptersとUse Casesレイヤー間の通信制御フローを示していますが、依存性逆転の原則により、抽象は詳細に依存してはならない。詳細が抽象に依存すべきであるというところを表現しています。 The Clean Code Blog より引用 今回は、APIサーバ複数実装する必要があるが、アカウント登録等の多くのユースケースのフローに差異はなく同じアプリケーションロジックが利用できます。そのため、図にあるところの ControllerやPresenterの実装は、APIサーバ毎に必要だが、Use Caseはひとつでよく、それはこの図のルールに準拠すれば実現できます。 依存関係を水平方向のレイヤー図で表すと以下のようになります。 以下のInterfaceの例だと、InputPortに適応するユースケース処理を実現するアプリケーションロジックを実装し、OutputPortに適応するPresenterを実装します。OutputPortの実装詳細は、APIサーバ毎に別の実装になります。 type InputPort[I any, O any] interface { Exec(ctx context.Context, input I, outputPort O) error } type OutputPort[T any] interface { Write(ctx context.Context, value T) error } また、OutputPortは値を戻さず、 ヘキサゴナルアーキテクチャ が推奨するポートアダプター方式を利用しています。ポートで write() が実行されたら、実装であるアダプターでその出力を何度でも受け取ることができます。エラーが発生しなければ、outputPortの出力データの受け取り手であるPresenterが、データを合成変換し、oapi-codegen等利用しているフレームワークに合う形のデータ構造に変換します。 func Interactor(ctx context.Context, input Input, outputPort OutputPort[Result]) error { account := AccountFromContext(ctx) if account == nil { return outputPort.Write(ctx, Result{Notice: "signupRequired" } } outputPort.Write(ctx, Result{Account: convertPublicFields(account)}) token, err := IssueToken(ctx, input) if err != nil { return err } return outputPort.Write(ctx, Result{Token: token}) } ちなみに、これは購入者向けのAPIの話で、社内業務での利用を目的としたadmin-apiは、ユースケースが異なるのと、 GraphQL を採用しており、そのフレームワークを最大限に活用しているため、これには準拠していません。リリースによる障害が発生したとしてもビジネス的な損失はあまりないことと、柔軟に要件に対応したり、短時間で実装しリリースしたいという目的のためです。 ただ、Entities層にあるコアロジックは、特定のフレームワークには依存しておらず、再利用可能なように定義しているため、例えば、アカウント情報の更新や退会処理などは共有することができます。 Uber Fx 依存関係逆転の原則を実現するために、DIコンテナの Uber Fx を利用しました。 ドキュメントがわかりやすく、依存関係が不足している場合、ログに出力してくれるためかなり使いやすかったです。 実装をインタフェースとして提供する際、 As() を利用して明示的にインタフェースを指定する必要があります。主に関数型の実装を追加した時、インタフェースとして提供できてないことがよくありました。 fx.Annotate( PasswordUpdaterFunc, fx.As( new (PasswordUpdater)), ) また、 As() の逆の役割をする From() というものもあり、これはInterfaceに実装を注入する際に利用します。とある実装で、database/sqlのDBと単体テストで利用するgo-txdbを両方受けとり可能にするため、共通のインタフェースを定義した際に利用することができました。 type Executor interface { Exec(query string , args ... interface {}) (sql.Result, error ) //[...snip...] } fx.Annotate( func (executor Executor) AFunc { return func (ctx context.Context, id ID, opts ...Option) error { //[...snip...] }, }, fx.ParamTags( `name:"ro"` ), fx.From( new (*sql.DB)), fx.As( new (A)), ) 変わったところで言うと、ドメインイベントとイベントハンドラーの登録でも利用しました。 var EventModule = fx.Module( "Event" , fx.Provide( fx.Annotate( func () []event.Event { events := make ([]event.Event, 0 , len (event.EventAllTypes)) for _, e := range event.EventAllTypes { events = append (events, e) } return events }, fx.ResultTags( `group:"listenableEvent,flatten"` ), ), fx.Annotate( eventhandler.MakeActivityRecorder, fx.ResultTags( `name:"recordActivity"` ), fx.As( new (event.Handler)), ), fx.Annotate( eventhandler.NewVerificationMailHandler, fx.ResultTags( `name:"verificationMail"` ), fx.As( new (event.Handler)), ), ), fx.Decorate( fx.Annotate( func (events []event.Event) []event.Event { return event.FilterEvents(events, func (event event.Event) bool { switch event.Name() { case "account.created" , "account.recovery" : return true default : return false } }) }, fx.ParamTags( `group:"listenableEvent"` ), fx.ResultTags( `group:"verificationMail"` ), ), ), fx.Invoke( asSubscribeHandler( "recordActivity" , "listenableEvent" ), asSubscribeHandler( "verificationMail" , "verificationMail" ), ), ) OpenAPI APIは複数あるという話をしましたが、実際どのような項目をAPIでやりとりしているかは、移管元のアプリケーションと利用側のアプリケーションのコードを全て確認し、OpenAPIのドキュメントとして記載していきました。また、利用側で全く使われていない項目は、この際まとめて削除していくことも忘れずにしていきました。 さらに、 Redocly CLI を利用し、社内ネットワークにドキュメントを公開し、いつでも閲覧できるようにもしました。 Goサーバのコードは、このOpenAPIのスキーマをもとに、 oapi-codegen を使って生成しています。 ORM ORMは、DB Schemaに併せてコード生成できるSQLBoilerを利用しています。 コード生成されているため、エディタでコード補完も効きますし、Eager Loadingやクエリビルダーのような機能もあります。 一点利用していて難しかったのは、LEFT JOINした結果をbindするstructの定義ですね。 タグでテーブル名を指定する必要がありましたし、コード生成されたstructを再利用しようとするとうまく利用できず、自前で定義しなければならなかったり、nullになりうる方は、ポインタで指定しなければいけませんでした。 type JoinedStruct struct { Account Account `boil:"accounts,bind"` Extra *ExtraAttr `boil:"extra_attributes,bind"` } ただ、前述の通り、クリーンアーキテクチャを採用しており、アプリケーションロジックではSQLBoilerが生成したモデルを直接使っていないため、データベースの操作をする場合、Entitiesのモデル等と相互変換しています。自動で更新すべきカラムを推測できないため、モデル上で何らかの変更を行った際、ドメインイベントを発行し、どのカラムを更新すべきか明示的にWhitelistで指定しています。 ちなみに、全ての更新処理をEntitiesのモデルを通して行っているわけではなく、単に外部APIから取得したレスポンスをDBに同期する場合は、アプリケーションロジックとは関係なく、Interface Adapter層内部での処理のため直接importする形にしています。 同様に、クライアントが要求するデータを単に返すだけの場合も、Entitiesのモデルに変換する意味はないため、ユースケースに特化したデータ構造に変換して返しています。 ※現在、SQLBoilerはメンテナンスモードで、今後新機能が追加されることはないため、別のものに移行した方が良さそうです。クリーンアーキテクチャだと、Interface Adapter層を書き直す必要はありますが、他の層は特に変更することなく移行できます。 ドメインイベントを利用した実装 メインロジックの処理フローが正常に完了したとして、メール送信や行動履歴等、副作用的な処理が必要になることがあります。 そのために利用したものが、ドメインイベントです。 ドメイン イベント: 設計と実装 - .NET から引用 この仕組みを使えば、トランザクションをコミット後、ドメインイベントを送信することで、イベントを購読していた各イベントハンドラーがそれぞれの副作用を実行することが可能です。 例えば、アカウント登録が成功した後、認証メールを送信したり、行動履歴を記録したりといったことができます。 その他 実装は、なるべくAPIの互換性を保つように努力しましたが、全てのデータやロジックを移管できないというところと、goでは通常レスポンスにnullを含めることができないということで、クライアント側の実装も変更せざるを得なかったため、追加でその実装も併せて行う必要がありました。他チームとの実装方針の調整等も併せて1ヶ月弱は追加でかかってしまい、リリースを延期せざるを得ない状況ともなりました。 また、開発しやすいように本体とは別のアプリケーションだったり、ライブラリを作成したりもしました。 OAuth認可フローを試せるミニWebアプリ Pay IDアプリのアカウント編集をWebアプリに統合するための認証クライアントライブラリ( Kotlin Multiplatform project ) おわりに プロジェクトで使われている技術スタックやアプリケーションアーキテクチャについて、簡単に一部ご紹介させていただきました。 システム移管は、互換性をできるだけ壊さないようにする必要があり、新規サービス立ち上げとはまた違った難しさがあり、当初想定してなかった課題が増えたりして、なかなかリリースまで漕ぎ着けられないもどかしさもありました。 Pay IDでは、今後もプロダクトの改善、技術的改善も積極的に行っていきます。現在エンジニアを募集しているので、興味のある方はぜひ採用情報などもご覧ください! binc.jp 明日は、oliverさんとtandenさんの記事です。お楽しみに〜
アバター
はじめに この記事は BASE アドベントカレンダー12日目の記事です。 こんにちは! Pay ID Design Grp Managerの @naomikun です。 今日は、通常の「業務内容」や「メンバーの役割」といった形式的なチーム紹介とは一味違う、 新規事業部メンバーの作業環境 に焦点を当てて、一部のメンバーをご紹介したいと思います。 作業環境を通して、メンバーそれぞれの個性や働き方がうかがえるので、皆さんの作業環境とこだわりポイントをぜひご覧ください。 まずは、私の所属するDesign Teamからご紹介していきます。 Design Team Pay IDのデザインチームは、アプリ開発から決済領域、マーケティング向けクリエイティブ作成まで、Pay IDの幅広い施策に日々取り組んでいます。 デザイン業務の大半はモニター前での作業。そんな普段の作業環境をのぞいてみましょう。 M.Tさん Pay IDのショッピング領域に関わっています。 デスクはシンプルだけど、ちょっと楽しい雰囲気にしたく好きなアイテムを置いています。壁の手書き風カレンダーは平山昌尚さんの作品でとても気に入っています。 机のお寿司のぬいぐるみは ジェリーキャット さんの商品で、Pay IDアプリで購入しました!仕事中に癒されます… N.K 次は私のデスクです。主に決済領域の開発に携わっています。 ラバーウッドの色味に合うようにレイアウトしています。数年前に購入したカワグチタクヤさんのドローイングがお気に入りで飾っているのと、仕事中はずっといい匂いがしていてほしいのでリードディフューザーを置いてます! Product Management Team PMチームは、新規機能の立案、検索アルゴリズムの改善、パーソナライズされたレコメンド機能の向上、そして決済体験の設計を担当しています。これらの業務を様々なステークホルダーと密接に連携しながら推進しているプロダクト開発の中心的チームです。 N.Mさん Pay IDのショッピング領域のPdMとして参画しています。 デスクはFlexiSpotのE8という昇降デスクを数年前に購入しました。リモートワーク日の朝はまだ体が起きてないので、よく立ち姿勢から仕事を始めたりしています。 モニターの後ろ側にあるライトセーバーみたいな間接照明は数千円で買ってみたのですが、一気にデスクのそれっぽさが増したのでおすすめです! B.Oさん Pay IDのショッピング領域のPdMとして参画しています。 主にオフィスで作業をしており、PCを常設しています。シンプルで必要最小限の環境ながら、詞華集が置かれているのが特徴的です。 K.Tさん Pay IDのPdM Mgrをしています。 デスクには様々な種類の観葉植物を配置しており、緑に囲まれた環境で仕事をすることで心穏やかに働けると考えています。 とても落ち着いた気持ちで仕事ができています。 M.Gさん 執行役員 VP of Productとして、プロダクトの責任者として従事⁠しています。 オフィスではいろんなキャラクターの視線を感じることで、自制心を保ちながら働いています。 Development Team Pay ID Development Teamは、アプリ開発、決済サービス領域の開発、および運用を担当しています。 iOSエンジニア、Androidエンジニア、バックエンドエンジニア、フロントエンドエンジニアの総勢約20名で構成されています。各メンバーが専門領域で素案を作成し、チームで議論しながら仕様や設計を決定していく方式を採用しています。 ユーザーからのお問い合わせやシステム不具合対応なども日々行っており、Pay IDの要となるチームです。 H.Mさん 機械学習エンジニアとして、Pay IDアプリの商品レコメンドシステムの開発を担当しています。 個人的こだわりポイントはKeychronの分割キーボードとDELLのモニターです!分割キーボードは肩こり予防力の高さと見た目がカッコいい点、DELLのモニターはUSB Type-C一本でMacBookの画面を映しつつ充電ができ、4Kで作業領域が広く取れる点がお気に入りです。ちなみに観葉植物は宣伝部長(1万円/月までPayIDアプリでの購入を補助するBASEの福利厚生)で買ったもので、常に視界に入って癒し効果を発揮しています。( こちらのお店 で買ったもので、現在は売り切れていました。) T.Tさん Webカートやショッピング関連のバックエンド/フロントエンド開発に携わっています。 集中力を保つ為に時間を意識せずに感じられるようにするのがこだわりです! 時計 はちょうどディスプレイの上部くらいに見えるようにして、手元には タイマー を置いてポモドーロタイマーとして使ったり1時間タイムアタックしたりしてます。 Y.Fさん Pay IDのインフラエンジニアとして働いています。 右側の Google Nest Hub に自分が競馬場で撮った写真をスライドショーさせてます。好きな写真が流れてくるとめっちゃ気分が上がっていい感じです。 T.Nさん フロントエンドエンジニアとして働いています。 画面の広さは正義だと思ってます。エディタ、ブラウザ、Figma、メモ帳、Slackなどを十分な大きさで同時に表示できるので、「あのウィンドウどこいったっけ?」という悩みが減って、とても快適に仕事ができてます。 E.Nさん 主にショッピング領域でバックエンドエンジニアをしています。 作業中は音楽を聴くのでオーディオセットは必需品です! モニターは4Kディスプレイとゲーミングモニターを使用していて、仕事も趣味も全部このデスクで完結できるようにしています! R.Oさん Pay IDの開発チームのMgrを担当しています。 人間工学に配慮した環境を整えるため、エクステンションデスク、モニターライト、エルゴノミクスマウス、CO2計測器を設置しています。また、アイデアの整理には紙のほうが効果的なので、自由帳は必需品です。 Business Team Pay ID Businessチームは、決済インフラの新たな可能性を追求しています。BASEの加盟店さまにより便利で安全な決済体験を提供するため、決済システムの運用・改善に日々取り組んでいます。 K.Tさん Businessチームに所属し、事業計画の策定と管理や、キャンペーン施策の企画・運営を行っています。 iPadでラジオを流しながら仕事をしています。思考の整理のために書き込むことが多いので、ホワイトボード型のノートを手の届く場所に置いています。 また、昇降デスクを使用しているので、時々立ちながら仕事をしています。 まとめ:作業環境から見えるチームの個性と魅力 普段目にする機会の少ない作業環境をご紹介することで、新規事業部のメンバー一人ひとりの個性や魅力を感じ取っていただけたのではないでしょうか。 メンバーそれぞれが持つ独自の視点や専門性が光り、個人的にもとても興味深くて楽しい記事になりました。 最後に、私たちPay IDでは共にプロダクトを開発していく仲間を募集しています! バックエンドエンジニア A-1.Pay ID_バックエンドエンジニア / BASE株式会社 UIUXデザイナー B-5.Pay ID_UI/UXデザイナー / BASE株式会社 プロダクトマネジメント/toCプロダクト B-5.Pay ID_プロダクトマネジメント/toCプロダクト / BASE株式会社 明日は、同じPay IDのメンバーの Ohki さんと、Kitagawaさんの記事が公開予定です。 お楽しみに!
アバター
本記事は BASEアドベントカレンダー2024 の12日目の記事です。 はじめに BASE BANK Division(以下、BANKチーム)でプロダクトマネージャーをしている 齋藤 です。 今年リリースしたPAY.JP YELL BANKを担当しています。 pay.jp これまでのキャリアの大半がデータを利活用する業務が多かったのですが、今年から金融サービスのプロダクトマネージャーにキャリアチェンジした経緯をお話ししたいと思います。データ分析人材のキャリアパスの参考として読んでいただけると嬉しいです。 これまでデータ関連の仕事をしてよかったこと 一概にデータにまつわるお仕事といっても機械学習エンジニアやデータアナリスト、データエンジニアなど様々な職種があります。私の場合、幸いにも濃淡はあれどそれぞれの分野で一通りの業務に携わることができました。 社内で進行している様々なプロジェクトに対してエンジニアリングで課題解決することもあれば、データ分析の観点からコンサルティング的な立ち回りをすることも多く、非常にやりがいのある仕事でした。また、BASEには様々なサービスやプロダクトがあることも多様な職種とのコラボレーションを通じて、各サービスの成功は何であるかを各場面で認識することができました。 結果として、様々な視点を持てることで自分が携わっているBASEの強さや役割を強く認識することができ、単純にスキルの幅を広げられただけでなく、仕事に対するモチベーションを維持することに繋がりました。 データアナリストからプロダクトマネージャーへ 職種的に社内横断的に活動することが多かったのと、かつ、求められる動きとして既に顕在化している課題を対処するというアプローチを取ることが多かった印象があります。スポットで小さな成果を出しつつも、携わったプロジェクトが長期的に成功させられたのかという実感を持ちづらかったという反省がありました。 また、今後のキャリアとしても長期的な目線でプロダクトを成長に繋げたという成果を持っておきたかったので、社内公募制度を利用し、新規事業であるBASE BANKの専業データアナリストとして社内異動しました。 異動後はデータアナリストとして動きつつ、時にはエンジニアリングをすることもあれど、課題発見から価値創造までを責任を持って活動できるようになってよかったと思っています。 ただ、一般的にデータアナリストの視点から見出される事業の問題点の多くは、純粋なデータ分析だけでは解決が困難なケースが多いと思います。施策を実行に移す際には様々なステークホルダーとの協働が必要となり、優先順位の判断もデータの観点だけでは不十分な場面が数多くあるのがデータアナリストの困難さだと思っています。 私のデータアナリストとしての動きとしては、基本的にPdMや事業責任者と並走することが多く、経営者や事業責任者と同じ目線で物事を見なくてはいけない場面も多く、自然と事業課題にフォーカスした動きをとりたいという意欲が湧いてきました。結果、プロダクトに関わることができるプロダクトマネージャーへのキャリアチェンジを決意しました。 データ分析出身者であることの強み プロダクトマネジャーとして、以下のようなデータ分析スキルを直接活用できています 意思決定に必要なデータの自由な取得・加工 必要なデータが存在しない場合のログ・データセット設計と整備 データの発生過程を踏まえた、確度の高いであろう統計的意思決定 これらのスキルを活かし、社内のBIツールであるLookerの管理も担当しています。 特に金融サービスにおいては、与信モデルがサービスの根幹にあたるものですが、特性や算出過程を技術的に理解できることで、ブラックボックス化せずに判断ができる状態を維持しています。 ただ一方で、新規事業という性質上、全てを定量的に評価することは現実的ではありません。今後は、定性的な洞察とのバランスを取りながら、データドリブンな意思決定の文化を組織に根付かせ、より価値の高いプロダクトの構築を目指していきたいと考えています。 データ分析出身者であることの弱み 前提として、そもそもプロダクトマネージャーとして求められるスキルセットが幅広いことは周知の事実としてあります。最初から全てをカバーするのは無理だと思ったので、自分が対応しないと仕事が回らない領域である各ステークホルダーとの折衝に注力していました。幸いにもシステム開発やデザイン、マーケティングについてはその道に長けたチームメンバーに恵まれており、それぞれの意思決定や提案を尊重しつつ、これまでサービス拡充を続けられました。 また、新規事業であるが故に、あらゆる方向での解像度が低いのが課題だと思っています。最近では、事業数字やユーザー属性について私がデータ分析した結果を共有する時間を取るようにすることで、各数字に対する解像度を上げる企画を実施していたりしてます。 その他、チームでデザインスプリント( 社内での過去事例 )を実施したり、競合他社の動向やサービスを深掘りする仕組みが運用がされ始め、チームとしての目線感が揃いつつある実感を得られています。 プロダクトマネージャーが一人で抱え込まずに、それぞれのチームメンバーの目線でサービス改善の意見をもらえる環境があるので非常に助けられています。 おわりに 私以外にも、データアナリストからプロダクトマネージャーへとキャリアを選択する方が増えていると感じています。ただし、これは全てのデータアナリストが目指すべき道筋というわけではありません。 実際、プロダクトの意思決定者がデータを細部まで把握し管理することは、労力的にもスキル的にも現実的ではないと思います。むしろ、データの専門性を極めることで組織の意思決定を下支えする参謀的な役割を担うというキャリアパスも、同様に価値のある選択肢だと考えています。 一方で、データを起点とした価値創造に関心があり、より事業やプロダクトの中核に近い立場で意思決定に関わりたいと考えるデータアナリストにとって、プロダクトマネージャーへのキャリアチェンジは、挑戦する価値のある選択肢の一つとなると思い、自分のキャリア選択について紹介させていただきました。 明日は、大木さんとOliverさんの記事となります。お楽しみに! BASE、BASE BANKでは一緒に働く仲間を募集しています! ぜひ採用情報をご覧ください! binc.jp
アバター
本記事は BASEアドベントカレンダー2024 の11日目の記事です。 はじめに はじめましての人ははじめまして、こんにちは!BASE BANK Division(以下、BANKチーム)のフロントエンドエンジニアのがっちゃん(  @gatchan0807  )です。 今回は私が担当しているPAY.JP YELL BANKというサービスについてお話しします! エンジニアとしてこのプロダクトを担当していて感じるおもしろいところがたくさんあるので、ぜひ一緒に開発しましょ!!というお誘い記事でもあります!! 全部読んでいただけるととっても嬉しいですが、熱がこもりすぎてとても長い記事になってしまいましたので、記事下部にある、まとめだけを読んでいただく形でもよいです! よろしくお願いします! この記事を読む上でのキーワード: エンベデッドファイナンス PoCフェーズ〜グロースフェーズ 分析 / フルサイクルエンジニア 機械学習 FinTech PAY.JP YELL BANKについて まずはPAY.JP YELL BANKについて軽くご紹介させてください。 サービスサイト: https://pay.jp/yellbank 2024年6月に正式公開された際のプレスリリース: https://prtimes.jp/main/html/rd/p/000000224.000030814.html PAY.JP YELL BANK自体は「将来債権ファクタリング」という仕組みの金融サービスになります。もともとネットショップ作成サービスBASE(以下、BASE)向けに同様のサービスとして提供していた「YELL BANK」が元になっており、オンライン決済サービスPAY.JPを利用しているユーザー(以下、PAY.JP加盟店)に将来の売り上げを「ファクタリング」というスキームですぐに使えるお金に代えるサービスになります。 [補足]YELL BANKのサービスサイト: https://yellbank-lp.thebase.com/ 上記YELL BANKのサービスサイトから引用 BANKチーム、PAY.JP YELL BANKの今後について BANKチームは元々、BASEのショップオーナーの金融課題を解決する機能を提供するチームとして立ち上がり、BASE向けYELL BANKを軸に、BASE Card、お急ぎ振り込み機能と、ショップオーナーの金融課題を解決するための機能を拡充する形で展開してきました。 これは今後も変わらずに進めていく予定で、実際に2024年も請求書カード払いの提供開始など引き続きBASEのショップオーナーの金融課題を解決するために新領域にも展開をしていきます。 BASE BANKチームの紹介資料から引用 (引用元: BASE株式会社 BASE BANKチーム紹介資料 - Speaker Deck ) そして、中長期目線では、YELL BANK機能の展開を皮切りにBANKチームで開発・運用している各機能の提供プラットフォームを拡大して、より広いユーザーに価値を提供できるようにしようとしています。 元々、BANKチームが開発・運用しているYELL BANKをはじめとした各機能は、 エンベデッドファイナンス(組み込み型金融)と呼ばれるサービス形態 のものです。 これは「金融以外のサービス提供企業(非金融企業)が、既存サービスに金融サービスを組み込んで金融サービスを提供する」という形のサービス提供方法に名前がついたもので、「ネットショップ作成サービスBASE(非金融サービス)と将来債権ファクタリングのYELL BANK」のような関係のことを言います。 詳しくは こちらの解説ページ や、「 エンベデッド・ファイナンスの衝撃―すべての企業は金融サービス企業になる 」という書籍に譲ります。 これらの金融関連の各機能をBASE以外のプラットフォームに提供を始めることで、BANKチームが関わるプロダクト自体はよりエンベデッドファイナンスっぽい形になりはじめてきています。 その第1弾として、BASEグループのPAY株式会社が提供している「オンライン決済サービスPAY.JP」向けに提供を始めた「PAY.JP YELL BANK」はBANKチームで2018年から開発・運用してきた「YELL BANK」のBASE以外のプラットフォームへの展開というところで、上述の付加価値拡大の方針の試金石にもなるプロダクトになっています。 PAY.JP YELL BANKの技術構成について PoC期間から現在までは、以下ような形の技術構成になっています。 バックエンドはGoで書かれたPAY.JP加盟店ごとの債権管理などのシステム全体の処理の責務を担うAPIと、Pythonで書かれた機械学習を使ったPAY.JP加盟店ごとの売上予測を提供する責務を担うAPIの2つをAWS ECS Fargate上に置き、フロントエンドはNext.jsをAWS Amplifyで動かしてページをiframeでPAY.JPの画面に埋め込むようにしています。 これは、YELL BANKのシステムアーキテクチャを参考に実装しており、再利用できる部分を再利用してアジリティ高くPAY.JP YELL BANKの機能提供を開始できるように実装してきました。 また、フロントエンドはPoC段階の機能の提供速度を早めるためにBANKチーム管轄でNext.jsアプリケーションを作成し、それをPAY.JPの管理画面に埋め込んでもらう形をとりました。 技術構成のこれから その後、PoC期間が終わり、6月に正式提供が開始されてから約5ヶ月が経った今は、スピード重視で作ってきた技術構成を中長期的に運用がしやすく、さらにグロースするために必要な機能を追加しやすいように一部変えようとしているところです。 フロントエンドはiframeで画面丸ごと埋め込む形をやめ、PAY.JPの管理画面に直接機能を実装して、Next.jsのフロントエンド・BFFからではなくPAY.JPのバックエンドからGoのAPIを利用する形に変えていこうとし始めています。 これは、PAY.JP加盟店ユーザーの動線上に適切に表示し、よりシームレスな資金調達体験を作ることと、その先の調達した資金を使った加盟店成長に繋げてもらうためのアップデートです。 ただ、PAY.JPはクレジットカード番号を直接取り扱うプロダクトであるため、開発するメンバーは PCI DSS 準拠のリリースフロー・開発ルールに則って実装を進める必要がありました。 そのためのBANKチームメンバーのPCI DSSを理解度を上げるトレーニングや事前準備を進めたりしてきたので、満を持してPAY.JPへの直接実装をしていくぞ!というタイミングが今だったりします。 他にも、グロースさせていくフェーズに入ったため、新たに必要になった機能を開発する上での各バックエンドAPI改修、分析基盤との連動のためのバッチの追加などを今後も進めていく予定です。 PAY.JP YELL BANKの開発のおもしろいところ さて、そんなPAY.JP YELL BANKを開発する上での特徴であり、個人的に担当していてとてもおもしろいと感じていることを皆さまにも少し知ってもらえればと思います。 大きく分けて、3つあります BASE / YELL BANKのリソースを活用した開発スピードの速さ・手数の多さ エンベデッドファイナンスという少し特殊なプロダクトであること 金融のドメイン知識と機械学習・銀行APIなどのテクノロジーを組み合わせて課題解決していること BASE / YELL BANKのリソースを活用した開発スピードの速さ・手数の多さ 「リソースを活用」と書くと、いささかタダ乗りしているようなニュアンスを含んでしまいますが、どちらかというと 伴走してくれるチームやプロダクトがとても近いところにいて、一緒に開発できている状態で、それがとても価値がある ものだと思っています。 より具体的には、先人の知見をベースに再設計したり検討した上でPAY.JP YELL BANKに取り込んだり、新しい機能を作る際には調査を共に進めて分担したり、そういった知の高速道路に乗りながらBANKチーム・BASEグループ全体で向かうべき未来を目指して開発をしている点です。 また、後述のエンベデッドファイナンスというプロダクトである特徴の話にも重なりますが、BASE・PAY.JPという既存プラットフォームがあり、そこに異なる角度からの新しい機能を追加するという経験はプロダクトを完全にゼロから模索するよりも、ある程度ターゲットが絞れている分、高速に開発を始めることができます。 さらに、既存プラットフォームで得た資金を投資することで、よりダイナミックに機能を作ることが出来るところも魅力です。 他にも、 普段の開発を組織規模やフェーズが先に進んでいるプロダクトと深く関わり、同じ会社内で同じ方向を向いて働くのはとても楽しい です。 BANKチームの紹介資料にも記載がありますが、フルサイクルエンジニアを目指すエンジニアチーム風土であるため、エンジニアはアプリケーションはもちろん、インフラや分析基盤、企画にも携わっていくことが多々あります。 GoやPythonの話をBANKチームのメンバーに聞いたり、AWSの知識が深いBASE内の別のチームに聞いたり、フロントエンドの話を聞かれて答えたりと、それぞれ持っている役割や得意な領域を互いに組み合わせてプロダクト作りを進められているのはエンジニア組織としておもしろいところだなと常日頃から感じています。 エンベデッドファイナンスという少し特殊なプロダクトであること エンベデッドファイナンスの特徴として、BtoBtoC(BASE BANK to BASE to BASEユーザー)という流れでサービスを提供する形になるので、 BASE BANK目線だと提供先によってユーザーのタイプがかなり変わるという特徴 があります。 PAY.JP YELL BANKの初期提供時は、BASE向けに提供しているYELL BANKの知識をベースに作り始めていたのですが、年始あたりからはPAY.JPの加盟店の特性をLookerなどを使って分析し、より適したサービス提供方法を模索しています。 エンジニア目線では、エンべデットファイナンスというサービス形態は「(外部展開も見据えて、)社外の人が使うAPIとしてどう設計するべきか?技術構成はどうするべきか?」を一緒に考える必要があります。 これは他のサービス形態だとなかなか深く考えることがないポイントで、自分の技術力や設計力を伸ばす上で必要な観点であるし、それを磨くことでサービスとしての価値が高まることに繋がるのでとてもやりがいがある開発になります。 PAY.JP YELL BANKにおいては、これまではNext.js製のアプリケーションがBANKチームが提供するAPIのクッションと画面実装を担っていましたが、今後はPAY.JPのバックエンド向けAPIを直接提供する形に差し替えていくので、よりAPIスキーマを重視する方向で動き始めています。 金融のドメインに機械学習・銀行APIなどのテクノロジーを組み合わせて課題解決していること PAY.JP YELL BANKでは「PAY.JP加盟店の将来の売上予測」がサービスのコアにあり、その機能を実現するために機械学習を使っています。 このPAY.JP YELL BANKにおける売上予測の開発には ① PAY.JP加盟店の売上をより精緻に予測し、適切な額を資金提供して回収まで滞りなく実現することで、 BASE BANKとしての事業継続性を高めるための「守りの売上予測精度向上」 ② リスクコントロールが可能な範囲で資金提供可能な金額を上げるためにどのようなデータを追加で提供してもらって機械学習モデルに取り込むべきか考えて、 より多くのPAY.JP加盟店に使ってもらえるようにするための「攻めの売上予測精度向上」 の2種類があり、今後もこの辺りは改善を進めていこうと考えていたりもします。 その他にも、お金に関わるプロダクトであるため、高い責任感が求められる面とそれをテクノロジーを使って解決できることがPAY.JP YELL BANK開発、ひいてはBANKチームで開発を行うことのおもしろさであると思っています。 一例としては、銀行APIなどを使った振込入金におけるユーザー体験の改善などが挙げられます。 また、実際に資金提供を行うことが加盟店の運営・成長につながる一手になりうることもBANKチームで積極的に行なっているユーザーインタビューから様々なユーザーの声を聞いて知ることができ、自分たちのやりがいにつながってもいます。 まとめ ここまで長々と自分が担当しているPAY.JP YELL BANKというプロダクトのおもしろいところ、所属しているチームのいいところをたくさん語らせていただきましたが、ザッとまとめると以下の通りです! 【プロダクト内容・フェーズ的なおもしろいところ】 これからグロースしていくフェーズなので、新機能や技術構成のアップデートなどダイナミックさがあるところ エンベデッドファイナンスは直接金融サービスを提供するよりはBtoBtoCという形で複雑だが、既存のユーザーの課題により適した形でサービス価値を提供することができるところ 【組織的なおもしろいところ】 BASE / PAY.JPと伴走してプロダクトのグロースや開発を進めていくところ フルサイクルエンジニアを目指す風土なので企画、開発、運用や分析も含めて全てのステップに深く関わるところ 【技術的なおもしろいところ】 プロダクトのコア体験につながる売上予測に機械学習を使っているので、データの集め方やデータ連携の契約の組み方を考える余地がまだまだあるところ 金融(特に資金調達と決済)のドメイン知識を深めることができること 外部向けAPIとしての設計、そのためのワークフロー構築ができる・必要なこと 私自身は、この環境で開発を行なっていくことで自分の技術力やエンジニアとしての成長に繋げられるなと感じているので、今後もPAY.JP YELL BANKの開発に深く携わって、どんどんとサービスの価値を高めていけるように開発をしていく予定です!🙋 おわりに ここまでPAY.JP YELL BANK、BANKチームについての紹介をお読みいただきありがとうございました! BASE BANKチーム(求人媒体での記載はYELL BANK)ではエンジニアを大募集中ですので、少しでも気になるな👀 と思っていただけたのであれば、ぜひぜひカジュアル面談やX( @gatchan0807 )などでお声がけいただけると嬉しいです! binc.jp 明日は kitamuran さんと pigooosuke さんの記事2本立てです!お楽しみに〜!
アバター
この記事は BASE アドベントカレンダー10日目の記事です。 はじめに はじめまして! BASEのカートチームでバックエンドエンジニアをしている @ykagano です! カートチームは主にBASEのショッピングカート機能の開発を担当しているチームです。 そんなカートチームでのドキュメント管理術をご紹介したいと思います! ドキュメント管理の課題 BASEではNotionを社内Wikiとして使用しています! 社内資料は、Notionのページやデータベースで一元管理しています! カートチームでもドキュメントはNotionの一つの階層構造で管理していますが、使用している中で以下の課題がありました。 大抵のページは最初に書かれた後、 ページが更新されない ページ作成者以外は更新しづらい心理的な壁がありそう 毎回新しくページを作っているため、 新しい情報と古い情報が混在している 古いページに前提となる情報が書いてあることも 課題を解決するために Notion AIを使用しています! AIに尋ねれば各ページの情報を元に情報が出てくるのですごく便利です! でも正しい情報が一発で出てくるとは限りません。 鮮度が古い情報を拾ってくることもあります。 毎回同じことをAIに尋ねていることも……。 そこで、よく使う情報はチームの共有ページにまとめておくことをお勧めします。 これにより、以下の利点があります。 必要な情報にすぐにアクセスできます チームメンバー全員が同じ情報を共有できます チーム全体の開発効率が向上します さらに、まとめたページは、Notion AIと組み合わせることで、 より効率的に情報が検索できる ようになります。 カートチームのドキュメント管理 ではNotionを使った具体的な管理方法を見ていきます! カートチームでは一つのNotionデータベースにナレッジを集約しています! ただ更新されないままになっているページも多いです。 時間が経つとページも増えてくるので、全てのページを最新化するのも困難な状況です。 そのため、カートチームでは、ポータルページで 保守するドキュメントと保守しないドキュメントを分ける ことにしました。 カートチームのポータルページ 下図はカートチームのポータルページのトップです! 最初に保守対象のページを表示してすぐアクセスできるようにまとめています。 保守対象のページは チームメンバー全員で更新していくページ となります。 先ほどのデータベースの中から以下のラベルの付いたページを保守対象としてビューに表示しています。 チーム運営 保守(運用) 保守(技術) 保守(メンテナンス) 次に保守対象外のページです。 左側はデータベースの一覧に新規追加された資料を表示しています。 右側は保守対象外のラベルで資料にアクセスできるようにしています。 最初は工事中のページも資料が育てば、ラベルを付けて保守対象にしています。 資料はチーム全員で育てるもの です! その時気になったことが一行だけ書かれたページでも、後から気付きを得て、書いた方がいいと思ったことはどんどん追記しています! カートチームはBASEの決済動線を守るチーム これまでは主に業務視点のドキュメント管理術でした。 カートチームはBASEの決済動線を守るチームです! そのメインの仕様書はどうやって管理しているのかをお話ししたいと思います! 仕様書を常にメンテナンスすることは非常に困難です。 案件ごとにNotionやFigJamを使って資料は作っていますが、保守対象としている仕様書は一つに絞っています。 それはシーケンス図です。 決済手段ごとに外部連携先(主に決済会社)も異なれば、非同期処理になっている箇所もあります。 監視ではどこが障害ポイントになるのか確認し、改修時はどこを変更することになるのか検討が必要になります。 そうした時に素早く共通認識が持てるように、 処理全体が俯瞰できるシーケンス図を更新 しています。 シーケンス図をGitHubで管理する シーケンスを以下のようにGItHubで管理しています(表示部分は一部です)。 GitHubはMermaid形式のプレビューに対応していますので、Mermaid形式で書いたmdファイルを格納しています。 カートチームの決済動線のシーケンスは主に以下の階層構造になっています(あくまでイメージのため、一般的な概念図まで簡略化しています)。 import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; mermaid.initialize({ startOnLoad: true }); sequenceDiagram participant User as ユーザー participant Front as フロントエンド participant Back as バックエンド participant Payment as 決済会社 User->>Front: 商品選択・購入ボタン押下 Front->>Back: 購入リクエスト送信 Back->>Back: 在庫確認 Back->>Payment: 決済実行 Payment->>Back: 決済結果通知 Back->>Back: 注文情報保存 Back-->>Front: 決済完了通知 Front-->>User: 完了画面表示 シーケンス図はバックエンドのGitHubリポジトリに格納しています。 決済動線の処理が変更になるときは必ずバックエンドが更新されるためです。 決済の処理フローに変更が発生した場合、 コード修正と合わせて、シーケンス図も修正 するようにしています。 おわりに 実はカートチームに上記のドキュメント管理ルールを作ったのは最近の話になります。 今後、カートチームに文化として根付いていければと思います! BASEは決済に興味があるエンジニアを積極採用中です! ご興味がありましたら採用情報をぜひご覧ください! binc.jp 明日は、gatchan0807さんの記事です。お楽しみに!
アバター
本記事は BASEアドベントカレンダー2024 の9日目の記事です。 はじめに 初めまして、BASEでフロントエンドとバックエンドの開発を担当している kondo と申します。この記事では、私自身が開発エンジニアとしてプロジェクトを成功させるために心掛けていることを紹介します。日々プロジェクトを進める上でどのような取り組みを行なっているのか、参考になる事例紹介となれば幸いです。前提として、私はBASEではスクラム開発・ほぼフルリモートで働いております。 プロジェクトを成功させるための工夫 スクラム開発でベロシティを計測して、ストーリーポイントから逆算してリリース日がいつか可視化する スクラム開発でチームがスプリントでいくらベロシティを消費できるのか計測して、残りのストーリーポイントが何スプリントで終わるのか計測する取り組みをしています。リリース時期を予測しておくことで、自分たちはもちろん、マネージャーやビジネスサイドとも会話しやすくなります。またベロシティが向上し、チームが成長できているかなどの指標も計測することができます。 振り返りで良かったこと、改善したいこと、そのアクションを決める 振り返りの場では、チーム全員でスプリント中に良かったこと、改善すべき点を共有し、それを基に具体的なアクションを決めます。例えば、良かったこととしてペアプロの実施が挙げられた場合はペアプロを継続して、改善すべき点で細かい仕様が不透明であると挙げられたら、仕様書をより詳細に記載していきます。このプロセスを繰り返すことで、改善点は徐々に改善されて、良かったことが継続されていきます。 チームメンバー全員がフロントエンド・バックエンド・インフラの対応をできるようにする チームメンバー全員ができる領域を増やしていくことで、タスクが個人に依存しない強いチームになっていくと考えています。自分たちのチームではバックエンドエンジニアがReact、Typescriptに取り組んだり、フロントエンドも書くしSQLも書くエンジニアがいるなど、各々が領域拡大に取り組んでいます。ペアプロなどを通じてサポートすることで、無理なくできることを増やすことができています。 コードレビューより自分のタスクを優先しない コードレビューは、機能をリリースするためには必須事項です。チームが機能を素早くリリースすることは、顧客への価値提供を早めると考えています。そのため、自分が終わらせたいタスクがあり、コードレビューを先延ばしにしてしまうと、その分チームが機能をリリースできる時間が伸びてしまいます。したがって、コードレビューは優先して見るようにしています。 前提の考えや知識、立ち位置のギャップを埋めるため、同期的コミュニケーションを活用する 私のチームでは、フルリモート環境でSlackやGitHubを使い、非同期で開発を進めています。しかし、非同期のやり取りでは、前提知識や経験則、問題に対する温度感が伝わりにくく、議論が平行線になることもあります。この課題を解消するために、同期的なコミュニケーションを取り入れています。例えば、Aさんは効率性を重視し、Bさんは分かりやすさを優先している場合、意見がすれ違うことがあります。このような時、オンラインミーティングでお互いの思考プロセスや背景を共有することで、認識のギャップが埋まり、新たな解決策を見つけることができます。このプロセスを通じて、相互理解が深まるだけでなく、議論を通じてメンバー同士の新しい視点の獲得にもつながっています。 不確実性の高いタスクから取り組んで、PJの不確定要素を減らしていく プロジェクトの序盤で、不確実性の高い仕様や技術課題から優先的に取り組むことで、リスクを早期に発見し、解消するようにしています。特に外部APIとの連携や、今まで活用したことのない技術などは、実際にそれが必要になるより前に調査して、不確実性を取り除くようにしています。後半になるほど不測の事態の対応は大きくなってしまうので、早めの対応が肝心と考えています。 ストーリー・タスクの優先度を明確化する 全てのタスクに明確な優先度を設定することで、チームが今どのタスクを最優先で取り組むべきか明確にしています。こうすることで重要なタスクの抜け漏れを防いだり、優先度の高いタスクからリリースをすることができます。 仕様書を明確に記載する 仕様書を詳細に記載することで、プロジェクト全体の透明性を高め、チームメンバーや将来の関係者が迷うことなく作業を進められるようにします。また、テストケースが仕様書から明確に導き出せるようになるため、テストの効率化やバグの早期発見にもつながります。これを怠ってしまうと、Slackのどこかで会話した記憶があるとか、どこかのミーティングでこうなった気がする、といった情報が流れてしまう事象が発生してしまいます。仕様書を書く手間より情報を掘り起こしていく手間の方が高い場合も多いため、なるべく書くようにしています。 おわりに 本記事では、プロジェクトを成功させるために工夫していることを紹介いたしました。プロジェクト運営の参考になれば幸いです。明日はykaganoさんの記事が公開予定です、お楽しみに! binc.jp
アバター
本記事は BASEアドベントカレンダー2024 の8日目の記事です。 はじめに BASE Feature Dev1 Group アプリケーションエンジニアの @yuna_miyaa です。 実は、私がBASEに転職してもうすぐ3年ですが、前職はなんとパティシエでした! (受託会社を経て現在入社しております) 少し変わった経歴なのでなんでエンジニアに?はもう100回以上は聞かれています。 現在も副業でパティシエを続けられているので、いつか「パティシエとエンジニアの共通点」というテーマで記事を書いてみたいと思っています。 さて、今回はエンジニアとしての話題。 私は現在、 スクエア連携App 開発のリーダーを務めています。 この半年間でチームは2度も大きな変化を経験しました。その中で、私がリーダーとして心がけていることや、チーム開発を円滑に進めるための工夫についてお話ししたいと思います。 チーム開発を進めるために意識していること 大きな目標をみんなで共有する エンジニアリングはタスクの連続。 しかし、それだけに追われると視野が狭くなりがちです。「なぜこの仕事をするのか」「それがどんな価値を生むのか」という全体像を共有することが、モチベーションアップとチームの一体感に直結すると考えています。 たとえば、プロジェクト初期の段階では、タスクの完了条件や進め方を細かく記載して、時間をかけて認識合わせを行いました。 当時は「ここまでやる必要ある?」と思うほど大変でしたが、結果としてメンバーがタスクを進める中で全体像をつかむようになり、プロジェクトへの理解が深まっていきました。 困っていることを素直に共有する 「リーダーだから全部解決しなきゃ!」と思っていたら、いくら時間があっても足りません。 むしろ、「実はここがわからなくて…」と自分の弱みをオープンにすることで、自分の弱みや困っていることをオープンにすることで、「助け合えるチーム」という安心感が生まれるんです。 実際に、締切直前にトラブルが起きたとき、早めに「これが解決できない…助けてほしい」と声を上げたことで、メンバーが積極的に協力してくれて無事に乗り越えることができました。 一人で抱え込むのではなく、チームを信じることの大切さを痛感した瞬間でしたね。 メンバーの困りごとをいち早く察知する リーダーの役割の一つは、メンバーが成果を出せる環境を整えること。だからこそ、メンバーが困っていないかを素早く察知するように心がけています。 特に新人メンバーが入ったときは注意深く観察します。 初めての環境に慣れるまで時間がかかりますよね。 たとえば、雑談中に「最近どう?困ったことない?」と何気なく聞いてみたり、ミーティング後に個別で声をかけたり、チームの信頼関係を作る大きな一歩になると信じています。 ユーモアを忘れない 仕事に真剣さは必要。でも、真剣さばかりだと空気が重くなることもありますよね。そんなとき、ユーモアが場を和ませてくれます。 たとえば、毎週1回の雑談デイリーと称してデイリーの後に、雑談タイムを設けています。「最近ハマってるランチの話」や「ちょっとした趣味の話」など、話題は何でもOK。 仕事は真剣に取り組むべきですが、楽しい雰囲気も大事です!チームの空気を和らげるために、時々ユーモアを交えた雑談を心がけています。 笑顔が増えると、不思議とアイデアも出やすくなりますし、チーム全体の雰囲気が明るくなります。 おわりに チームは生き物のように常に変化しています。変化の中で何を大事にするか、どんなアプローチで進めるかを考えることはとてもやりがいがあります。 今回の記事が少しでも誰かの参考になれば嬉しいです。そしていつか、エンジニア×パティシエという異色のキャリアについてもお話しできる機会があればと思っています! 明日はPayIDチームのkitagawaさんです!お楽しみに! 採用情報 | BASE, Inc. - BASE, Inc.
アバター
この記事はBASE アドベントカレンダー7日目の記事です。 6日目の昨日はasakoyaさん!妊娠から復帰までの体験記がとても参考になるので興味ある方は是非ご覧ください。 はじめに こんにちはBASEのProduct Dev / Feature Dev1 GroupでアプリケーションエンジニアをしているTorataです。 突然ですが、みなさんそろそろ年末で大掃除の時期ですが整理整頓やってますか? ブラックフライデーで買いすぎて無理やり収納してるもの、なかなか捨てる踏ん切りがつかず家に残ってしまっているものはありませんか? そんな大掃除でもやるであろう整理整頓ですが、これには王道のステップがあります。 全部出す 不要なものを取り除く グルーピングする 優先順序をつけて並び替える 定位置を決めて収納する これは私の所属するチームのマネージャーのtakashimaさんから教えてもらったものです。 takashimaさんは家の中だけでなく、日々の業務における課題の整理にもこのメソッドを応用していると聞いたので自分も真似してみようと思いました。 今回はこの整理整頓のメソッドを用いて、お問い合わせの対応時間を短縮しようとした話を書いていこうと思います BASEにおけるお問い合わせ対応 ネットショップの作成サービスであるBASEには日々ショップのオーナー様やそのショップを利用するユーザー様から機能の仕様に関する質問や不具合のお問い合わせをいただきます。 技術的な調査が必要なものに関してはエンジニアに調査依頼が来るのですが、エンジニアも担当プロジェクトと並行しなければなりません。 かといってお問い合わせ対応の優先度を下げることはできないので、問い合わせに使う時間が増えることはそれだけ担当プロジェクトに割ける時間が短くなることを意味します。 改善前の課題 現在BASEでは機能をいくつかにカテゴリ分けして、チームごとに担当が割り振られています。 さらにその担当を一定期間でローテーションをすることで属人化を排除しようという方針で運用がされています。 今年の9月にも担当カテゴリのローテーションがあり、 Instagram販売App と Instagram広告App を私のチームが担当することになりました。 これらのAppはInstagramとBASEを連携するという機能で問い合わせ対応の際に以下の課題がありました 連携するのに複数ステップが必要な機能で理解が難しい 開発に携わったメンバーがすでに社内にいなく、仕様を完全に把握している人間がいない 担当カテゴリのローテーションをしたばかりでチーム内に知見がない 上記の要因と外部連携の機能という特性上、原因がBASEにあるのか、Instagram側にあるのかの切り分けが難しい これらの理由で9月10月はお問い合わせ対応に時間を取られてしまい、担当プロジェクトに影響が出てしまいました。 やったこと 実際に改善に向けて整理整頓のメソッドに則って以下のように進めました。 全部出す 今までチームで対応した問い合わせを全てFigJamに出しました。 この時、「グルーピングする」の時の指標に使えるように「問い合わせ内容」「問い合わせの回答」「かかった時間」をFigJamに並べました。 不要なものを取り除く グルーピングの時にノイズになってしまいそうな問い合わせをこの時にFigJamから削除しました。 (具体的にはInstagram関連の問い合わせではあるが、関連する別の機能に関する問い合わせ) グルーピングする 「1.全部出す」 でFigJamに出した内容をもとに以下の4グループに分けました。 社内管理画面から解決できるグループ BASE / Instagram の仕様を詳しく知らないと回答できないグループ BASE側での調査が難しく、Instagram側に再度問い合わせをお願いしたグループ その他グループ 優先順位をつけて並び替える 問い合わせ対応の時間を短縮することを目的に改善をしているので改善活動自体に時間がかかっていては意味がありません。 今回の改善はコスパよく時間短縮を目標にしていたので、件数自体が一番多く、対応自体もシンプルにできそうな「社内管理画面から解決できるグループ」を一番優先度高く、このグループの問い合わせ対応時間を減らす施策を考えることにしました。 定位置を決めて収納する 課題解決における収納はタスク化になります。 今回フォーカスすると決めたグループの対応時間を減らすために、 問い合わせ対応で最初に見るべきフロー図を作ること をタスクとしました。 また、このフロー図を問い合わせを一時受けしているCSの方にも共有することでエンジニアに来る件数を減らす、来たとしてもテンプレ的に解決できるようにすることを狙いとしました。 おわりに 片付けも仕事も何をやるか決まっていない時に腰が重くなりがちです。 これがテンプレになって次やることが明確だと最初の一歩を踏み出しやすくなるんじゃないかなと感じました。 今後、お問い合わせに限らずチームや組織で見つけた課題があれば積極的に改善活動やっていきたいですね。 他にもお問い合わせでこんなことやっているよという事例があれば是非教えてください。 BASEでは現在アプリケーションエンジニアを積極採用中です。 興味ある方は是非ご応募ください! 採用情報 | BASE, Inc. - BASE, Inc. 明日は同じチームのyunamiyaaさん!お楽しみに!
アバター
本記事は BASEアドベントカレンダー2024 の6日目の記事です。 はじめまして、BASEでエンジニアをしているasakoyaです。 2022年10月にBASEに入社し、Merchant Management Devというチームで、不正決済対策や法改正対応など、決済に関する開発に取り組んでいます。 2024年1月に第1子の男の子を出産し、10月に職場復帰したばかりです。 初めての妊娠・出産・育児に挑戦する中で、BASEのサポート体制に助けられたことがたくさんありました。この経験が誰かの参考になればと思い、筆を取りました。 なお、BASEは男性でも育休を取得される方がとても多く、関連したブログ記事もあります。 devblog.thebase.in devblog.thebase.in 今回は女性目線での体験をお届けします! 妊娠期 妊娠が分かったのは2023年の6月。 安定期に入るまでは周囲への報告を控える方も多いと思いますが、私はすぐに上司と会社へ伝えました。当初は体調が不安定だったため「迷惑をかける前に言っておこう」というのと、BASE独自の制度である「妊娠特別休暇」を付与してもらうためです。 これは定期健診や両親学級への参加、体調不良時に使える特別な休暇で、有給です。「無理せず休める環境」というのは、体の面でもありがたいですが、個人的にはそれ以上に「会社が気遣ってくれている」と感じられることが大きかったです。 また、BASEではハイブリッドワークを取り入れており、在宅でも働くことが可能なため、体調が優れない日でも無理のない働き方ができました。 12:00〜16:00がコアタイムのフレックス勤務のため、妊婦健診も、比較的空いている平日の午前中に、仕事を休むことなく行くことができました。(健診が長引いた時などは妊娠特別休暇を使いました) 仕事の引き継ぎ 体調が比較的すぐに安定したこともあり、妊娠を理由に仕事の内容や働き方を変えたり、セーブしたりする必要はほぼありませんでした。 ただ当然、出産後は長期間お休みすることになるので、引き継ぎについてはマネージャーと相談して調整していきました。私の担当していた仕事は、新しく業務委託の方を採用して引き継ぐことになったので、チームへの負担が増えることにそこまで罪悪感を覚える必要がなかったのはありがたかったです。 おかげさまで、出産予定日の3週間前まで元気に働き、1年間のお休みをいただく予定で、産前休暇に入りました。 いざ出産 「産休ってヒマだな〜」などと余裕ぶっていたら、予定日より10日も早く、いきなり破水してあっという間の出産になりました。3,300gの元気な男の子でした。 あまり準備ができていなかったので「なんか色々書類出さなきゃいけないんだよね!?」と大慌てでしたが、BASEには「産休・育休HAND BOOK」という詳しい資料があり、そこに妊娠・出産関連の手続きの情報がまとめられているので、これがとても参考になりました。 また、妊娠を報告した段階で、労務担当の方がSlack上で専用の育休対応チャンネルを作ってくれました。手続き関連で分からないことがあればすぐに聞けるので、心強かったです。私は手当金の細かい額まで色々と口うるさく質問したのですが、丁寧に答えてくださり感謝です。 手当金といえば、これは会社独自の制度ではありませんが、BASEは関東ITソフトウェア健康保険組合に加入しているため、出産育児一時金として、法定給付50万円に加えて付加給付9万円が受け取れます。ありがたいですね。 復帰まで 子育てが始まって最初の1〜2ヶ月は、体調も安定せず、慣れないことも多くてヘトヘトでした。 ただ、その期間を過ぎると(こんなことを言うと怒られてしまうかもしれませんが)結構ヒマでした。夫が5ヶ月間の育休を取ってくれたのと、子どもがとても優秀で、昼も夜もまとまって寝てくれるという、大変に親孝行な子だったからです。 というわけで、ぼんやりと桃鉄に励む日々の中でふと「早めに復帰できるのでは…?」と思い至りました。 また同じ頃に実家近くへ引っ越し、その地域で入園できる保育園が見つかったため、当初1年の予定だったのを短縮し、8ヶ月で復帰することになりました。 復帰前にはマネージャーや労務担当の方との面談があり、復帰後の働き方についてしっかり相談できたので、安心して仕事を再開できました。 時短勤務や、時間外勤務の制限など、色々な選択肢がありますが、私は休業前と同じくフルタイムで働くことを選択しました。フルタイムで復帰しようと思えたのは、自分の妊娠期の経験も含めて、BASEには柔軟に働ける環境が整っていると感じていたからです。 いざ復帰 「BASEには柔軟に働ける環境が整っている」は本当にその通りだと感じています。 たとえば、夕方以降はカレンダーをブロックして子どものお世話に専念したり、お迎えのために業務を途中抜けしたりする働き方が、マネージャー陣も含めて当たり前のように浸透しています。 子どもの送り迎えを夫と分担して、私自身は基本的に8:00〜17:00で働いていますが、Slackを活用した非同期的な業務が多いため、チームメンバーとの勤務時間のズレも問題になったことはありません。 携わるプロジェクトの内容も、私が休業前に携わっていたものと領域が近く、比較的容易にキャッチアップができるものだったので、スムーズに業務に戻ることができました。 0歳児(10ヶ月)とのリアルな生活 参考までに、今現在の私の1日のスケジュールを載せてみます。 7:00 子どもと一緒に起床 7:15 子どもと自分の朝食、自分の支度 8:00 子どもを夫に任せ、業務開始 時間がないときはパジャマのまま仕事を始めます 8:30 玄関で夫と子どもを見送り 12:00 昼休み、夫と一緒に昼食 まだパジャマのときはこのタイミングで着替えたりメイクしたり 13:00 業務再開 17:00 業務終了 17:20 保育園お迎え→子どもと遊ぶ 18:00 子どもの夕食 18:30 子どもと一緒にお風呂→ミルク→歯磨きなど 19:00 寝かしつけを夫に任せ、夕食準備 20:00 夫と夕食→片付け 21:00 フリータイム お風呂に入ったり、家事したり たまに仕事に戻ることも 時間があれば読書やゲーム 0:00 就寝 見ての通り、朝と夜はバタバタですが、この寝顔のために頑張っています。 夫婦共に在宅勤務・フレックス勤務でこれなのだから、毎日出勤しているお父さんお母さんは本当にすごい。頭が下がります。 今後のこと ここまで書いてきて、なんだか本当に順風満帆にやってきたなあと思い返しています。妊娠の経過がとても順調だったこと、生まれた後も手のかからない性格で健康な子どもだということ、それから夫の全面的な協力があるという、非常に恵まれた環境のおかげです。 みんながみんなこうはいかないだろうし、私自身の家庭環境も、これからどう変化していくか分かりません。 でも、もしこの先、家庭環境が大きく変わっても「働き方や目指す方向を相談すれば、柔軟に対応してもらえる環境がBASEにはある」と感じています。 おわりに もちろん「子育て中でもみんなフルタイムで働くべきだ!」と言うつもりは全くありません。 大事なのは、幅広い選択肢と、柔軟な環境があること。少子化が叫ばれて久しい現代、子育てとキャリアの両立ということが言われますが、なによりも、変化に対応できるという安心感が必要だなと思います。 ここに書いた私の経験が「こんな働き方・生き方もあるんだ〜」と誰かの参考になったり、希望になったりしたらとても嬉しいです。 もしBASEという会社に魅力を感じていただけた方がいれば、是非一緒に働きましょう! binc.jp 最後になりましたが、産休・育休に快く送り出してくださり、復帰後も温かく迎えてくださったチームメンバー、支えてくださったマネージャーや労務担当の皆さま、本当にありがとうございました。 これからもよろしくお願いします! ※当記事でご紹介した制度等は2024年の執筆時のものです。法律や会社の制度は変わる可能性がありますのでご了承ください。 明日はTorataさんが整理整頓の極意を教えてくださるそうです。お楽しみに!
アバター
devblog.thebase.in 本記事はBASEアドベントカレンダー2024の5日目の記事です。 はじめに BASE Feature Dev2 Groupでバックエンドエンジニアをしている大塚( @ohiro88 )です。 10月1日に入社し、2ヶ月が経過したので入社してから現在までを振り返ってみようと思います。 これまでの経歴 インターネット広告代理店(ASP) → 求人広告メディア → BASE という感じです。 新卒でエンジニアとして入社してからずっとPHPをメインで使用しています。 BASEに入社するきっかけ 前職では求人メディアのバックエンドエンジニアをしていました。 エンジニアとしてのキャリアを通じて、ユーザーに価値を提供することにやりがいを感じてきました。もちろん前職でもやりがいはありましたが、プロダクトの成長がユーザーへの価値提供により直接的につながる開発に携わりたいと考え、転職活動を始めました。 転職活動をする中で、転職サービスを通じてBASEにお声がけいただきました。 サービスはもちろん知ってましたし、カンファレンスなどのスポンサーをされていることも知っていたので、技術に対して積極的に取り組んでいるという印象でした。 お話を伺う中で、ユーザーへの価値提供がプロダクトの成長にダイレクトにつながっていくような、サービスにもっとコミットできる開発ができる環境があると感じ、入社を決めました。 入社して感じたこと スピード感 まず感じたことは開発や意思決定などの速さです。 開発チームやプロジェクト単位で意思決定をする場面が多く、開発メンバー一人一人の自分たちの開発しているプロダクトに対しての解像度が非常に高いと感じました。 それと同時に、技術力やコミュニケーション力も非常に高く、実装の方向性の議論をしてから実装までが非常に速いと感じました。 デプロイのフローもある程度整っておりオンデマンドで実行されるため、総じて開発組織としてのパフォーマンスが非常に高いと感じています。 プロダクトやユーザーへの熱量 次に感じたのはプロダクトファーストやユーザーファーストな考え方です。 BASEはプロダクトの成長がユーザーの成長に直結するので、常にユーザーのことを考えながら開発を進めています。 この後にも記載していますが、ユーザーの声から発足するプロジェクトなども多くあります。 こういった開発は私自身もやりたかったことなので、かなりやりがいを感じています。 入社直後のお仕事 入社後は配属予定のチームから1名メンターを付けていただき、社内の手続きや開発サポートなどを行なっていただきました。 入社して初めの2週間ほどはメンターの方とオンボーディングタスクを中心に実施しました。 あらかじめオンボーディングタスクとして社内の制度や簡単な開発などを用意していただいているので、それを消化することで自然とBASEのことや開発方法などを知ることができました。 BASEは会社としての規模が前職よりも小さかったので、会社の制度として行き届いていない部分もあると思っていましたが、人事の方やメンターの方含め手厚くサポートして頂き、スムーズにチームに合流することができました。 各部署の責任者の方々や社長から直々に説明会や質疑応答の場を用意していただいているので、疑問や不安などもなく入社できたと思います。 現在のお仕事 現在はショップオーナー様が利用する管理画面の検索機能のパフォーマンスを改善するプロジェクトを進めています。 BASEはスモールビジネスをされている方々向けにスタートしたサービスですが、リリースから10年を超えありがたいことに規模の大きなショップも増えてきております。 サービスが順調にスケールしているので良いことではありますが、データ基盤などがそれに追いついておらず、検索機能のパフォーマンス問題が顕著に現れるようになってきました。 実際に規模の大きなショップオーナー様からも課題としてお声をいただいていました。 そういった課題を解決するため、今までの検索で利用していた技術を全く新しいものに一新するプロジェクトになってます。 検索に関わる技術というのは多くのwebサービスのコアの技術になってくると思っており、それを改善することでプロダクトの価値は向上すると考えています。 それが直接ユーザーであるショップのオーナー様のショップ運営の効率改善にも貢献でき、ショップの売上向上などにも貢献できると思うので、とてもやりがいを感じています。 私の入社する前から動いていたプロジェクトで、終盤に差し掛かっているタイミングでのアサインとなりましたが、質問などにも丁寧に答えてくださったり、ペアプロなどを通じて私のコードの理解のサポートをして頂いたりと手厚くサポートいただいているので、なんとか皆さんに付いていけているような状況です。 そういった大変さはありますが、プロジェクトを通じて私自身の成長にも繋がっているという実感もあるので、いち早くチームの力になれるように成長していこうと思います。 その他にやっていること 検索機能のパフォーマンスを改善するプロジェクト以外にも並行して様々なことをやらせていただいています。 その1つとして、New Relicの活用推進のお手伝いをさせていただいています。 New Relicについての説明は割愛させていただきますが、BASEではアプリケーションのメトリクス監視やログ監視、サービスレベル運用などで活用しています。 直近ではサービスレベルの活用を進めていこうということで、社員主導のワークショップでメンバーのサービスレベル運用の理解促進をしようとしています。 前職でもNew Relicの活用を推進させていただいていたので、入社2ヶ月程度の新参者ですがその経験を活かしてワークショップの進行をさせていただいたりしています。 サービスレベルの運用についてはまだまだ始めたばかりなので、しっかりと活用できるように尽力して行きたいと思います。 おわりに まだまだ慣れていない部分もありますが、社員の皆さんはとても良い方ばかりでサポートや制度は充実しておりいつも助けれらています。 常にユーザーのことを第一に考え、日々ユーザーのためにプロダクトを成長させています。 そんなBASEに興味を持っていただけたら嬉しいです。 BASEではエンジニアを絶賛募集中ですので、興味のある方はぜひ採用情報などもご覧ください! 明日は、Asakoyaさんの記事です。お楽しみに! binc.jp
アバター
はじめに BASE BANKの出金Dev Group でエンジニアをしている 池田聖示 です。 2024/07に入社し、今月で6ヶ月目になりました。 今までどのような取り組みをしてきたのか、何を感じたのかなどを書きたいと思います。 自己紹介 妻と4歳の息子がいます。 エンジニア経験としては、大学時代の長期インターン1年と新卒で入社した企業で2年ほどの計3年、主にバックエンドエンジニア(タスクに応じてフロントエンドの開発なども行っていた)として働いていた状態で入社しました。 なので本ブログは、主にエンジニア経験年数の短いand子育てしている私にとってBASEがどのような環境かという視点で書いています。 ※本記事の情報は2024/12/04時点でのものです。特に組織に関することや福利厚生に関することは変更されることもあるためご了承ください。 入社した理由 大規模なシステムに携わりつつ、さまざまな領域に知見のあるエンジニアの方々と研鑽してエンジニアとして成長したいという気持ちと、BASE BANKのエンジニアの指針であるフルサイクルエンジニアとして開発に閉じずプロダクトにインパクトを出せるエンジニアになりたい、という思いで選考を受けご縁があり入社しました。 選考を通してエンジニアリングのレベルの高さを感じ、成長できそうと強く思ったことと、この先自分が開発だけではなく、施策の企画〜リリース後の検証や評価という一連のサイクルに携わりながらサービスにインパクトを残せるエンジニアになりたい、という目標が明確になり入社を決意しました。 ※BASE BANKについては下記の資料をご覧ください speakerdeck.com ※フルサイクルエンジニアに関しては詳細に下記のブログで説明されているのでぜひご覧ください devblog.thebase.in 入社してからのこれまで 7月1日の入社から2週間〜1ヶ月くらいまではオンボーディング期間として、会社の説明、各部署の説明、所属部署の各サービス説明などを受けつつ、オンボーディングタスクとして担当サービスの開発業務を行いました。 感想としては、オンボーディングがとても手厚かったので率直に驚きました。ドキュメントが整っていたので開発環境構築や担当サービスのキャッチアップなどで特段困り事などがなかったです。 不明点があればメンターの方が丁寧にサポートしてくださったのでスムーズに開発に入れました。 また、基本的にBASE BANKは週に一度出社ですが、入社した週は出社してメンターとマネージャーがオフィスでオンボーディングをしてくれたので、不明点がある際にすぐ聞ける環境だったのでとてもありがたかったです。入社した人の希望を聞きつつオンボーディング期間の出社日などを柔軟に対応してくれたのでとても助かりました。 入社後行ってきたタスクで大きなものだと、特定エンドポイントのパフォーマンス改善タスクや月初業務の自動化などを行ってきました。 どちらも経験のないタスクだったのですが、毎日のデイリースクラム後に同期作業会という相談や壁打ちなどチームで自由に使える時間でサポートして貰いつつなんとか開発を行ってきました。 仕事の環境 私が所属しているBASE BANKという部署は、5つほどのサービスを提供しておりエンジニアは3つのチームに分かれています。 ただ、チームを超えて定期的なミーティングや輪読会なども行っています。チームを超えて技術的な相談なども気軽にできる環境です。 私が所属しているチームでは主に、振込申請という機能とBASEカードというサービスの開発をしており、各メンバーは軸足を置いて開発している機能・サービスもありつつ複数のサービスの開発を担っています。 そして、各サービスで技術スタックは異なるので様々な技術に触れることができて刺激的です。 後述する成長した点で詳しく書きますが、バックエンド、フロントエンド、インフラなど問わず開発をしているので経験技術の幅がとても広がっています。 入社して感じたBASEの良いところ 経験のあるエンジニアが沢山いる フロントエンドやバックエンドの領域で技術力があるエンジニアが多いのはもちろんのこと、SREやデータ分析などの専門チームがあるので、インフラに関する実装を行った際にはレビューしてもらったりできるので技術力を研鑽できる環境だと感じています。 チームを越境して助け合う文化がある Slackで誰かが質問や相談のコメントをしたら誰かしらがすぐ回答する様子をよく目にします。チーム問わず困っている誰かを助けようと動く文化がとても素晴らしいと感じています。 情報がオープン 肌感ですが上場している関係で出せない情報や個人情報以外は全て観れるのではないかと思っています。特に月に一度の全社の会議では各部署の数字や行っていることなどをキャッチアップできるとともに、ドキュメントとして後から閲覧できるのでとてもオープンだなと感じています。 ドキュメントが充実している 技術的なドキュメントで言うと特定ツールの使い方や設定や「〇〇しました」系のTipsドキュメント(例えば、「デッドロック対策しました」とか)を書いて社内で公開してくれるので、何かで詰まった際や同じような実装をするときなどに先人の知恵が豊富にあります 働きやすい 私は子供がいるため、フレックスタイムを利用して幼稚園の迎えやご飯、寝かしつけなどが終わってから夜再度働くなどのスタイルで働く日が多いです。コアタイムが12時から16時という比較的短い時間なので突発的に何かあっても柔軟に動けます。 嬉しかった福利厚生 子の看護休暇 「子の看護休暇」という通常とは別の有給休暇が付与されており(1年あたり5日)子供が熱を出した時などに使用できるため、有給の残りの日数を気にすることが少なくなりました。 メンターランチ オンボーディング期間に会社の人と行くランチ代を会社が支給してくれます。他のチームの方と交流できるのでとてもありがたかったです。 「BASE」加盟店での購入補助 BASEを利用しているショップで、月1万円までの購入金額を会社が支給してくれます。この福利厚生があるおかげで、BASEを利用してくれているショップを実際に購入して関われる機会が増えたので素晴らしい福利厚生だと感じています。 ※福利厚生についての記事は下記をご覧ください basebook.binc.jp 入社してからどのような成長があったか 技術の幅が広がった 主に下記の3つにおいて技術の幅が広がったと感じています。 設計 月初対応の自動化タスクでバッチ処理を設計から行い非同期でのETLや、使用するAWSサービスの選定などを行ったことで今まで行ったことのなかった設計タスクを行い技術の幅が広がりました インフラ 今まで経験のなかったAWSサービスを使用したり、Terraformを使いリソースの管理を行ったりしてインフラ領域での経験を積むことができました 未経験の言語など 前職ではGoとReactで開発していましたが、振込申請は主にPHPとVue、BASEカードはGoとVueという技術スタックなのでPHPとVueに関しては未経験だったのでキャッチアップしつつ経験をすることができました(チームに今年のPHPカンファレンス実行委員長( @cocoeyes02 )がいるのでPHPキャッチアップにおける最高の環境がある) また、Vue Fes Japan 2024にも会社の補助で参加させてもらい、技術的な挑戦を後押ししてくれる会社だと強く感じています。 より精度の高い仕事をする上での考慮することが増えた 例えば、DBのテーブルのレコード数増加によるパフォーマンスへの影響や、非同期処理も考慮した運用手順などの設計、複数のリポジトリやサービスが絡んだ機能のリリースフローなど、規模が大きいからこそ特に考慮しなければならないことがあり、今まではあまり考えたことがなかったことも考慮するようになりました。そしていかにリリース時の懸念を無くし不確実性を減らすかという視点で考えるようになりました。 ただこれらのことも入社当初は何も考えることができていませんでした。前述した同期作業会で日々壁打ちしつつフィードバックをもらい短いサイクルで開発→フィードバック→修正を繰り返して早く成長することができたと思っています。 まとめ 最後に働きやすさや成長の観点で弊社がどのような環境か私が思っていることを書きます。 総じて言えることは、少なくとも今私が所属しているチームは若手のエンジニアの成長にとても素晴らしい環境であると思っています。 チームの成長のためにアドバイスやフィードバックを惜しまない環境があるおかげで多くの挑戦ができていると感じています。未経験のこともサポートしてもらいつつリリースすることで貴重な経験を積むことができています。 今回は量の関係もあり、入社の目的の1つであったフルサイクルエンジニアとしての働き方や取り組んでいることなどについては触れることができませんでした。機を見てブログなどで振り返りたいと思っています。 おわりに BASE、BASE BANKでは一緒に働く仲間を募集しています! ぜひ採用情報をご覧ください! binc.jp 明日は @ohiro88 さんです!お楽しみに!
アバター
<この記事は Hatena-Blog-Workflows-Boilerplate によって作成されました> こんにちは! BASE 株式会社 Pay ID 兼 BASE PRODUCT TEAM BLOG 編集局メンバー の @ zan_sakurai です。 今回は、BASE PRODUCT TEAM BLOG のブログメンバーを Terraform Provider for HatenaBlog Members で管理をはじめたので、その紹介をいたします。 前回は、 Hatena-Blog-Workflows-Boilerplate を使ってとある SaaS のリンクを一括置換した話 について書きました。 今回も企業ではてなブログで技術ブログ等を運営されている方の一助になればと幸いです。 Terraform Provider for HatenaBlog Members Terraform Provider for HatenaBlog Members とは、株式会社はてなさん が公式で公開されている、はてなブログのブログメンバーを Terraform で管理できる Terraform Provider です。 以下のドキュメントに詳細を記載いただいており、今回はそちらを参考にし構築を行いました。 はてなブログのブログメンバーを Terraform で管理できる Terraform Provider for HatenaBlog Members を公開しました hatena/hatenablog-members | Terraform Registry https://github.com/hatena/terraform-provider-hatenablog-members Terraform Provider for HatenaBlog Members でのブログメンバーの管理 これまで、BASE PRODUCT TEAM BLOG でのブログメンバーの管理は、BASE PRODUCT TEAM BLOG 編集局に依頼を行って、手動でブログメンバーの追加・削除を行っていました。 有り難いことに、ブログを執筆したいと言ってくれる方々も増えてきておりまして、それと同時に編集局のブログメンバーの管理の負担が徐々に大きくなってしまうことが予想できました。 そこでブログメンバーの管理を簡略化するために、Terraform Provider for HatenaBlog Members を導入に至りました。 基本的には前述のドキュメントを参考にしながら構築すれば特にハマるところはなく、スムーズに導入することができるかと思います。 弊社の場合、ブログメンバーもそこそこ多く? resource... を都度記述して追加するのも面倒に思えましたので、 以下のように、メンバーのリストを変数として管理し、そのリストを使って、メンバーを追加・削除するようにしています。 variable "members" { description = "List of members to manage" type = list ( object ( { username = string role = string } )) } members = [ # This is an example of the structure of the members variable. # You can add or remove members as needed. # { # username = "your_hatena_id" # The Hatena ID of the blog member. # role = "editor" # Role of the blog member. Role must be one of 'admin'(管理者), 'editor'(編集者), or 'contributor'(寄稿者). Basically, Please set the 'editor'(編集者). # }, { username = "your_hatena_id" role = "editor" } , また、Github 上での管理を行っていますので、 申請(Pull Request) -> 承認(Approve) -> 反映(terraform plan/apply) といった流れでメンバーの追加・削除を行うことができますので、変更履歴も残り、誰がどのような変更を行ったかも把握しやすくなりました。 承認(Approve) に関しては、CODEOWNERS を設定して、マージ前に必ずレビューを強制するようにして、承認プロセスを表現しています。(CODEOWNERS のメンバーは、編集局のメンバーを設定しています。) さいごに(と余談) 今回は弊社での Terraform Provider for HatenaBlog Members の利用例を取り上げてみました。 企業ではてなブログで技術ブログ等を運営されている方にはぜひ一度お試しいただきたいです!!! 余談ですが、今回の Terraform Provider for HatenaBlog Members のようにコードでのメンバー管理をするよく目にすることが増えてきたように思います。 このようなパターンは Teams as Code などと呼ばれるそうですね。 GitHub Provider など他にもあるので、いつか検証してみた記事を書くかもしれません。 明日は、 @ikechen さんの記事です。お楽しみに!!! また、弊社では仲間大募集中です! open.talentio.com
アバター
この記事は BASE アドベントカレンダー2日目の記事です。 勉強会の隠れた課題「読書会のジレンマ」に立ち向かう BASEでシニアエンジニアをしている プログラミングをするパンダ です。 この記事では普通の社内読書会をレベルアップする方法を紹介します。自分の所属するチームでは2ヶ月に渡る勉強会でこの方法を実践した結果、参加者の全員が書籍の内容をしっかり学べたという手応えを感じています。実際に、チームメンバー全員から今までよりも効果的な読書会だったと感想を貰えました。 結論を先に書くと、その方法は自分たちが触っているレポジトリから書籍の内容に当てはまるところと、書籍の内容を実現できていないところを探して発表するというものです。つまり、本で得た知識をすぐに使うというアクティブラーニング志向の読書会です。 そもそも読書会というものは、課題の書籍を読んできてその感想を共有するやり方が一般的です。ただし、この方法はパッシブな読書体験であり、読み手のスキルによって知識の定着量に差が出てしまいます。なぜなら、技術本の読書会の目的はみんなの知識レベルを揃えることなのに、本を読みこなすためにはそれなりに事前知識やスキルが必要だからです。このことを私は 「読書会のジレンマ」 と呼んでいます。 読書会のジレンマは読書会に必ずついて回る課題なのに、読書会での学習効果は学習者自身の責めに帰せられるが故に大っぴらに語られることのない隠れた課題でもあります。この読書会のジレンマについて、以前 ある勉強会の発表 で触れたところ「うちの会社でもあります」と共感を得られました。そこでこの課題は普遍的なことだと思ったこと、勉強会の工夫によってこの課題の解決に一定の成果が出ていることから記事にして公開することにしました。 読書会の隠れた課題を紹介するスライド 勉強会の隠れた課題が露見した瞬間 BASE 社内では読書会が盛んに行われています。あるプロジェクトが組成された後、プロジェクトの完遂までに1,2回ほどは各チームで読書会が開催されるように思います。もちろん業務時間内にです。 私が読書会のジレンマに気づいたのは、かつて自分が所属していたチームで読書会を開催した時でした。技術雑誌のある特集を教材として2回の短期的な勉強会を実施しました。 その方法は以下です。対象範囲を事前に読んで感想をドキュメンテーションツールに記述し、勉強会当日にそれを共有する。疑問が共有されれば、その場でその疑問をみんなで一緒に考えるというごくごく一般的なスタイルです。 読書会の開催後、短い教材の中から「実務に活かせる」とそのエッセンスを読み解いて翌週のプルリクエストに学んだ内容を反映したメンバーもいれば、「書かれている内容は理解したが、実務に活かせるイメージが湧かない」という感想を共有したメンバーもいました。 自分にとってこの言葉はショックでした。読書会の教材は実務に活かすためのものであり、「ここに書かれていることは今の自分たちのコードのここに活かせるな」と考えながら読むもので、それこそが実用書の価値というものです。 「内容はわかったけど、実務にどう活せばいいかわからない。」この言葉は自分に勉強会のあり方を再考させるきっかけとなりました。そして、次はこのフィードバックを活かした勉強会を開催しようと心に決めました。 チームのスキルレベルを揃えるための勉強会を開くために そのチームは無事にプロジェクトを完遂して解散した後、全く異なるメンバーで新しいチームが組成されました。 チームメンバーには転職してきたばかりの方もいました。どのチームでも最初はそうなのですが、全員のスキルレベルが揃っていないが故に、開発初期のコードレビューでは「本当に届けたい機能、実現したい仕様」といった本質的な議論よりも「このときはこう書くのが一般的です」という表面的なレビューのコメントが増えていました。 開発の現場で学習のコストは見過ごされているように思います。実のところ勉強会というものは、事前の見積もりに含まれない隠れた教育・学習のコストです。つまり、「開発の期限は、開発者が不足しているスキルを補う時間を考慮していない」のです。これはアジャイル開発かどうかは無関係です。 少ない時間でチーム全員をある程度同じスキルレベルにまで手っ取り早く引き上げる手段を採用したいと思うのはごく自然なことです。この課題を解決するために、 オブジェクト指向設計スタイルガイド の勉強会を開催することに決めました(この本の選定理由はスライド 「軽量DDDはもういらない! スタイルガイド本で OOPの実装パターンを学ぼう」 をご覧ください)。 しかし、事前に読んで感想を共有するベーシックな勉強会のやり方では以前の二の舞になる可能性があります。前回の勉強会を反省した結果、そもそも勉強会には「読書会は参加者のスキルアップのために実施するのに、そもそも読み手のスキルレベルによって吸収量に差がある。その結果、学習効果は一様ではない」という勉強会のジレンマがあることに気づきました。 そこで、このジレンマを解消するためにアクティブラーニングな勉強会を実施する方法を考えました。 アクティブラーニング志向の勉強会で知識をすぐに活用する アクティブラーニング志向の勉強会では各自の感想の共有を実施しません。代わりに以下の2点を勉強会で議論することにしました。 書籍の内容で理解できなかったこと、疑問に思ったことをメンバーに共有する。その内容が理解できるまで、疑問が解消するまで徹底的に議論する 書籍の内容と自分たちが開発しているレポジトリのコードを照らし合わせて、書籍の内容を実践出来ているところと出来ていないところを pick up して紹介する 勉強会の開催にあたり、まずチームメンバーに対して勉強会のコンセプトを共有しました。コンセプトはアクティブラーニング形式であることを表現したものです。 コンセプト 議論中心。知識をそのまま覚える暗記に加えて、なぜそれが有用なのか、あるいは不要なものがあるかを語り合う コードで実践!とにかく実践!新規実装でもリファクタでもすぐに実践する! 次に各回の進め方と事前に準備してもらうことを共有しました。 各回の進め方 各設計スタイルで理解できなかった点を共有してもらい、それについて議論する 「このスタイルを採用するメリットがわからない」とか 自分が気に入ったスタイルを反映しているプロダクションコードを最低1つ紹介してもらう 「スタイルを反映していないので、これからリファクタか改修したい」というコードでもOK 準備してもらうことは負担が大きくなりすぎないように設計しました。 準備してもらうこと 1章ごとに 疑問を持った設計スタイルを書いておいてもらう いくつでも 気に入ったスタイルが反映されている、もしくは反映されていない backend のコードを pick up してもらう 最低1つ 本を読む、コードを探す、本の知識でコードを批評する。この3ステップがアクティブラーニング志向の勉強会の構成要素です。 実際に参加メンバーが共有した内容はこちらです。「本番コードでの assert の使いどころ」や「getter でプロパティを検証していることは Allways Valid なドメインモデルではない」などかなり踏み込んだ疑問や指摘がされているのがわかります。実際にこのときの議論は盛り上がり、また建設的な内容になりました。 疑問を持った設計スタイルの例 スタイルを反映していないコードの例 アクティブラーニング志向の勉強会の効果を振り返る 勉強会の開催以降、コードレビューでは「このときはこう書こう」という一般的な指摘がほとんどゼロになったため、実際に効果があったと言って良いでしょう。「ここは本に書いてましたよ」ということもほとんどありませんでした。 さらに、 オブジェクト指向設計スタイルガイド は良書であるため、チーム全体の設計力も向上しました。 これらの結果は勉強会の目的通りだったので一安心しました。しかも、当初想定していた以上の成果を出すことが出来たのは自分にとって驚きでした。それは、参加メンバーからの勉強会に対するフィードバックの内容に表れています。 本文の質問をきっかけに心理的安全性を醸成 フィードバック1「書籍を読んで感じた疑問についてみんなに聞いて考えることで、書籍の内容をきちんと理解できた」 疑問に対しては誰か知ってる人が教えるというのではなく、みんなで一緒に考える雰囲気を作ることを意識していました。思わぬ効果として、何でも質問ウェルカムな雰囲気の中で自分が持つ疑問についてきちんと議論することで「自分はここで何を聞いてもいいんだ。わかってないやつだとバカにされないんだ」という心理的安全性が醸成されました。 チームでは勉強会が終わった後もこの雰囲気は継続しています。後日あるメンバーと1on1をしたところ「何を聞いてもみんな優しく答えてくれるので安心して働ける」と言って貰えたのでチームビルディングにも役立つことを体感しました。 スタイルの適応例探しから業務外のコードリーディングへ フィードバック2「事前準備をする中で、今触っているレポジトリから本に関係するコードを探してくるので、書籍の内容の使用イメージがすぐに湧いた」 この点に関しては狙い通りです。さらなるフィードバックとして「普段開発している箇所以外から垣根をこえてコードのサンプルを探しに行くので、システムの中心的な部分など他のところでどんなクラスがあってどんな処理をしているのかを学ぶきっかけになった」と言って貰えました。 こちらも思わぬ効果です。実際、ある人はAというモジュールに対象のコードを探しにいき、ある人はBというモジュールに探しに行くとします。すると、それぞれの人はAモジュールとBモジュールのコードリーディングをして「こうだった」と発表をします。その知識は勉強会のメンバー全員に共有されるため、「ここは触ったことがないけどこういう作りになってるんだね」とみんなの視野が広がりました。 スタイルに従ってコーディングしたら、コードの良し悪しの言語化が上達した フィードバック3「スタイルに従うことで自分の考えていることをうまく表現できるため、コーディング中に迷うことが減った」 このフィードバックには続きがあります。「それに加えて、スタイルの原理原則に従っていないコードの違和感に気づける上に、それがなぜ、どのように違和感があるのか説明ができるようになったのは大きな恩恵です。」 良いエンジニアはコードを書いている間も「本当にこの書き方がベストだろうか」と自分に問い続けています。スタイルガイド本を学ぶことでこのセルフレビューの質が上がりました。他の人が書いたコードに対する違和感を言語化してうまく説明することができるようになったため、コードレビューのスキルもアップしています。 まとめ 読書会の準備と内容を見直すことにより、冒頭の「本の内容はわかったけど、実務にどう活せばいいかわからない」という課題に対処できました。もちろん書籍が扱っている内容によるものの、この勉強会の方法自体は社内勉強会でも社外の人たちと集まって輪読する会でもそのまま適用できると思われます。 普通の勉強会をぜひ学習効果の高い勉強会にバージョンアップしていきましょう。 BASEではWebアプリケーションエンジニアを積極採用しています。興味のある方はぜひご応募ください。 採用情報 | BASE, Inc. - BASE, Inc. 明日のアドベントカレンダーは zan_gi さんの「Terraform Provider for HatenaBlog Members を使ってみました。」です!お楽しみに!
アバター
はじめに こんにちは!BASE株式会社で開発担当役員をしているえふしんです。 今年もBASEグループ 2024年のアドベントカレンダーのトップバッターを務めさせてもらっています。 今回の記事では、2024年では、Xのタイムラインなどでよく聞いた「開発生産性」について考えてみたいと思います。 開発生産性を高めるとは 開発生産性という言葉を今年よく聞きましたが、その定義は、なかなか難解です。 生産性を定義する難しさについては、廣木さんの下記ドキュメントが参考になります。 qiita.com 経営レベルのマクロな意味であれば、GDPなどと同様に単純に 「売上総利益 ÷ 開発に携わる人数」 などでいいはずです。単純に、そのプロダクトの成果としての売上総利益や営業利益に対して、人数で割って見ておけば、過剰人員になった場合はこの値が落ちるわけですから、コスト的に効率の良い製品開発かは測ることがしやすいです。 一方で、開発チームで考えたいと思うのは、開発という行為に対して、我々の開発活動のあり方が今のままでいいのかそうでないのかを推し量る指標のことで、いろんなものが提案されていますが、何かを妥協して決めつけていかないと万能な指標はでないというのが定説です。また、開発という投資に対する遅行指標である売上総利益を待ってたらもう遅いです。 世間ではFour Keysが定番化しているようですが、ちょっとDevOps寄りすぎるというか、自分がイメージしている改善のアクションへの接続がちょっと難しそうだなと思って、活用自体はポジティブですが、もう少し納得できるように考えたいと思いました。 そもそも自動車の生産ラインのように一つの生産ラインで同じ類の製品をひたすら量産して、一定のものさしを前提としている仕事とは違って、一品一様で二度も同じコードを作らないWebサービスのソフトウエアの開発活動をなんらかしらの指標で平準化して求めようなんてのは難しい話です。 個人的には目標値として一切、置かない形で、デプロイ頻度を見ておくことがチームの活気を示すという意味ではありかなと思うぐらいで、間違っても、LL言語やオープンソースライブラリ、コード補完エディタに依存してるプロダクトがソースコード量を生産性指標にするのは違うと思いますし、案件難易度がプロジェクトごとに違うのにスプリントやバックログの数を複数チーム間での比較指標にするのも違うかなと思います。むしろ目的達成に対して変更するソースコード量の少なさを測りたいくらいです(いや、それも違うと思うが、更新するWebサービスにおける理想はむしろそっちかと思いますし、知識労働者がコード量だけで測られるのはおかしい話)。 指標化というのはカジュアルにやってしまいがちですが、不適切な指標化は人間の行動をミスリードすることがあります。そんな簡単なものではないと思えばこそ、慎重に考えたいものです。それこそ、馬のおしりをムチで叩くようなマネジメントなどになってしまったら、知識労働者であるソフトウエアエンジニアがその状況に我慢できるハズはありません。脳内がポジティブでなければ、適切にソースコードが生み出せないので、生産性云々以前の話です。 そもそも何のために継続的に開発をやっているのか?といえば、ユーザーに価値を届け、ユーザがなんらかしらのメリットを獲得し、その成果としてのビジネス指標が改善され、我々のビジネスの成長につながることを実現するということです。 つまり重要なのは「何回、ユーザさんに「いい変更だね!」という評価を受けたか?」という回数にあると思います。そのためには告知を含めた機能リリースや改善リリースの数を増やすということがプロダクト開発がやりたいことで、その中から成功確率を上げていくというのは、ビジネス全体視点での提供品質の話になります。 なので、プロダクト開発視点では「ユーザさんが知るリリース数を増やす」「トライの数を増やす」ということを「アウトプット量の増加」= 「結果としての生産性の向上をもたらす取り組み」に着目できたらと思っています。測定ツールとしては、品質指標や集計のことを考えて、まるっとFour Keysの利用でいい気がしてますが、重要なのはユーザインパクトをもたらすことを目指したトライの数を意識することです。 たとえソースコード1行でも迅速に素晴らしい改善をしてユーザの感動を得れば、それは「有益だった1トライ」だと思いますし、大きい機能開発で大きなインパクトを得ることも重要なことです。 トライの数を増やすために必要なこと 個人的に継続開発するWebサービスの開発生産性に対する考え方として個人的に好みなのは、牛尾 剛さんという米国マイクロソフトで働かれている方の著書「 世界一流エンジニアの思考法 」に書かれている、 生産性とはいかに「レベル1( = 何もググらずに実装できる)」を増やすかどうかではないか? という文章にジワジワ来ました。 つまり人間のアウトプットを計測した数字の多寡を重視するアプローチではなく、能力に着目した考え方です。なお、牛尾さんの本では、このことが生産性の高さの指標だと書いてるわけではなくて、そのような能力を獲得することが重要なのではないか?という形で書かれています。 めちゃめちゃわかりやすく、いい感じに煽られてると思いませんか? そう言われてみれば、社内で一番望ましい開発能力というのは、いわゆる汎用的な地頭の良さである仕様理解力に加えて、 大まかにソースコードを把握していて、どこをいじれば望み通りの結果が出るかを如何に素早く見積もりできて、見積もり通りに実現できること ではないかと思いました。正確には案件仕様と現場の仕様をかけあわせて、何をどこまでやるべきかについて正しい見積もりと脳内設計をして、適切にアウトプットできる人が一番確実な仕事ができるはずです。CTOへの仕様レビューも、さくっと口頭でやれてしまうのが望ましい(それだけの信頼と実績があることも含め)。 開発言語や開発ライブラリの知識はもちろんですが、自分たちのソースコードを掌握している人が一番、仕事の対応速度は早いと考えられます。同じだけの設計能力があるなら知識量が多い方が仕事が早い可能性が高いです。 そもそも、開発生産性に対する社内議論が出てきたのは、自分たちのプロダクトが、新入社員では簡単には把握できないほど大きくなってしまったと言われ始めた頃と符号します。もっと会社が小さかった頃の創業当初のメンバーは、今よりも少ないソースコードを把握していて、おそらく生産性という面では、一番そのころが早かったのでしょう。新しい機能の話が出てきても、話を聞きながら大まかの設計は完了していて、あの辺とこの辺を直せばいいと思っていて、即座に開発作業に取り組んでいました。 しかし、昨今の中途採用や業務委託の方による一定の人の出入りを前提とした組織において、ソースコードやサービスの仕様を知るというオーバーヘッドと、コード量やプロジェクト量の増加に伴う把握のしにくさが、開発者の作業性のオーバーヘッドにつながっていったと考えると、極論すると、全部のソースコードを把握していて、頭の中で整理してくれるような脳内のLLMを保有しているエンジニアが、結果として、一番、速やかに実装できると考えることが可能です。 ちゃんと改善を前提とした時に、エンジニア個々人のスキル評価にあわせることができれば、エンジニアの評価を上げる = エンジニアのグレード(給料)が上がる = 生産性が上がるはずです。機能リリースが増えて、ユーザー評価を受けるためのトライの数が増えたのに「売上総利益 ÷ 開発に携わる人数」が改善しなければ、それ以外の何かが間違ってるって話になり、根本から見直す必要があるでしょう。 トライを増やすための「オーナーシップ」と「脳内LLMへのインプット」 内製開発におけるエンジニアの能力を 「何もググらずに実装できるレベルのこと」 に置いたと仮定します。もちろん何もググらずというのは、かなりの理想の話で、Google検索したって、社内ドキュメントを調べても良いのですが、それらの行動と理解速度が最速で作業を進められるようになることを意識することが大事です。 これをもう少し抽象化し、このような状態を「ソースコードのオーナーシップ」を持っている状態と表現してもいいかもしれません。そして、同時に複数人が同じリポジトリのソースコードをいじってるわけですから、毎日、どこかしらの新しいソースコードが生成されると考えると、これはstaticな記憶力のことではなく、コードリーディングを通じて人間の動的に脳に備わっているLLM(人間の知力)が再構築できる能力ということになります。 また、エンジニアの能力をあげていくことを前提として、それを阻害する要素を取り除くことも重要な取り組みです。この場合は、アウトプットに着目するよりも、エンジニアへのインプットの最適化を考えるアプローチになると思います。こちらの方が、ふわっとアウトプットの量を高めようとするよりは、具体的な施策に落ちてきそうな気がしてきませんか? 例えば、議論や会議の質を高めるであるとか、オンボーディングの改善、ドキュメントの整理の最速化、リファクタリング、更新情報の共有の仕方などが挙げられます。BASEのリアーキテクチャで取り組んでいるclean architecture化(バックエンドリポジトリ)への貢献もオーナーシップ化を促す活動とも言えます。コードを大きく書き換えるタイミングは、オーナーシップ獲得のチャンスです。また、如何に、最少人数、最小構成でリスクを伴った意思決定を最速にできるかというのも重要になってきます。 当社のCTOは、チームメンバーが増えた今もリリースされているプルリクを把握してると聞いてますが、脳内LLMのインデックスを更新する作業を自然にやっているのは素直にすごいと思います。普段、プロジェクトに関わってなくてもコードリーディングを通じて、プロダクトに何が起きてるかを把握し続けているわけです。 なお、ChatGPTなどの生成AIなどの取り組みについても触れておきますと、人間の記憶力は揮発性ですし、年齢や体調によっても脳の力の活用度は変わってきます。ただ単に人間の記憶力だけに頼るのは芸がありません。人間としてのスキルだけでなく、検索や生成AIの活用などで外部記憶を効率よく人間の脳内LLMと連結することができれば、とても生産的なAIに対する向き合い方もできると思います。プロンプトに仕様を書いてコード生成されるだけのAIの利用はイマイチだと思っていますが、人間の理解や意思決定を支援するAIというのであれば歓迎です。開発エディタへのAI支援機能の搭載やNotion AIの活用など開発環境も進化していきますから、AIに対する老害にならず、しっかり取り入れていきましょう。 マネージャが意識しないといけないこと マネジメントラインにおいても、現実的に中途採用、転職、社員、業務委託などの属性を考えて、かつ、エンジニアの成長曲線とグレード評価に必要な時間などを勘案すると、現状の我々が構成しうる、チームメンバーのグレード構成の布陣というのは、一定の範囲を取るはずです。 要は退職が増えて、代わりに入っていただいた新人が増えれば、コードの知識、ドメイン知識がないのは当然だから、学びのリードタイムが必要になり、その時点のチームの人材ポートフォリオは弱くなるでしょうし、経験者が長く働いてスキルが向上していけば、給与もあがって、チームメンバーのグレード構成も高いからこそ、高い生産性で開発プロジェクトをこなしていけるというのは、自分で書いても実感のあるところもあります。 つまり開発生産性の高みを目指すというのは、チームマネージャが望む人材ポートフォリオを、状況にあわせて構成することで、高い生産性を発揮できるチームを作ることに邁進することそのものではないかと思いました。新規事業やM&Aなどでも組織が拡張されていくので動的な概念です。常にチームはアメーバのように分裂したりして増加減可能なことが大前提です。BASEグループのカルチャーが組織の増加スピードと人員移動に対して適切に平準化したりお互いに相乗効果を生み出し続けることも大事です。 マネージャは自分を城を作るという考え方ではなく、会社全体の開発力を上げて、よいアウトプットを生み出す状況を作り続けるのが仕事です。その際にチャンスを得て適切にソフトウエアエンジニアとしての信頼を評価に結びつけていくためにも、メンバーと日常のコミュニケーションを取ってほしいと思います。 人間は機械じゃないので、安定したアウトプット量を出すことでさえ大変だと思いますが、生成AIがどれだけ進化したとしても最後の決断は人間が下し続けると思いますので、最適、最速な意思決定をして、適切なリスクにチャレンジするためにメンバーのスキルを上げ続けることになると思います。ちなみに、適切なリスクとは「基本は無茶をする」です。意思決定の際に無難な方と無茶な方の2つの選択肢があったとしたら、無茶をした側の意思決定じゃないと、プロダクトは成功しないです。でも、できないことを部下にやらせるわけにはいかないと思いますので、ちゃんと実現できるように頑張ってください。 おわりに なお、開発だけの話を書いているように見えるかもしれませんが、当然、開発プロジェクトは開発チーム、ビジネスチーム、プロダクトマネージャやデザイナーが渾然一体となってスケジュールタイムラインを成すので、すべてのチームにおける考慮は不可欠です。オーナーシップという視点でも、それぞれが、ビジネスのオーナーシップ、プロダクトのオーナーシップ、プロジェクトのオーナーシップ、UXのオーナーシップなどを適切に発揮できているかは最重要な確認事項とも言えます。 特に、各々がリスクを伴った意思決定を増やし、失敗を恐れず、朝令暮改も上等という考え方で、チーム間のトライも増やしてく。そのための心理的安全性の構築などが重要です。 改善においても適切なリスクの取り方、意思決定のあり方、リモートワークとリアルmtgの使い方、会議の質の向上など要素は多岐に渡りますが、トータルで言うサービス開発の生産性を阻害するものを効率化しつつ、それぞれの役割のスキルを向上させていくことで、トライを増やし、アウトプットの総量は増えていくのではないでしょうか。来年はこのことについて、一つ一つ考えていけたらいいなと思っています。 現在、2025年に向けた人員計画を立てている真っ最中ですが、引き続き採用活動をすると思いますので、以下の採用情報ページからマッチする求人があるかを見ていただけると幸いです。 binc.jp 明日のアドベントカレンダーは @Panda_Program さんの「「読書会のジレンマ」を克服する: 成果を生むアクティブラーニング勉強会の実践法」です!お楽しみに!
アバター
こんにちは!BASE PRODUCT TEAM BLOG 編集部です。みなさまそろそろ年の瀬ですが、いかがお過ごしでしょうか。 今年も恒例のBASEメンバーによるアドベントカレンダーを開催します! 毎年公開しているアドベントカレンダーも今年で7回目を迎えます。 過去の様子 2023年のアドベントカレンダー 2022年のアドベントカレンダー 2021年のアドベントカレンダー 2020年のアドベントカレンダー 2019年のアドベントカレンダー 2018年のアドベントカレンダー 今年も1日1記事に限定せずたくさんのバラエティ豊かな記事を公開する予定です。 公開され次第以下のカレンダーも随時更新していきますので、ぜひお楽しみに! 日付 執筆者 タイトル 12/1 @fshin2000 プロダクトの開発生産性について考える 12/2 @Panda_Program 「読書会のジレンマ」を克服する: 成果を生むアクティブラーニング勉強会の実践法 12/3 @zan_sakurai Terraform Provider for HatenaBlog Members を使ってみました。 12/4 @ikechen 若手エンジニアが BASE 入社 6 ヶ月目で感じていること 12/5 @ohiro88 【入社エントリー】BASE に入社して 2 ヶ月を振り返る 12/6 asakoya 妊娠・出産・育休を経て Hopeful に働いている話 12/7 Torata 整理整頓のメソッドでお問い合わせ業務の改善をした話 12/8 @yuna_miyaa チーム開発を進めるために意識していること 12/9 @kondo プロジェクトを成功させるための工夫 12/10 @ykagano カート開発を加速させる!効率的なドキュメント管理術 12/11 @gatchan0807 技術目線でみた、PAY.JP YELL BANKのおもしろいところをご紹介! 12/12 @kitamuran Pay IDメンバーの個性がひかる お仕事環境紹介 12/12 @yusuke.saito 私のキャリアチェンジ:データ分析者からプロダクトマネージャーへ 12/13 @roothybrid7 Pay IDシステム移管プロジェクトの技術スタックの紹介 12/14 tac_tanden SLI/SLOの設定を進めるその前に、アラート品質の改善に取り組んだ話 12/14 @toshi-oliver 輪読会「ドメイン駆動設計をはじめよう」をプロジェクトチームで開催した話 12/15 @miyachin_87 Notion FormとAutomationを使って定型タスクの自動生成ツールを作ってみた 12/16 uenoka レポートシステムの安定稼働に向けた取り組み 12/16 @ysssssss98 ペアプログラミングの体験がすごく良かったので布教したい 12/17 @eijenson Jetpack Compose上でのconstraintLayoutの利用事例紹介 12/18 @meihei PhpStorm で PHPUnit をずっと実行している話 12/18 gonzaresu108 INFORMATION_SCHEMAを用いたBigQueryデータ監視 12/19 @ichi アジャイル組織のデザイナーがやってきたチームビルディングの話 12/19 kaneko20 Pay ID 3回あと払いの開発をきっかけに知る住所の味わい深さ 12/20 @gimupop 登壇やアウトプットを後押しするnote 12/20 noji_ma Next.jsのServer Actionとreact-hook-formでフォームを実装した 12/21 basems (仮)エンジニアと非エンジニアで足並み揃える PJ 進行 12/21 kuma プロダクトマネージャーは 「面白がり力」があると良い 12/22 bun タイトル未定 12/22 @zan_sakurai 今年の記事をふりかえってみた 12/23 @endu PHP Conference Japan 2024 に参加しました! 12/23 shotakeuchi 不均衡データにおけるROC曲線とPR曲線について 12/24 @simezi9 今、リモートワークについて思うこと 12/24 @u_hayato13 統括マネージャー(EM of EM)の仕事7選 12/25 dmnlk タイトル未定
アバター
はじめに こんにちは、Pay IDのフロントエンドエンジニアのnojiです。 普段はPay ID あと払いやPay IDのアカウント周りのフロントエンド開発を担当しています。 10月に Pay IDのアカウント編集画面 ( こちら )をリニューアルしました。この記事では、そのリニューアルプロジェクトでNext.jsのApp Router / Server Actionを活用し、便利だと感じた点をご紹介します。 使用技術 Next.js 14(リリース当時のもの。現在は15になっています) React 18(リリース当時のもの。現在は19になっています) リニューアルの背景 今回のリニューアルは、PAY社が保有する「Pay ID」のデータおよびシステムをBASE社へ移管・再構築するプロジェクトの一環でした。 単なる移管に留まらず、デザインリニューアルを伴うため、PAY社で使用されていたVue.jsのコードは再利用せず、Next.jsでゼロから構築しました。 Next.jsを選定した理由: BASE社内での採用実績があり、安心して使用できること。 BASE以外でも採用事例が多かったり、近年のfrontendのトレンドをリードしていること また、新規アプリケーションの構築であることから、Next.jsのApp Routerを採用しました。(開発着手当初はNext.jsバージョン13でした) App Routerの活用 App Routerはディレクトリ構造がそのままURLパスとして反映されるほか、ファイル名によって役割が明確になる点が特徴です。 /app/〇〇 └ layout.tsx // pageをラップした共通画面(画面遷移で再レンダリングされない) └ template.tsx // pageをラップした共通画面(画面遷移で再レンダリングされる) └ error.tsx // エラー時の画面 └ loading.tsx // ローディング中の画面 └ not-found.tsx // notFound()がthrowされたときの画面 └ page.tsx // 実際のURLに対応するページ これをもとに以下のようなツリー構造で描画が行われます <Layout> < Template > < ErrorBoundary fallback = { < Error /> } > < Suspense fallback = { < Loading /> } > < ErrorBoundary fallback = { < NotFound /> } > < Page /> </ ErrorBoundary > </ Suspense > </ ErrorBoundary > </ Template > </Layout> 親ディレクトリのLayoutを子ページが自動的に引き継ぐため、レイアウト部分を共通化しやすいのが便利でした。 https://nextjs.org/docs/app/building-your-application/routing Route Groups 一方で、「URL階層は親子関係があるがレイアウトを切り替えたい」というケースでは、 Route Groups を活用しました。 Route Groupsはディレクトリ名を () で括ることで利用できます。 (a) └ hoge └ layout.tsx ← レイアウトA └ page.tsx (b) └ hoge └ layout.tsx ← レイアウトB └ fuga └ page.tsx /hoge ではレイアウトAを利用 /hoge/fuga ではレイアウトBを利用 このように適切にレイアウトを切り替えられるのが便利でした。 https://nextjs.org/docs/app/building-your-application/routing/route-groups Parallel Route 管理画面では、ダッシュボードのように 複数の情報を1ページに集約して表示 することが多く、特定のページで Parallel Routes を利用しました。 app └ @parallel └ hoge └ page.tsx // A情報 └ default.tsx └ default.tsx └ hoge └ page.tsx // B情報 @ で始まるディレクトリがslotとなり、同じ階層にある layout.tsx でpropsとして受け取ります。 default.tsxは初期読み込み時やページ全体の再読込中に一致しないスロットのフォールバックとしてレンダリングするファイルを定義しています。 何も表示させない場合はnullを返却するように定義しておきます。 // layout.tsx export default async function Layout ( { children , parallel , } : { children : React.ReactNode ; parallel : React.ReactNode ; } ) { return ( <> { parallel } // A情報 { children } // B情報 </> ); } この仕組みを利用し、A情報とB情報をそれぞれ別のページとして描画し、以下のように表示しました: /app/@parallel/hoge/page.tsx → A情報を表示 /app/hoge/page.tsx → B情報を表示 Parallel Routesの利用により、slotに分割されていることで各ページでの処理が少なくなったり、コードの可読性が上がるように感じました。 Parallel routeでもloading.tsxやerror.tsxを配置しておくだけでローディング処理やErrorBoundaryを利用できるので便利に感じました。片方エラーだったときのハンドリング等しやすい印象を受けました。 しかし、soft navigation時に前のParallelRouteのページが表示されたままの状態になることがあり、Client Componentでラップして、pathを見て出し分けするようにし、意図せず表示が残ってしまうことを防いでいます。 // MatchPathRenderer.tsx 'use client' ; export default function MatchPathnameRenderer ( { matches , children } : Props ) { const pathname = usePathname(); if (!matches || !matches. includes (pathname)) { return null ; } return <> { children } </> ; } // layout.tsx ... < MatchPathnameRenderer matches = { [ '/hoge' ] } > { parallel } </ MatchPathnameRenderer > { children } ... https://nextjs.org/docs/app/building-your-application/routing/parallel-routes Server Component / Server Actionの活用 Server側でのデータ処理周り 各Server Componentからサーバー側で非同期にデータフェッチが可能となり、データ管理が効率的に行えるようになりました。また、同じデータのフェッチ処理は自動的にキャッシュされるため、パフォーマンスへの影響も最小限に抑えられます。 さらに、App Routerの Suspense 処理(loading.tsx)を活用することで、サーバーから先にHTMLを返却し、高いユーザー体験を簡単に実現できるようになりました。 サーバー側で動作するため、機密情報やセキュリティに関わるデータも安全に取り扱うことが可能です。 クライアントからサーバーへのリクエスト周り 従来、クライアントからNext.jsのサーバーへリクエストを送る際には、 API Route を定義し、それを通じてAPIを実行する必要がありましたが、 Server Action を使用することで、クライアント側から直接サーバー側の関数を呼び出せるようになり、非常に便利に感じました。 // server action処理 'use server' ; export const action = async ( prevState : any , formData : FormData ) => { // server側処理 } // フォームコンポーネント 'use client' ; export default function Form () { const [ state , formAction ] = useFormState(action); return ( < form action = { formAction } > ... </ form > ) } https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations リダイレクト周り アカウント管理画面では、認証が必須のため、各URLで適切なリダイレクト処理が必要だったのですが、 Server Component や Server Action 内で redirect を利用することができます。 Server Component内でのリダイレクトはPage RouterでのgetServerSidePropsと似たような処理が実現できます。Server Component内にリダイレクト処理が書けるようになった部分は少し可読性が上がったように感じました。 // Server Component内 export default async function Page () { const session = await getSession(); // セッション取得処理 if (!session) { return redirect( '/login' ); } const { response , error } = getData(session); // データ取得処理 if (error. status === 401 ) { return redirect( '/login' ); } ... } Server Action内のリダイレクト処理については以前だとAPI Routeでクライアントにレスポンスを返してからその結果を見てページ遷移を行っていましたが、サーバー側で処理を完結させることができるため、リダイレクト先のページ表示が高速化されました。 // Server Action内 'use server' ; export const action = ( formData : FormData ) => { const session = await getSession(); // セッション取得処理 if (!session) { return redirect( '/login' ); } // API callなどサーバー処理 return { message : '成功しました' } ; } ; https://nextjs.org/docs/app/building-your-application/routing/redirecting キャッシュ周り Server Action でサーバーサイドの処理を実行した後、クライアントの情報(キャッシュ含む)を更新する際に revalidatePath を利用しました。クライアント側でレスポンスを受け取り、値に基づいてUIを更新したり、 router.push や router.refresh で明示的にページを遷移させる必要がなくなりました。 'use server' ; export const action = ( formData : FormData ) => { // API callなどサーバー処理 revalidatePath( '/hoge' ); return { message : '成功しました' } ; } ; https://nextjs.org/docs/app/building-your-application/caching#revalidatepath おわりに システムリニューアル時にNext.jsのapp router / server actionsを触ってみて便利だった部分を紹介させていただきました。 これからNext.jsを利用するプロジェクトの参考になれば幸いです 現在Pay IDではエンジニアを募集しているので、興味のある方は気軽にご応募ください。
アバター
はじめに こんにちは。BASE株式会社 Pay IDでiOSアプリエンジニアをしている kakkki です。 iOS版のPay ID ショッピングアプリ の開発を担当しています。 iOS版Pay IDアプリ(以下Pay IDアプリ)はリリースされてから9年以上、新機能開発を行いながら継続的に運用されています。普段の業務では機能開発を進める一方で、マルチモジュール化やStrict Concurrency対応、SwiftUIへの移行など継続的に技術的な改善に取り組んでいます。 Pay IDアプリ内に古くから存在する画面の多くはUIKitベースで実装されていますが、最近はSwiftUIで実装された画面が増えていくようになりました。そこで本記事では、 SwiftUIを導入する過程で直面した課題や取り組みについて 、約2年前から今日に至るまでの画面実装方針の変遷を3つのフェーズに分けながら紹介していきます。 特に長期間運用されているUIKitベースのアプリにSwiftUIを導入したいと考えているiOSエンジニアの皆様にとって、少しでも参考になれば幸いです。 フェーズ1 (2022年10月頃): Compositional Layouts × Diffable Data Source この時点では、複雑なレイアウトやデータ管理を効率化するためにiOS13から導入されたCompositional LayoutsとDiffable Data Sourceを採用していました。Compositional Layoutsにより、複雑なレイアウトでもセクションごとに設定できるようになりレイアウト実装の柔軟性が高まりました。また、Diffable Data Sourceによりデータの変更を即座にUIに反映できるようになり、データとUIの統一的な管理が可能となりました。 しかし、この時点ではSwiftUIは全く導入しておらずUIKitのみでの開発でした。UIKitは今でもバリバリ現役ですが、SwiftUIと比べてUI実装の速度が劣るケースもあり、後々開発効率などの観点からSwiftUIを導入していきたいという気持ちが強くなっていきました。 フェーズ2 (2023年10月頃): Compositional Layouts × Diffable Data Source × SwiftUI(UIHostingConfiguration) 次のステップとして、SwiftUIを小さくコンポーネント実装に留める形で導入していくことを試みました。 この時点では以下の図のようなイメージです。 具体的には、 UICollectionViewCell 内でSwiftUIを利用する方法です。以下のような UICollectionViewCell の共通実装を用意して、iOS16以上では UIHostingConfiguration 、iOS15では UIHostingController 経由でセル内のUIをSwiftUIで実装していくようになりました。 /// HostingCellの中身となるViewが準拠するプロトコル public protocol HostingCellContent : View { associatedtype Dependency init (_ dependency : Dependency ) } open class HostingCell < Content : HostingCellContent >: UICollectionViewCell { // TODO : iOS15サポートを切ったら削除する private let hostingController = UIHostingController < Content? > (rootView : nil ) override public init (frame : CGRect ) { super . init (frame : frame ) // TODO : iOS15サポートを切ったら削除する hostingController.view.backgroundColor = .clear hostingController.view.translatesAutoresizingMaskIntoConstraints = false } @available ( * , unavailable ) public required init ?(coder : NSCoder ) { fatalError( "init(coder:) has not been implemented" ) } public func configure (_ dependency : Content.Dependency , parent : UIViewController? = nil ) { if #available(iOS 16.0 , * ) { configureWithHostingConfiguration(dependency) } else { configureWithHostingVC(dependency, parent : parent ) } } /// iOS16以上ではUIHostingConfigurationを使う func configureWithHostingConfiguration (_ dependency : Content.Dependency ) { if #available(iOS 16.0 , * ) { contentConfiguration = UIHostingConfiguration { Content(dependency) } // デフォルトでマージンが設定されているので0にする .margins(.all, 0 ) } } // TODO : iOS15サポートを切ったら削除する /// iOS15以下ではUIHostingControllerを使う func configureWithHostingVC (_ dependency : Content.Dependency , parent : UIViewController? ) { guard let parent else { return } hostingController.rootView = Content(dependency) hostingController.view.invalidateIntrinsicContentSize() guard hostingController.parent == nil else { return } parent.addChild(hostingController) contentView.addSubview(hostingController.view) NSLayoutConstraint.activate([ hostingController.view.leadingAnchor.constraint(equalTo : contentView.leadingAnchor ), hostingController.view.topAnchor.constraint(equalTo : contentView.topAnchor ), hostingController.view.trailingAnchor.constraint(equalTo : contentView.trailingAnchor ), hostingController.view.bottomAnchor.constraint(equalTo : contentView.bottomAnchor ), ]) hostingController.didMove(toParent : parent ) } } セルの呼び出し側はセルの中身がどのように実装されてるかを知る必要はなく、従来の UICollectionViewCell を用いた開発と同様に実装することができました。この取り組みにより、セル内のUIをSwiftUIで宣言的に記述できるようになり、UI構築の開発速度が向上しました。 フェーズ2のSwiftUI導入時に直面した課題 しかし、フェーズ2の UICollectionViewCell 内にSwiftUIを埋め込む実装の方法だと、いくつかの課題に直面しました。 特に以下の要件を満たすために、実装が複雑化しやすいことが分かりました。 お気に入り状態を更新する際に、APIの結果を待たずに一時的にUIに反映したい ユーザーによるデータ更新操作およびUIへの反映処理が頻繁にある これらの要件を実現する過程で、次のような問題が浮かび上がりました。 @State による状態管理が可能なケースが限定される UIHostingConfiguration 内でSwiftUIビューを使用する際、 @State プロパティを外部から初期化すると問題が発生していました。具体的には、外部から渡された値で @State を初期化すると、セルの再利用時に状態がリセットされず、データの不整合が発生することがありました。 以下は簡略化したコード例です。 struct ItemView : View { @State private var isFavorite : Bool init (_ isFavorite : Bool ) { // 外部から渡された値で@Stateを初期化 _isFavorite = State(initialValue : isFavorite ) } var body : some View { // ... } } UICollectionViewCell 内でこの ItemView を使用すると、セルの再利用時に ItemView の init が再度呼ばれますが、 @State の値は初期化されず以前の状態を保持します。そのため、表示とデータの不整合が生じ、意図しないUIの挙動になります。 また、以下のようにSwiftUI内部で isFavorite を更新した際も、同様にUIに正しいデータが反映されないことがありました。 struct ItemView : View { @State private var isFavorite : Bool init (_ isFavorite : Bool ) { // 外部から渡された値で@Stateを初期化 _isFavorite = State(initialValue : isFavorite ) } var body : some View { Button(action : { // お気に入り更新APIを呼ぶ前に、スムーズにUIが更新できるように一時的に更新する isFavorite.toggle() // お気に入り更新APIをコール }) { // ... } } } これらの問題の原因は、 @State がSwiftUIのビューのライフサイクルに基づいて状態を管理するため、外部からの初期化と相性が悪いことにあります 。WWDC22にある Use SwiftUI with UIKit のセッションでは、以下のようにSwiftUIの外で保持されるデータを使用する際は @State , @StateObject といったプロパティラッパーは使うべきでないという言及がされています。 To store data that is created and owned by a SwiftUI view, SwiftUI provides the @State and @StateObject property wrappers. Since we're focused on data owned outside of SwiftUI, these property wrappers aren't the right choice. 引用: Use SwiftUI with UIKit (値を利用する箇所がSwiftUIの中に閉じているケースでは、 UIHostingConfiguration の中のSwiftUIで @State を利用しても期待通り動きます。) 当時、Pay IDアプリ内の多くの画面はMVPアーキテクチャで構成されていました。Presenterを知っているのは UIViewController のみです。つまりSwiftUIの外でデータを保持する構成にしていた以上、大半のケースでは @State を使用することはできませんでした。 SwiftUIビュー内で発生したイベント伝搬とデータ更新伝達の複雑化 セル内のSwiftUIビューでユーザーアクションが発生すると、それをPresenterに伝える必要があります。するとコールバックやデリゲートなど、なんらかの形でUIViewControllerを経由しないといけません。 // イメージ図(諸々簡略化して書いています) アプリユーザー ↓ ユーザーアクション(お気に入りボタンをタップなど) SwiftUI.View ( in UICollectionViewCell) ↓ ユーザーアクションの伝達 UIViewController ↓ ユーザーアクションの伝達 Presenter ↓ APIリクエストなどのハンドリング さらに、Presenterの処理の結果何かしらSwiftUI.Viewの中の表示を変える場合、Presenterの結果をまたSwiftUIビューに伝えないといけません。当然Presenterは直接SwiftUIビューを知ることはありません。そこで以下のようなフローでデータ更新を伝搬することになります。 // イメージ図 Presenter ↓ データ更新の伝達 UIViewController ↓ データ更新の伝達 SwiftUI.View ( in UICollectionViewCell) ↓ UIを更新 つなげるとこのようなフローになります。 // イメージ図 アプリユーザー ↓ ユーザーアクション(お気に入りボタンをタップなど) SwiftUI.View ( in UICollectionViewCell) ↓ ユーザーアクションの伝達 UIViewController ↓ ユーザーアクションの伝達 Presenter ↓ APIリクエストなどのハンドリング ↓ データ更新の伝達 UIViewController ↓ データ更新の伝達 SwiftUI.View ( in UICollectionViewCell) ↓ UIを更新 もちろんこのフローでもアプリを要件通り実装することは可能です。ですが、SwiftUIでUIを書いているとSwiftUIで実現しやすいデータバインディングの機構を取り入れて、上記のようなフローをよりシンプルにしたいと思うようになっていきました。 フェーズ3 (2024年7月頃): SwiftUI × Observationフレームワーク フェーズ2の UICollectionViewCell 内に埋め込む形でSwiftUIを導入した際に出てきた課題を解決するため、よりSwiftUIベースな実装方針を目指すことを決めました。ここでの構成が、現在新しい画面を実装する時に積極的に取り入れている形です。 SwiftUIとUIKit間の責務分離と整理 まず、状態管理とビジネスロジックをSwiftUI内部で完結させる実装方針に転換しました。また、レイアウトも今まではUIKit(Compositional Layouts)で実装していましたが、この部分もSwiftUIで実装するように変更しました。 UIKitは主に画面遷移を担当します。ナビゲーションバーなど一部UIを担当することもありますが、API通信などのデータ取得もSwiftUI内部(ViewもしくはViewが保持するViewModel)からRepositoryを参照するようになり、SwiftUIとUIKit間のデリゲート処理もかなり減りました。それによりビュー内で発生したイベント伝搬・データ更新の複雑だったフローの見通しも改善されました。 View+コンポーネントは、言わずもがなSwiftUIです。View+画面遷移の部分は、引き続きUIKit(UINavigationController)で行います。View+レイアウトについては、UIHostingControllerを利用してUIViewControllerにSwiftUIで書いたレイアウト実装を埋め込んでいます。 そしてView+ロジック・状態管理については、Swift5.9, iOS17以上で利用できるObservationフレームワークを導入することにしました。 PerceptionによるObservationフレームワークの導入 しかし、Pay IDアプリはまだiOS16.2以上をサポートしています。そこで Point-Free が公開してくれている、Observation相当のバックポート実装を提供してくれる swift-perception を利用します。swift-perception(以下Perception)ライブラリはiOS17以上であればObservationフレームワークのAPI(withObservationTracking)を呼び出し、iOS17未満ならObservationフレームワークの挙動を模倣した実装が呼ばれます。 SwiftUI上の状態管理において、Perception経由でObservationを導入するか、Combine製のObservableObjectプロトコルを利用するか悩みましたが、ObservableObjectよりもObservationの方が以下の点から使い勝手が良いと判断しました。 子ビューの差分更新において不要に親ビューの差分更新が走らない 値(クラス)を入れ子にした場合、ネストしたクラスの値の変更を検知できる Perceptionを利用する場合、追跡したい値を保持するオブジェクトを @Perceptible クラスとして宣言します。そして @Perceptible クラスへのアクセスを持つViewを WithPerceptionTracking Viewでラップします。 import Perception struct HogeScreen : View { let viewModel = HogeViewModel() var body : some View { // WithPerceptionTrackingで囲む必要がある // TODO : iOS16切ったらWithPerceptionTrackingラップを外すだけでいい WithPerceptionTracking { VStack { Text(viewModel.count) Button( "Increment" ) { viewModel.increment() } // ... } } } } @Perceptible // TODO : iOS16切ったら@Observableに変更する @MainActor final class HogeViewModel { var count = 0 func increment () { count += 1 } // ... } // UIHostingControllerでSwiftUI.ViewをUIViewControllerの中身として埋め込む final class HogeViewController : UIViewController { override func viewDidLoad () { super .viewDidLoad() let vc = UIHostingController(rootView : HogeScreen ()) self .addChild(vc) self .view.addSubview(vc.view) vc.didMove(toParent : self ) vc.view.translatesAutoresizingMaskIntoConstraints = false vc.view.topAnchor.constraint(equalTo : view.topAnchor , constant : 0 ).isActive = true vc.view.leftAnchor.constraint(equalTo : view.leftAnchor , constant : 0 ).isActive = true vc.view.rightAnchor.constraint(equalTo : view.rightAnchor , constant : 0 ).isActive = true vc.view.bottomAnchor.constraint(equalTo : view.bottomAnchor , constant : 0 ).isActive = true // ... } } このようにSwiftUI上のデータバインディングが可能になったおかげで、データの変更が発生した際に @Perceptible クラスの値を更新するだけで済み、UIへの反映がとても楽になりました。 Perception利用時の注意点 Perceptionを使う上で特に重要な注意点の一つとして、 ForEach などViewを返すクロージャーの中で @Perceptible な値を参照する場合は、クロージャーの中でも別途 WithPerceptionTracking で囲む必要があります。囲っていないと、値の更新があってもクロージャー内のViewには反映されません。 struct HogeScreen : View { let viewModel = HogeViewModel() var body : some View { WithPerceptionTracking { ForEach(Array(hogeViewModel.items.enumerated()), id : \\.offset) { index, item in // WithPerceptionTracking { // ❌ hogeViewModel.itemsの一部のnameが更新されても変更を追跡できていない Text(item.name) // } } } } } @Perceptible final class HogeViewModel { /* ... */ } その理由は、 ForEach のクロージャーがViewのbody本体と同じタイミングで呼び出されるわけでなく、Viewのbody本体が呼び出された後に ForEach クロージャーが呼ばれることにあります。そのため ForEach 自体を囲った WithPerceptionTracking の中には ForEach クロージャーの中身のViewは含まれていないので、 @Perceptible のプロパティを追跡するためのViewの登録処理が動いていないのです。 以下のように、ForEachクロージャーの中もWithPerceptionTracking囲っていれば追跡可能です。 struct HogeScreen : View { let viewModel = HogeViewModel() var body : some View { WithPerceptionTracking { ForEach(Array(hogeViewModel.items.enumerated()), id : \\.offset) { index, item in // ForEachのクロージャーの中もWithPerceptionTrackingで囲ってるので、 // 値の変更を追跡できてる // ⭕️ WithPerceptionTracking { Text(item.name) } } } } } @Perceptible final class HogeViewModel { /* ... */ } また、自作のViewにViewBuilderを渡す際も注意が必要です。 以下のコードだと、CustomContainerに渡すViewBuilderの中身が WithPerceptionTracking で囲まれていません。body本体が呼び出されるタイミングではViewBuilderの中はまだ呼び出されてないので、 Text(hogeViewModel.count) の箇所はbody直下の WithPerceptionTracking の範囲外になってしまいます。 // ViewBuilderを渡す側 struct ContentView : View { let hogeViewModel = HogeViewModel() var body : some View { WithPerceptionTracking { CustomContainer(title : "Custom Container" ) { // ViewBuilderの中身 Text(hogeViewModel.count) } } } } // ViewBuilderを受け取る側 struct CustomContainer < Content : View >: View { let title : String let content : () -> Content init (title : String , @ViewBuilder content : @escaping () -> Content ) { self .title = title self .content = content } var body : some View { VStack() { Text(title) content() .padding() } } } @Perceptible final class HogeViewModel { /* ... */ } なので先ほどと同様に、ViewBuilderの中身もWithPerceptionTrackingで囲む必要があります。 // ViewBuilderを渡す親View struct ContentView : View { let hogeViewModel : HogeViewModel var body : some View { WithPerceptionTracking { CustomContainer(title : "Custom Container" ) { // ViewBuilderの中身をWithPerceptionTrackingで囲む WithPerceptionTracking { Text(hogeVieModel.count) } } } } } おわりに 本記事では、古くからあるUIKitベースのiOSアプリにおいてSwiftUIを導入する過程で直面した課題や取り組みについて紹介させていただきました。 前述した通り現在のPay IDアプリのサポートバージョンはiOS 16.2以上であり、次回のサポートバージョン整理のタイミングでiOS17以上のサポートとする可能性があります。その時はPerceptionから純粋なObservationへの移行もあるので、その取り組みの中で新たな気づきがあればまたどこかで紹介させていただければと思います。 また、新規実装はもちろんのこと、古いUIKitベースの画面に対してSwiftUIベースな実装方針に基づいてリアーキテクチャなどもチームで進めていきたいと思います。 最後になりますが、Pay IDでは随時メンバーを募集しています。ご興味のある方は気軽にご応募ください! open.talentio.com
アバター