TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

590

はじめに こんにちは。BASEのDataStrategyチームで機械学習を触っている竹内です。 機械学習といえばLLMやDiffusionモデルなど生成モデルの発展が目覚ましい昨今ですが、その一方で構造化データに対して特徴量エンジニアリングを行い、CVを切って、LightGBMなどの便利な決定木ベースのフレームワークに投げて、できたモデルの出力を吟味し、時には致命的なリークに気付き頭を抱えるといった王道のアプローチは相変わらず現役で、実務に関していえば当分お世話になる機会が減ることはないかなという気がしています。 今回はそういったクラス分類モデルにおける性能の評価指標の1つである、ROC曲線やAUC、PR曲線といった概念について振り返りつつ、実務上しばしば見られる不均衡データに適用する際の注意点などについて、軽いシミュレーションも交えつつ掘り下げてみようと思います。 ROC曲線 2クラス分類器の性能を評価する際によく使用されるツールとしてROC Operating Characteristic)曲線が挙げられます。 ROC曲線は縦軸に再現率(Recall)、横軸に偽陽性率(False Positive Rate)をとり、それぞれの検出閾値に対する再現率、偽陽性率をプロットすることで得られる曲線です。ROC曲線の下側の領域の面積をAUC(Area Under the Curve)と呼び、その面積の大きさによって分類器の性能の高さ、つまりその分類器がどの程度うまく正例と負例を分離できているかを評価することができます。 同じデータセットにおける異なる2つのモデルの性能を比較する際や、再現率と偽陽性率のトレードオフを考慮した適切な閾値を決定する際にROC曲線のプロットは有効な選択肢の1つとなります。 model_1よりmodel_2の方がAUCが高いため、model_2の方が性能が高いと判断できる。 不均衡データにおいてROC曲線を扱う際の注意点 実務において2クラス分類器を適用する例はいろいろありますが、例えば「ある工場の部品生産ラインにおいて、ごく稀に発生する不良品を検知する」といった不良品検知や異常検知、不正検知などの文脈においては、正例に対して負例のサイズが極端に大きい、いわゆる不均衡データを扱うことがしばしばあります。 そうした極端に不均衡なデータにおいては、ROC曲線におけるAUCの指標がうまく機能しないことがあります。 具体的にはROC曲線は適合率(Precision)の指標を含んでいないため、「正しく検出できた正例に対して、巻き込んで検出してしまった負例がどれだけ多いか」という点が考慮されず、結果として非常に多くの偽陽性が発生してしまっていてもAUCは高い値を示してしまうことがあります。 先ほどの工場の不良品検知の例で言えば「AUCの値はほぼ1であるにも関わらず、稼働させてみたら不良品の見逃しは少ないものの、大量の偽陽性が発生してしまうことで、後続の目視チェックの工程をパンクさせてしまう。」といった問題を生じさせてしまう可能性があります。 また、しばしばそうした不均衡データにおいてはある程度の性能であればどのモデルのAUCもほぼ1に近い値を取るようなROC曲線がプロットされてしまい、異なるモデルを比較することが難しいこともあります。 PR曲線 ROC曲線と同様な概念としてしばしば用いられることがあるPR(Precision-Recall)曲線は、縦軸に再現率(Recall)、横軸に適合率(Precision)をとったものです。こちらはROC曲線とは異なり、横軸に適合率を採用しているため「正しく検出できた正例に対して、巻き込んで検出してしまった負例がどれだけ多いか」を反映することができます。 そのため、ROC曲線における高いAUCが観測できていても、データに不均衡性がみられる場合はPR曲線もプロットしてみることでモデルの性能に関する示唆を得られる可能性があります。また、ROC曲線におけるAUCについては差がほとんど見られない2つのモデルの性能を比較する場合においても、PR曲線における改善がみられるケースもあります。 データに不均衡性がみられる場合はROC曲線だけでなくPR曲線のプロットも見ておくと良いかもしれません。 実験 簡単なシミュレーションを行うことで、不均衡データにおけるROC曲線とPR曲線の性質について軽く検証してみます。 まず、正例は平均3、負例は平均1で分散は共に1の正規分布にしたがう特徴量Xもつことを既知とした上で、Xが閾値以上のものを正例、閾値以下のものを負例として分類するモデルを考えます。 その上で、正例のデータは1000件に固定し、負例の方は正例の件数の1倍、10倍、100倍、1000倍の4つのパターンで生成し、このモデルの性能を評価することを考えます。 上図は生成されたデータのヒストグラムであり、負例が多くなると潰れて見えなくなっていますが、正例のデータは4パターンで全て同じものを使用しています。 緑色の点線は閾値を示しており、モデルはこれより右側のものを正例、左側のものを負例として識別することになります。 図では閾値1.5の例を示していますが、ROC曲線やPR曲線をプロットする際はこの緑色の点線を左端から右端へ移動させた際の各指標を計算することになります。 # コード例 import numpy as np import matplotlib.pyplot as plt # 正規分布に従う乱数を生成 ratios = [ 1 , 10 , 100 , 1000 ] sample_size_positives = 1000 positives = np.random.normal( 3 , 1 , sample_size_positives) dataset = {} for r in ratios: sample_size_negatives = sample_size_positives * r negatives = np.random.normal( 0 , 1 , sample_size_negatives) dataset[r] = (sample_size_negatives, positives, negatives) # ratio=1の場合 ratio = 1 _, positives, negatives = dataset[ratio] fig, ax = plt.subplots() ax.hist(positives, bins= 100 , color= "r" , alpha= 0.5 , label= "positives" ) ax.hist(negatives, bins= 100 , color= "b" , alpha= 0.5 , label= "negatives" ) ax.axvline(x=threshold_example, color= "g" , linestyle= "--" , label=f "{threshold_example=}" ) ax.legend(loc= "upper right" ) ax.title.set_text(f "{ratio=}" ) ちなみに、グラフにおける各指標のイメージは以下のようなものとなります。 再現率(Recall) = (点線右側の赤色部分の面積) / (赤色部分全体の面積) 偽陽性率(False Positive Rate) = (点線右側の青色部分の面積) / (青色部分全体の面積) 適合率(Precision) = (点線右側の赤色部分の面積) / {(点線右側の赤色部分の面積) + (点線右側の青色部分の面積)} まず、4つのパターンについて、ROC曲線をプロットしてみます。 ROC曲線はどのパターンについてもほぼ同じで、左上に張り付くような形をとっており、AUCはほぼ1に近い値を取っていることがわかります。つまり、ROC曲線はデータの不均衡性の影響をほぼ反映していないことがわかります。 上図の赤い点はそれぞれの曲線上での閾値1.5における点を示したもの # コード例 # ROC curve threshols = np.linspace(- 5 , 5 , 1000 ) threshold_example = 1.5 fig, ax = plt.subplots() ax.plot([ 0 , 1 ], [ 0 , 1 ], linestyle= "--" , color= "g" , label= "random" ) for i, r in enumerate (ratios): recalls = [] fprs = [] sample_size_negatives, positives, negatives = dataset[r] for j in range ( len (threshols)): recalls.append(np.sum(positives > threshols[j]) / sample_size_positives) fprs.append(np.sum(negatives > threshols[j]) / (sample_size_negatives)) ax.scatter(fprs, recalls, color=plot_colors[i], label=f "ratio={r}" , s= 3 ) # 閾値threshold_exampleの時のrecallとfpr ax.scatter( [np.sum(negatives > threshold_example) / sample_size_negatives], [np.sum(positives > threshold_example) / sample_size_positives], color= "r" , s= 15 ) ax.legend(loc= "lower right" ) plt.xlabel( "FPR" ) plt.ylabel( "Recall" ) 次に4つのパターンについてPR曲線をプロットしてみます。 PR曲線についてはROC曲線とは異なり、4つのパターンそれぞれで全く異なる曲線がプロットされており、ROC曲線と比較してデータの不均衡性の影響を反映しやすいことがわかります。 図の赤い点における同じ閾値1.5で見てみると再現率は全てのパターンで同じ値を取っていますが、適合率においてはデータが不均衡になるにつれて低い値を取ることがわかります。 上図の赤い点はそれぞれの曲線上での閾値1.5における点を示したもの # コード例 # PR curve fig, ax = plt.subplots() for i, r in enumerate (ratios): recalls = [] precisions = [] sample_size_negatives, positives, negatives = dataset[r] for j in range ( len (threshols)): recalls.append(np.sum(positives > threshols[j]) / sample_size_positives) precisions.append(np.sum(positives > threshols[j]) / (np.sum(positives > threshols[j]) + np.sum(negatives > threshols[j]))) ax.scatter(precisions, recalls, color=plot_colors[i], label=f "ratio={r}" , s= 3 ) # 閾値1の時の適合率と再現率 plt.scatter( [np.sum(positives > threshold_example) / (np.sum(positives > threshold_example) + np.sum(negatives > 1.5 ))], [np.sum(positives > threshold_example) / sample_size_positives], color= "r" , s= 15 ) ax.legend(loc= "lower right" ) plt.xlabel( "Precision" ) plt.ylabel( "Recall" ) 最後に 今回は2クラス分類における、データの不均衡性とROC曲線およびPR曲線の関係性について掘り下げてみました。 一般的にはモデルの性能評価にはROC曲線を用いることが多い気がしますが、PR曲線の存在も頭に入れておくと、不均衡なデータを扱う際に役に立つかもしれません。 一応適合率を扱う際の注意点を挙げておくと、検証用データにおける負例が本番のデータからダウンサンプリングされている場合、算出される適合率がそのサンプリング比率に依存してしまう点があります。 例えば負例が本番のデータから1/rの件数だけランダムサンプリングされている場合、実際の適合率へ補正する計算式としては、検証用データにおける偽陽性(False Positives)の件数をN(FP)などとして といった形が考えられます。負例が圧倒的に多いケースでは、状況に応じて分母のN(TP)を0に近似してしまって、検証用データにおける適合率に1/rをかけた値を真の適合率としてしまっても良いかもしれません。 また、実務においては偽陽性や偽陰性の数だけで評価するのではなく、その質や誤分類コストの非対称性などにも注意を払うことが重要です。不良品検知の例でいえば、偽陰性の中に深刻な欠陥を見逃している例がないか、不正決済の検知であれば大きな損害をもたらす決済を見逃している例がないか、といったサンプルごとに異なる誤分類コストについても十分吟味する必要があります。 最後となりますが、DataStragetyチームではBASEにおけるデータ分析基盤の改善を一緒に行っていくメンバーを募集しています。ご興味のある方は気軽にご応募ください! A-1.Tech_データエンジニア / BASE株式会社 明日のBASEアドベントカレンダーは @h7jin16 さんと @u_hayato13 さんの記事です。お楽しみに! 参考資料 Wikipedia,Receiver operating characteristic https://en.wikipedia.org/wiki/Receiver_operating_characteristic scikit-learn,precision_recall_curve https://scikit-learn.org/1.5/modules/generated/sklearn.metrics.precision_recall_curve.html Google,Machine Learning Crash Course,roc-and-auc https://developers.google.com/machine-learning/crash-course/classification/roc-and-auc?hl=en
本記事は BASE アドベントカレンダー 2024 の 22 日目の記事です。 はじめに こんにちは! BASE 株式会社 Pay ID 兼 BASE PRODUCT TEAM BLOG 編集局メンバー の @zan_sakurai です。 今年も残すところあとわずかとなりました。今年も多くの方に BASE PRODUCT TEAM BLOG ご覧いただき、ありがとうございました! 1 年間 BASE PRODUCT TEAM BLOG 編集局としていろいろな記事のレビューに関わらせていただきました。 この機会に改めて今年の記事を全て読み返したので、せっかくなので、今年の記事を振り返ってみたいと思います。 今年の記事を振り返ってみます 今年の投稿数は残りのアドベントカレンダーを含めると約 70 記事となりそうです。 BASE PRODUCT TEAM BLOG は、2016 年にスタートしていますが、 投稿数としては 2019 年の 93, 2021 年の 87 に次ぐ、3 番目に投稿数の多い年となりそうです。 有り難いことに執筆いただける方も増えており、技術に限らない、プロダクトマネジメント、マネジメント、採用、イベントレポートなど、幅広いジャンルの記事をお届けできたかと思います 今年の第一号は? 今年の記事は、竹本さんの YAPC:Hiroshima 2024 に参加しました から始まりました。 弊社 CTO の川口さんのスポンサー LT をはじめ、印象に残ったセッションをご紹介いただいており、幸先の良いスタートとなりました。 今年特にはてなブックマークをいただいた記事 今年特にはてなブックマークを頂いた上位 3 記事は以下となりました。 技術に限らない幅広いジャンルの記事をお届けした 1 年でしたが、特にはてなブックマークをいただいた記事は技術系の記事でした。 とてもわかりやすい記事ですので、まだお読みになっていない方はぜひご覧ください! explain だけじゃわからない!MySQL の index の考え方 OIDC って何なんだー?から、実際に使うまで AWS の DMS やブルー/グリーンデプロイを使って MySQL8.0 へ移行した話 イベントレポート・スポンサー・登壇に関する記事が多かった イベントレポート・スポンサー・登壇に関する記事が多かったように見受けられました。 さまざまなイベントに参加し、登壇やスポンサーをさせていただいたかと思います。 余談ですが、投稿いただいた記事以外にも社内での技術交流イベントもこれまで以上に活発に行われていたように感じた1年でした! Fullstack AI Dev & Raycast Summit にスポンサーしました! 社内 LT 会で半年間、毎月登壇をし続けた話 〜実際の登壇内容も添えて〜 BASE BANK, 中小規模のエンジニアイベントにも会場&飲食スポンサーします! Welcome Fintech Community #2 〜Fintech ドメインを深掘る LT 会〜を開催しました! Welcome Fintech Community #1 を開催しました! スクラムフェス大阪 2024 で BASE BANK のメンバーが 2 名登壇しました! PHP カンファレンス福岡 2024 に BASE BANK のエンジニアが登壇しました AWS Summit Japan 2024 に参加しました PHP カンファレンス小田原 2024 に BASE のエンジニアが登壇しました Object-Oriented Conference 2024 に参加しました PHPerKaigi 2024 に 2 名のメンバーが登壇しました PHP カンファレンス関西 2024 にコアスタッフとして参加しました YAPC:Hiroshima 2024 に参加しました 最後に ここでは紹介しきれないくらい多くの記事がありますので、まだお読みになっていない方はぜひ読んでいただきたいです...!!! 今年も多くの方にご愛読いただき、誠にありがとうございました。 また、来年も引き続き多くの方に読んでいただけるよう、記事の品質向上に努めてまいりますので、引き続きよろしくお願いいたします! また、BASE は現在エンジニア積極採用中なので興味があれば採用情報ぜひご覧ください! binc.jp 次回は kikuchi1201 さんの「PHP カンファレンス 2024 のスポンサーしました」が公開予定です。お楽しみに!
はじめに この記事はBASEアドベントカレンダーの21日目の記事です。 こんにちは!BASE株式会社でPay IDチーム プロダクトマネージャーグループのマネージャーをしているkumaです。 Pay IDは、BASEで作られたショップでのお買いものを楽しむためのショッピングサービスで、クイックでスムーズな決済機能と、ショッピングアプリを提供しています。 本記事ではフィンテックドメイン未経験からフィンテックプロダクトのPMを1年半ほど経験して感じた 「プロダクトマネージャーとして何でも面白がる、面白がり力」 の重要性を話します。 ドメイン知識を身につけるのはなかなか大変 プロダクトを成功させるためにPMとして必要なスキルや経験は様々ありますが、その中でも「ドメイン知識を身につけること」は重要な要素のひとつです。 しかし、ドメイン知識を習得するのは容易なことではありません。 座学や業界調査、顧客インタビュー、社内外の専門家との連携、勉強会やコミュニティへの参加など、方法は多岐にわたりますが、いずれも時間と労力がかかります。 そして、これを「やらなければならない義務」として捉えると、なかなかの苦痛を伴う作業になりかねません。 特に、もともと興味関心の薄いドメインや未経験の業界に飛び込む場合、その苦痛は一層増すでしょう。 急に未知のドメインに飛び込む可能性は、誰にでもある しかし、例えば新規事業の立ち上げや部署異動、マルチプロダクト間での担当プロダクト変更、さらには転職など、PMが 全くこれまで興味関心を持っていなかった、新しいドメインを担当する機会は誰にでも訪れる可能性があります。 では、そんな時にどのようなマインドで取り組むと、ポジティブにドメイン知識の習得に取り組んでいけるのでしょうか。 フィンテック未経験から決済関連のプロダクトマネージャーへ 私自身の話をすると、現在、BASE社にてコンシューマー向けのプロダクトマネジメントを主に担当しています。 そのなかでもワンクリックで簡単にクレジットカード決済ができるID決済や、Pay ID あと払い(いわゆるBNPL)という決済プロダクトを主に担当しています。 最近では分割払いの機能をリリースしました。 binc.jp これまでフィンテック業界でゴリゴリPMをやってたかでいうと全然そんなことはなく、 BASEに入ってから初めて経験しました。 私が入社した直後にあと払いのプロダクトがサービスインし、PMとしてアサインされたという経緯でした。 なので、特段フィンテックにとても興味がある状態から入ったわけでもありませんでした。 未経験で飛び込んだこの領域では、複雑なビジネスモデル、関連法令、契約形態など、これまで生きてきた中で一度も触れたことがないようなドメイン知識を理解しつつプロダクトづくりに取り組む必要が出てきました。 一例:クレジットカード業界のビジネスモデル。謎のワードや概念が飛び交う Credit Cardビジネスモデル 画像は以下から引用 https://www.kantei.go.jp/jp/singi/keizaisaisei/miraitoshikaigi/sankankyougikai/fintech/dai1/siryou2.pdf 「どんなことでも面白がる」というマインドセットがあれば、PMはどんなドメインでもやっていける アサインされた当初はこの複雑さに後退りしそうになりましたが、1年半ほどたった今では、同じチームの決済ドメインの業界歴が長いメンバーに「未経験からなんでこんなにやれてるのか謎ですごい」という言葉を頂けたくらいには、 やっていけてます。 なぜそうなれたか振り返ってみると 「どんなことでも面白がる」というマインドセット があったからこそ、だと感じています。 「どんなことでも面白がる」というマインドセット、 「面白がり力」 は言い換えれば 「未知の領域に対して怯えず好奇心を持ち、動きながら考える。そして自分に意味付けしてモチベーションを引き出す力」 だと考えています。 私が考えるこのマインドセットを持つために意識していた3つのポイントを紹介します。 ①:動きながら考える 分からないこと、未知のものに取り組むことは、不安だし腰が重くなります。 つい座学で色々勉強してから、動こうとしてしまいがちです。 私の場合は、特に「決済とは」「あと払い(BNPL)とは」のような研修等は特になく、いきなり実戦投入でした。 でも振り返ると、 分からないながらもとにかく動き回って実際のものごとに触れながら考えたほうが、結果得るものが大きかった と感じています。 やっていくと自然と疑問が湧き出てくるので、都度その疑問を解消していけば、自ずと身についてきます。 ②:分からない状態であることをポジティブに捉える ピュアに 何も「分からない・知らない」状況は最初しかない です。 どんどん知識がつくにつれて良くも悪くも業界に染まっていきます。 無知だからこそ感じる、 業界への純粋な違和感は最初にしか味わえず、ここで感じた違和感が今後のプロダクトづくりに活かせる ので、この状態をポジティブに捉えましょう。 私の場合、とあるアイデアに対しドメインエキスパートのメンバーから「その発想は業界の慣習的にはなかったが、ありかもしれない」と言われたことが何度かあり、プロダクトに取り込めたものもあります。 無邪気に物事を考えられる状態は貴重だと感じています。 ③:自分の興味関心とリンクする部分を発見し、自分に意味付けをする 私の場合、最終的には自分の興味関心や価値観とリンクしないと、継続的にモチベーション高く取り組むことがなかなかできないタイプです。 そのため、発見していく物事に対して、 自分の興味関心や価値観とリンクする部分を見つけて、自分がなぜそれをやるのか、自分に意味付けをすることで、モチベーション高く 取り組むことができます。 私の場合は、決済のことを知っていくうちに、決済は人類の歴史のなかでも大きな発明であることを実感し、これから自分たちが新しい「決済」を作っていくことは、人類の進化に貢献できると感じ、モチベーション高く取り組むことができています。 「面白がり力」があれば、どんなドメインでもPMとしてやっていける このように「どんなことでも面白がる」というマインドを持つことで、ドメイン知識を得る過程が苦痛ではなく、楽しいものに変わると考えています。 ①動きながら考える ②分からない状態であることをポジティブに捉える ③自分の興味関心とリンクする部分を発見し、自分に意味付けをする こうしてみると、割と当たり前のことを言っていますが、このマインドセットがあれば様々な未経験のドメインのプロダクトでもPMとしてやっていけると私は思っています。 最後に もしこの記事を読んで「私も未知の領域に挑戦してみたい」と思っていただけたなら、その第一歩を踏み出す手助けになれば幸いです。 また、私たちも現在Pay IDチームではプロダクトマネージャーを絶賛募集中です! 現在在籍しているPMは私のようにフィンテック未経験のメンバーが多いです。 所属するチームにはドメインエキスパートの方々もいらっしゃるのでとても心強いチームとなっています。 ご興味あればぜひ一度、お話からでも聞いていただけると嬉しいです! open.talentio.com 採用情報 | BASE, Inc. - BASE, Inc. 次回はPay ID ID決済グループのzanさんの記事が公開予定です。お楽しみに!
この投稿は BASEアドベントカレンダー2024 の21日目の記事です。 はじめに SRE Group でエンジニア?をしている@basemsです。 まだ入社して5ヶ月の若輩者です。 「エンジニア?」 という表現は、私の現状は技術そのものではなくチーム運営だったり業務改善といった方向に注力しているので、まだ 「BASEのエンジニアです!」 と自信を持って名乗る程ではないという心理の表れです。 本記事はエンジニアとしてというより、過去の経験からくるプロジェクトマネジメントの面が強い内容になっております。 この記事って何 私自身、BASE以前に何社か渡り歩いていますが、 PJ進行においてエンジニアと非エンジニア間の軋轢だったり衝突は大なり小なり発生しない方が稀 だと感じています。(部署の違うエンジニア間でもあったりする) が、その改善方法や心構えのブログ記事とかはネット上に存在はしてますが、体系立てて学ぶような研修やレクチャーといったものをやる会社はあまり聞かず、PMは元より関わるメンバーそれぞれが 実際に経験して身につけていってねといった傾向がある と感じています。 そこで「自身の頭の中を整理の為にアウトプットする」目的も兼ねて、まずは エンジニアと非エンジニアで同じ方向を向いてPJ進行する為の心構え 的な事を記事にしようと思った次第です。 堅苦しいタイトルになっていますが、 「この辺りを意識してお互い会話してみようよ」 といった内容なので、気軽に読んでいただけると嬉しいです。 当記事の内容は私の経験則なので、当然ながら異論・反論はあるものだと思います。 私自身がエンジニアなので逆の立場からすると 「いや、それは違うだろ」 といったご意見はあって然るべきだと思いますが、 それらをぶつけて改善し後進の為の教材作りができればいいな的に考えていますので歓迎します! (なのでタイトルも 仮 をつけています) ※ちなみに当記事の非エンジニアという括りは、だいたいディレクターやPM・PDMといった方を指しています。 役割が違うだけで上下はないという気持ち まずはざっくりと心構えの話です。 エンジニアと非エンジニア(ディレクターやPM・PDM)の関係性は会社によって違いがあるかと思いますが、上手くいっていないパターンは大体以下のどっちかだと感じます。 エンジニアが強過ぎて、非エンジニアが物申しづらい 非エンジニアが強過ぎて、エンジニアは従わざるを得ない 完全なパワーバランスというのは理想論だと思いますが、それでも 「役割が違うだけで対等な立場である」 という事を今一度心に留めましょう! 相手の意見や要望を”頭から”否定しない 「相手の意見を”頭から”否定しない」 ブレストなんかでは基本ルールだったりしますが、PJ進行においても心がけても良い事だと思います。 “頭から”というのは、まぁ無理なものは無理という状況は現実としてあり得るのですが、 その決断を出す前に 相手がその意見・要望を出した背景や根拠をはっきりしましょう・させましょう! 突っぱねたくなる無茶振りされた経験は大抵誰でもあるかと思います。 それに対して、 「期限的にムリだからできません」 「決まっている事だからやってください・間に合わせてください」 というスタンスだと意地の張り合いみたいな状況になってしまいますが、お互いの置かれている背景を知る事で何らかの糸口なりアプローチが見えてきたり、 エンジニア・非エンジニアというそれぞれの役割の中だけでは得られなかった、思わぬ気付きがあったりします。 すでに今までの経験で「言ってもダメだろう」的な諦めが入っている方もいるかもしれませんが、 こちらも前項と同じく改めて心に留めてみてください! やることの具体化と明確化 ここまでは心構えの話でしたが、ここからは具体的な進行の話です。 身も蓋もない言い方をするなら、プロジェクト進行は無期限じゃないのが普通な以上、 100%の理想を追いつつ関係者全員が飲み込める落とし所を見つける事の繰り返し だと思っています。 その為に 「やることの具体化と明確化」 という、 絶対に達成するべきライン(PJでやる事)をはっきりさせて意志統一をする 作業を行います。 エレベーターピッチがあれば、それを要件定義できるように要素を分解していくイメージです。 記事のテーマに沿って役割別の観点を加えるなら、 非エンジニア:PJをやる意義・やりたい事の具体化と明確化 エンジニア:やる事の輪郭(規模感)を掴む、実現性の「あたり」をつける こういった感じでしょうか。 これはエンジニア・非エンジニア問わず関係者全員が協力してやる事が重要で、 どちらかの観点がないと、大なり小なり 「顧客が本当に必要だったもの」 という有名なあのパターンにハマります。 はじめの一歩:最初に整理するべき項目 整理する項目をあげていきます。 誰が(発案か) 誰の為に 何をやりたいか(させたいか) いつまでに 何を(会社)得られるのか これらはPMやPDM・ディレクターの方は理解しているとは思いますが、 エンジニア側が知る事で、より良い要件定義や設計ができるはずです。 (断言) 項目毎に説明してきます [誰が(発案か)] プロジェクトの発端が 「誰・どこ」から出てきたものか を明確にします 。 この「誰・どこが」によって、他の情報が持つ意味合いが変わってくる事もあります。 「いつまでに」 という項目も、身も蓋も無い言い例を挙げると 不特定多数のユーザーからの要望 →   背景を確認しつつ、なるはやで進めるべき 重要クライアント →   期限は動かせない前提で考える 社内発案の施策 → ある程度の調整はききそう と、この様に温度感が変わってきますし、エンジニアの考え方・動き方も相応に変わるはずです。 (あくまで例なので、社内発案の施策が常に温度感高くないとかじゃ無いです!) また、ここで 「要件や仕様の承認あるいは最終判断をできるのは誰か」 もはっきりしておきましょう。 プロジェクトに対して誰と誰が裁量を持っているかを全員が知る事で、エンジニア・非エンジニアだけでなく部署間の連携もやりやすくなるはずです。 ※プロジェクトの責任の所在がどうこうでは無いので注意 [誰の為に] このプロジェクトのターゲットをはっきりさせましょう + それを可能な限り詳細に掘り下げましょう。 例えば「ショップオーナーさん」をターゲットにしたBASEの新しい機能の提供を考えるとします。 単なる「ショップオーナーさん」だけでは不十分 で、そこから 既存機能を使い慣れている人向け 新規に開設する人にも積極的に使ってもらいたい このどちらであるかを明確にする事で、考慮するべき内容 (エンジニア視点だと特にUI、UX) は大きく変わるはずです。 特に b の要素があるのに取りこぼすと 本当に使ってもらいたい人に使ってもらえないという悲しい結果になります。 [何をやりたいか(させたいか)] 今回のプロジェクトで 達成したい事・ターゲットに何を提供したいかを明確にして、関係者全員の認識を合わせましょう。 そして、それを実現する為に何が必要かを詰めていきましょう。 エンジニアは漠然と 今の手持ち(開発環境やシステム・インフラ等々)での実現性を考えます。 足りない要素があれば整理し、必要であれば課題提起しておきましょう。 (新規か改修か? 社外が絡む要素はあるか? 人以外のコストが発生するのか? 等々・・・) 例えば「AIを使って何かやりたい」という要望があったりしますが、 「そのAIはどこから持ってくるの?」とか「AIに学習させるデータや素材は?」とかが白紙だったりするので、 エンジニアはそういった点を提起しプロジェクトの課題として明確にします。 非エンジニアの方は、こういったエンジニアの指摘は細かいなぁと思われるかもしれませんが、 プロジェクト完遂の為に避けて通れない課題を早めに把握しておく為 と思ってお付き合いください。 [いつまでに] 具体的な日程だけではなく、 その性質をはっきりさせましょう。 ざっと思いつくレベルですが、以下は明確にしておくべきだと思います。 絶対にずらせない or 調整が効くのか 絶対にずらせない場合は、その理由 社外との連携はあるのか 間に合わない場合にどういう不利益を被るのか エンジニア視点だと、前項までの情報から明らかにスケジュールに無理があると判断できるパターンがあったりするかと思います。 そういった時は 非エンジニアの方にも無理な要因を理解してもらい、共通認識を持ってもらう必要があります。 私の場合ですが、以下のように3つの方針で情報整理しています。 プロジェクトを100%達成し、かつスケジュールに収める為に必要な事 お金とか増員とかでなんとかなるならという前提 お金はともかく、 人を”いきなり”倍に増やしても工期は半分にはならない ( 重要) 無理なら無理な理由を徹底的に整理する プロジェクトの100%達成を優先する場合に、絶対必要なスケジュール スケジュールに収める事を優先する場合に、犠牲にしなければならない事(要件や機能) PMの領域に踏み込んでる気もしますが、実装の規模感を知っているのはエンジニアだと思うので、この辺りは PMと協力して考えてみるべきだと思います。 [それにより何を(会社が)得られるのか] 売上だったりユーザー獲得だったりといった、 このプロジェクトが成功すると何を得られるのかをはっきりさせましょう。 直接的な利益ではなくとも サービスの安定性・信頼性の向上も得られるもの として捉えます。 エンジニア視点ではこの要素も重要で、 特に設計においては一つの指標となります。 ユーザーの獲得が目的なのに、スケジュールの都合で 快適性を損なうUIやUXで実装したり 、 売上が出ることを見込めたとしても、 それを帳消しにするようなランニングコストが発生します というのは本末転倒です。 プロジェクトの成果がどう出るのかは全員のモチベーションとなると思うので、これも認識を合わせておきましょう。 おわりに 長々と書いてきましたが、この内容はスタートラインです! 書ける事はまだまだあるので、機会があれば続きを書ければ良いなぁと思っています。 BASEでは幅広い職種で新しいメンバーを募集しております。 興味持たれた方は、カジュアル面談からでもぜひご応募ください! 採用情報 | BASE, Inc. - BASE, Inc. 明日は @zan_sakurai さんの記事です! お楽しみに!
はじめに 本記事は BASEアドベントカレンダー2024 の20日目の記事です。 Pay IDのフロントエンドエンジニアをしているnojiです。 以前執筆した  システムリニューアルでNext.jsのApp Router/Server Actionを使って便利だと思ったところ  に記載したように、Pay IDのアカウント管理画面ではNext.jsを採用し、Server Actionを活用しています。 今回は、そのServer Action導入時に行ったフォームバリデーション周りの取り組みについて紹介します。 react-hook-formを使ったフォームバリデーション アカウント管理画面の特性上、ログインだけでなく名前や住所など登録情報の編集といったフォーム操作が必須です。そのため、以下の要件を満たすフォームバリデーションが必要でした。 入力中 or 入力後にエラーを検出し、ユーザーに即座に通知できる Server Action実行前にクライアント側でバリデーションが実施できる。また、その際に追加処理を挟む柔軟性がある バリデーションエラー処理を簡潔に実装できる これらを実現するため、BASEでも採用実績がある react-hook-form を使うことにしました。 しかし、react-hook-form自体は12/16現在でServer Actionをサポートしているわけではありません。 そこでBETA版の Formコンポーネント を利用し、バリデーション処理とServer Actionの両方を実現できるような実装を行いました。 react-hook-formのFormコンポーネントを利用したフォームバリデーション 従来のreact-hook-formを利用したフォームバリデーション実装は以下のように記述します。formタグのonSubmitにhandleSubmitを渡すことでバリデーション成功時に引数のonSubmit関数が呼ばれます。 const { handleSubmit } = useForm(); const onSubmit = ( data ) => { // 処理 } ; < form onSubmit = { handleSubmit(onSubmit) } /> これを、BETA版の Formコンポーネント を使用することで次のように書き換えられます。 < Form onSubmit = { ( { data , formData , formDataJson , event } ) => { // 処理 } } /> onSubmitはバリデーション成功時に呼び出される関数として機能します。引数には、オブジェクト形式、FormData形式、JSON形式でフォームデータを受け取れるため、柔軟な処理が可能になります。 Formコンポーネントには他にもいくつかpropsの項目が用意されているので、気になる方は ドキュメント をご確認ください 実装例 Server Actionとの連携 Server Actionは、 useActionState を組み合わせて使用します。フォームのアクション結果に基づきstateを更新してくれるhooksになります。また、バリデーションスキーマには  zod  を採用し、サーバー側とクライアント側で共通化しました。 以下は、Server Actionとフォームバリデーションの実装例です。 バリデーションスキーマの定義例 // schema.ts export const schema = z.object( { email : z.string() .min( 1 , 'メールアドレスを入力してください' ) .email( 'メールアドレスの形式が正しくありません' ), } ); Server Actionの実装例 // server-action.ts export const serverAction = async ( prevState : any , formData : FormData ) => { try { const data = schema.parse( { email : formData. get ( 'email' ), } ); await fetch ( 'https://backend-api-example.com/v1/hoge' , { method : 'POST' , headers : { 'Content-Type' : 'application/json' , } , body : JSON . stringify (data), } ); redirect( '/complete' ); } catch (error) { return { code : 'server_error' , message : 'エラーが発生しました' , } ; } } ; Formコンポーネントの実装例 // form-client-component.tsx 'use client' ; import { useActionState } from 'next/server-actions' ; import { useForm } from 'react-hook-form' ; import { zodResolver } from '@hookform/resolvers/zod' ; import { schema } from './schema' ; const FormClientComponent = () => { const [ state , formAction , isPending ] = useActionState(serverAction); const { register , control , formState : { errors } , } = useForm( { resolver : zodResolver(schema) } ); return ( < div > { state?.message && < p > { state.message } </ p > } { /* サーバー側エラーメッセージの表示 */ } < Form control = { control } onSubmit = { ( { formData } ) => formAction(formData) } > < label htmlFor = "email" > メールアドレス </ label > < input { ...register( 'email' ) } type = "email" id = "email" /> { errors.email && < p > { errors.email.message } </ p > } { /* クライアント側バリデーションエラー表示 */ } < button type = "submit" > 送信 </ button > </ Form > </ div > ); } ; FormコンポーネントのonSubmit内でServerActionを実行するようにしています。 Formコンポーネントのpropsでactionも渡すことができるのですが、このactionについては fetchを呼び出すため のもののようで、現状formActionを渡してもServerActionをうまく実行できなかったので使わないようにしました(今後はここが修正されてactionで実行できるようになるかもしれません) カスタム処理の追加例 onSubmit内で非同期処理を実行し、formDataをカスタマイズすることも可能です。 onSubmit= { async ({ formData } ) => { const token = await getToken(); formData. append ( 'token' , token); formAction(formData); } } 使ってみての感想 ServerActionとreact-hook-formのFormコンポーネントを組みわせることで以下の部分が便利だと思いました。 FormコンポーネントのonSubmitがバリデーション成功時しか呼ばれないため、バリデーション制御処理をすべてreact-hook-form側に寄せることができたこと。また、ServerAction実行前にformData等に値を追加したりクライアント側で非同期処理を呼び出したりできること。 useActionStateを利用していることで、onSubmit内でServerActionを呼ぶだけでよく、レスポンスのハンドリングや状態管理についてクライアント側で考えなくて良くなったこと。 おわりに 現時点では、react-hook-formのFormコンポーネントはBETA版のため注意が必要ですが、将来的にServer Actionを完全サポートする可能性も考えられます。その際には今回の実装をさらに簡略化できるかもしれません。今後も動向をウォッチしながら改善を進めていければと思います。 Server Actionとreact-hook-formの連携について紹介しましたが、皆さんのプロジェクトの参考になれば幸いです。 最後に、Pay IDではエンジニアを募集しています。興味がある方はお気軽にご応募ください! 採用情報 | BASE, Inc. - BASE, Inc.
この記事は BASEアドベントカレンダー2024 の19日目の記事です。 同じく19日目には住所の奥深さを知れる Pay IDのエンジニアの金子さんの記事 も公開されていますので、ぜひご一緒にお楽しみください! 自己紹介 プロダクトデザイナーの ichi です。BASEに入社してから21ヶ月が経ち、入社時からBASE BANKチームの一員として金融プロダクトの開発に取り組んでいます。 社内では「お祭り小僧」として、会社主催の忘年会やチーム懇親会の幹事として企画や運営を担当したり、イベントに参加しやすい雰囲気づくりを心がけてきました。 また、今回のアドベントカレンダーのOGP画像もその一環で制作協力させていただきました! 自分が関わるチームのコミュニケーションをスムーズにし、チームをつなぐ「場」の設計に力を注いでいます。 今回は、そんな私がBASE BANKチームで行ってきたチームビルディングの取り組みについてお話しします。 BASE BANKチームについて BASE BANKチーム は、「個人やスモールチームの金融をかんたんにし、挑戦がめぐる世の中に」をミッションに掲げ、金融プロダクトの開発を通じて、ユーザーにとって価値のあるサービスを届けることを目指すチームです。アジャイルな開発体制が特徴で、個々の強みを活かしながら日々プロダクト開発を行っています。 今回のアドベントカレンダーでも何人かメンバーが記事を投稿しているので、よかったらみてみてください〜! Day04 | 若手エンジニアがBASE入社6ヶ月目で感じていること Day11 | 技術目線でみた、PAY.JP YELL BANKのおもしろいところをご紹介! Day12 | 私のキャリアチェンジ:データ分析者からプロダクトマネージャーへ Day20 | 登壇やアウトプットを後押しするnote アジャイル組織のデザイナーとしてのチームビルディングの取り組み 私が取り組んできたチームビルディングの具体例を紹介します。 1. Slack絵文字を活用した自己紹介の場づくり 今まで登録してきたslack絵文字 BASEのSlackチームスペースにはSlack絵文字が5000以上存在し、コミュニケーションに欠かせない文化です。私も現在までに、私は75個以上のSlack絵文字を作成してきました。 入社3日目にまず行ったのは、自分のアイコンのアニメーション絵文字を3種類作ることでした。これによって同僚に覚えてもらいやすくなり、これをきっかけに、「あのアイコンの人だ」「何持ってるの?きりたんぽ?」とはなしかけてもらえて、初対面の人との会話やつながりも自然と生まれました。 アニメーションgifについては、元々自己紹介ツールの一環として LINEスタンプ を作成していたので、腕だけ動かすアニメーションを作るのはそこまで大変ではなかったです。 2. Figma勉強会での知識共有 入社5ヶ月目には、BASE BANKメンバーを対象にしたFigma・FigJamの勉強会を開催しました。「デザイナーだけが使うツール」だと思われがちなFigma・FigJamを、チーム全員がスムーズに使えるようサポートする取り組みです。 これにより、デザイナーと非デザイナーの間で共通言語が生まれ、プロジェクトの効率も少しは向上したと思います。現在はFigJamをデザイナー以上に使いこなす非デザイナーがたくさんいます。 また、この内容は後日ドキュメント化して、入社時のオンボーディングタスクに組み込みました。 3. 入社オンボーディングクエスト 入社10ヶ月目ぐらいの時に、自分がメンターをする機会があったので、新入社員が入社した際のタスクリスト…通称【入社オンボーディングクエスト】をNotion上に作成しました。膨大な情報量の中で何を優先してインプットするべきかを明確にすることで、新しいメンバーがスムーズにチームに馴染めるようサポートしました。 もとになった神テンプレート をBASE BANK用にカスタマイズし、すでに結構な新入社員・異動社員メンバーに使われています。その度にドキュメント情報をメンターになったメンバーがアップデートしてくれているため、完成度が上がりすぎたフォーマットに。 最近はBASE BANKチーム以外にも広がり、現在はプロダクトDevチームのオンボーディングでも転用されています。 4. ストレングスファインダーを活用したチーム理解 ストレングスファインダー というツールを使い、チームメンバーの特性を可視化するためのフォーマットと、ワークショップの場作りをしました。これにより、メンバーそれぞれの強みが可視化され、仕事の依頼の仕方や、逆に協力して欲しいことなどがシェアできました。 実際に作ったワークショップ用フォーマット(左)と、実際に行われたチームで共通点を見る作業(右) 超有料級のフォーマットになって大満足です。何よりも、ただストレングスファインダーを受けてもらうだけではなく、それをチームとしてどう活かせるかを考えて作ったワークショップがめっちゃ盛り上がってくれたのは感動しました。 詳しくはこちらをご覧ください! basebook.binc.jp 5. ふりかえりの文化醸成 ふりかえり(retrospective)は、入社4ヶ月目から現在まで、私が特に力を入れてきた活動です。最近はふりかえりを強化するような野望(BASE BANK 独自用語。個人目標みたいなもの)を掲げてさらに頑張ってます。 当初、ふりかえりは「なんとなく必要だから」ぐらいの温度感で、チームによって行われていたり、行われていなかったりしていました。 自分は元々「 ファシリテーターを必要としないふりかえりツール 」の開発をしていたことから、チームのふりかえりの底上げが必要だと感じ、FigJamを使ったふりかえりのためのフォーマットの作成やファシリテーターとしての活動を通じて、より価値あるプロセスに進化させました。 最近では、「ふりかえりエヴァンジェリスト」を名乗りチーム向けにふりかえり勉強会を開催し、「ふりかえりの目的や価値」を共通言語にすることで、チーム全体の共通認識を深める活動を行いました。 ふりかえり勉強会 これも超有料級の勉強会になったとおもいます。 実際に目論見通りふりかえりの価値がチームの共通言語にすることができたと思っているし、この勉強会をするにあたって結構調べて作ったので、今度改めて記事にできたらいいなと思ってます。 ふりかえり勉強会の様子 そしてこれからも草の根活動としてふりかえりの継続的な改善を続け、ふりかえりがチームの成長を支える文化としてしっかり根付くよう取り組んでいきます。 まとめ 自分はBASE BANKチームを、個人の個性を最大限に活かせる組織にできたらいいなと思っています。デザイナーとしての視点を活かしながら、チームビルディングに取り組んできました。 その中で、以下の点を意識して活動しています。 メンバーの個性を最大限に発揮できる場を作る ホスピタリティを持って場を作る ワークショップを主催する際は参加メンバーの工数を最小限に抑え、最大の成果を目指す 作成したツールは継続的にアップデートする デザイナーの強みは「誰でもわかるもの」を作れることです。 この強みを活かして、これからもチームの成長を支える場やコミュニケーションツールの開発を続けていきます! 明日の担当は…… BASE BANK事業責任者のyanagawaさん PayIDのフロントエンドエンジニアのnojiさん のお二人の記事が公開予定です。 おたのしみに! join us! 絶賛採用活動中です! まずはお気軽にカジュアル面談しましょう〜🙌 採用情報 | BASE, Inc. - BASE, Inc.
本記事は BASEアドベントカレンダー2024 の19日目の記事です。 はじめに こんにちは、Pay IDのエンジニアの金子です。普段は「Pay ID あと払い」の決済サービスのバックエンドを中心に開発を担当しています。 10月末に「 Pay ID 3回あと払い 」の機能をリリースしました。「Pay ID 翌月あと払い」に続き、新たな決済手段として使用可能になっています。 今回は、Pay ID 3回あと払い機能の実装をきっかけに住所の深さを知ることになったので、それについて話せればと思います。 Pay ID 3回あと払いとは 「Pay ID 3回あと払い」とは、Pay IDアカウントでのお買いものを、分割手数料無料で3回に分けてお支払いいただける機能です。月々のお支払い負担を軽減しながらお買い物を楽しむことができます。 https://payid.jp/blog/3pay より この機能の利用にあたっては、 ショップの「Pay ID 3回あと払い」の審査 購入者の「Pay ID 3回あと払い」の審査 が必要になります。 いずれも分割払いを使用するための審査となっていて、BASEから外部サービスに連携し、審査依頼を出しています。 住所問題 2023年、デジタル大臣が住所の表記ゆれにAIを使用して解決するといった趣旨の発言を発端に、「日本の住所がヤバい」問題がトレンド入りしました。当時の私はそこまで深く考えず、ふ〜んくらいにしか思っていなかったのですが、今回そのヤバさを少し垣間見ることになりました。 ショップ審査にあたっては、規約に同意のもと、ショップ情報を外部サービスへ連携する必要があります。そのうちの1つに「住所」の連携がありました。 ここで住所をそのまま連携できれば良かったのですが、BASEの住所は、都道府県以下が1つのカラムにそのまま保存されています。一方で、連携先には都道府県以下の住所を「市区町村(address1)」「丁番地以下(address2)」のように分割して送る必要がありました。 例えば、BASEの住所は、 〒106-6237 東京都港区六本木三丁目2番1号 住友不動産六本木グランドタワー 37F となっているので、BASEのテーブルのaddressカラムには「港区六本木三丁目2番1号 住友不動産六本木グランドタワー 37F」が入っています。 これを「港区六本木」「三丁目2番1号 住友不動産六本木グランドタワー 37F」のように連携する必要がありました。 住所分割のイメージ図 これを実現する方法としては、 ケンオールの郵便番号検索API や 郵便番号データ 等から、郵便番号を元に「市区町村」「町域」を取得し、一致するところまでで区切るといった対応があります。BASEでは独自の住所ライブラリを作っており(データ元は郵便番号データと同様)、これを使用して住所分割を行いました。 市区町村(address1) → 「市区町村」& 「町域」 丁番地以下(address2) → それ以下 「市区町村」「町域」とショップが入力した住所が完全一致とならない場合は、前方一致で一致するところまでを「市区町村(address1)」とし、住所不備の場合はショップに住所情報を確認してもらうといったフローで実現することができました。実装自体はこの仕様で進めることができたのですが、これをきっかけあることを知りました。 1つの郵便番号に複数の「都道府県」「市区町村」「町域」が紐づく住所が存在する 恐らくこれは住所について少し知っている人からすれば当たり前すぎることかもしれないのですが、私はそれまで全く知らずに生きてきました。そして自分が住所の初心者であることを自覚しました。 せっかく住所の入り口に立てたので、その深さを味わいたくなり興味半分で少し調べてみました。以降の説明は2024年12月12日時点の郵便番号データを元に調査したものになります。 (データ元: https://www.post.japanpost.jp/zipcode/dl/kogaki-zip.html ) 前提として、住所は郵便番号検索をすると大きく分けて「都道府県」「市区町村」「町域」と分かれて表示されます。 よくwebサイトで郵便番号を入れた際に自動補完されるのは、郵便番号から「都道府県」「市区町村」「町域」を特定できるためです。この際、1つの郵便番号に対して複数の結果が出てくる地域が存在するということになります。 1つの郵便番号に複数の町域が紐づくケース 1つの郵便番号に複数の「町域」が紐づくパターンがあり、このケースに該当する郵便番号は1,548件も存在することが分かりました。全体の約1.3%に当たります。 以下の表に紐づく町域が多い順TOP3を並べていますが、 452-0961 の「愛知県清須市〜」にはなんと 66の町域が紐づいていました 。 紐づく町域の多い順 TOP3 郵便番号 都道府県 市区町村 町域 紐づく町域数 452-0961 愛知県 清須市 春日明河原、春日一番割など 66 480-1103 愛知県 長久手市 岩作石田、岩作井戸ケ根など 65 441-0302 愛知県 長久手市 御津町下佐脇、御津町下佐脇新屋など 46 1つの郵便番号に複数の市区町村が紐づくケース 今度は、1つの郵便番号に「市区町村」が紐づくケースです。「町域」よりも1段階大きい括りになります。このケースに該当する郵便番号は134件存在し、全体の約0.1%でした。 紐づく市区町村の多い順 TOP3 郵便番号 都道府県 市区町村 紐づく市区町村数 950-0000 新潟県 新潟市江南区、新潟市西蒲区など 6 861-0000 熊本県 熊本市西区、熊本市東区など 5 636-0000 奈良県 生駒郡三郷町、生駒郡平群町など 4 1つの郵便番号に複数の都道府県が紐づくケース 驚きだったのが、 1つの郵便番号で複数の都道府県が紐づく地域 が存在しました。郵便番号を入れただけでは都道府県を特定できないということです。これらの地域の場合、郵便番号入力後の自動補完に頼らず全て自分で入力しなくてはなりません、大変です。このケースに該当する郵便番号は3件で全体の約0.002%でした。 紐づく都道府県の多い順 郵便番号 都道府県 紐づく都道府県数 498-0000 愛知県、三重県 2 618-0000 京都府、大阪府 2 871-0000 大分県、福岡県 2 超高層ビル番号 今までのとは対照的に、 1つのビルで複数の郵便番号を持つ場所 もありました。このケースは同じビルにありながら階層によって郵便番号が異なります。 郵便区番号が3けたの郵便区内に所在し、配達物数が特に多い超高層ビル(テナントビルに限ります。)については、ビルと階層を表す郵便番号を設定することがあります。 引用: https://www.post.japanpost.jp/zipcode/zipmanual/p04.html 例えば、新宿区にある西新宿パークタワーは1つのビルに対して53の郵便番号が紐づいていました。 西新宿パークタワーから一部抜粋 郵便番号 都道府県 市区町村 町域 163-1001 東京都 新宿区 西新宿新宿パークタワー(1階) 163-1002 東京都 新宿区 西新宿新宿パークタワー(2階) 163-1003 東京都 新宿区 西新宿新宿パークタワー(3階) 163-1004 東京都 新宿区 西新宿新宿パークタワー(4階) 1つの郵便番号で都道府県を跨ぐ地域があるかと思えば、1つのビルに対して複数の郵便番号が紐づく場所もあるといったように、単純に郵便番号と地域が1対1の関係ではないということが分かっていただけたかと思います。 これまでの事例は、郵便番号を元に住所を分割したいとなった際に問題になりそうな例で、「日本の住所がヤバい」と言われている所以はもっとあります。 春日部市八丁目〜の「八丁目」は固有名詞 兵庫県明石市の「和坂」は隣接する地域でありながら「かにがさか」と「わさか」と読む地域が存在する などなど…ここから先に足を踏み込むと更に険しい山道が続いていそうなので、この辺りで身を引くことにします。 おわりに 現状の住所分割に関しては、1つの郵便番号に複数の地域が紐づいているケースは件数が多くないこともあり、手動で対応をしていますが、今後システムで解決できるように調整していく予定です! 最後に、Pay ID では一緒にプロダクトを作るメンバーを募集しています。興味のある方はぜひ採用情報などもご覧ください。 binc.jp 明日はnojiさん、yanagawaさんによる記事です。お楽しみに!
この記事は BASEアドベントカレンダー2024 の18日目の記事です。18日目は shota.imazeki さんの記事( INFORMATION_SCHEMAを用いたBigQueryデータ監視 - BASEプロダクトチームブログ )も公開されていますので、ぜひご一緒に読んでみてください。 はじめに こんにちは、はじめまして。BASE の Feature Dev1 Group でバックエンドエンジニアをしている @meihei です。 2024年6月に入社してから半年が経ち、この間に多くの経験を積ませていただきました。 この記事では、私の日常の開発業務、特にローカル環境で PHPUnit を自動的・継続的に実行している方法についてお話しします。 息を吸うように PHPUnit を実行する さて、具体的に何をしているかと言うと、PhpStorm で PHPUnit 実行後の「Rerun Automatically」をオンにしています。 これにより、ソースコードを変更すると自動的にテストが再実行されるようになります。 つまり、コードを書いて一息つくたびにテストが実行される状態です。 symfony/demo を使って自動的にテストが再実行している様子 どうやって実現するのか 息を吸うように PHPUnit を実行するためには、PhpStorm でのテストが実行される設定をする必要があります。また、テストが高速で実行される環境も必要不可欠です。 PhpStorm でテスト実行を設定する Edit Configuration でテストに名前をつける PhpStorm の「Run/Debug Configuration」から「Edit Configuration」を開くと、新しいテスト実行の設定ができます。 www.jetbrains.com または、▶ Run ボタンを押すだけで、自動的に名前がついたテストを実行できます。 compound で複数のテストをまとめる compound 機能を使うと、複数のテストを同時に実行できます。 www.jetbrains.com これは、異なる phpunit.xml のテストを同時に実行したい場合や、特に遅い部分のテストを並列実行したい場合に便利です。 テストの実行速度を高速化する テストの実行速度を上げることで、開発効率が向上します。実行速度を向上させるには「テスト自体を高速化する」「実行するテストの量を減らす」という2つの方法があります。 テスト自体を高速化するには、テストケースをシンプルに保ち、スタブやモックを適切に活用します。 実行するテストの量を減らすには、テスト実行のスコープを狭めたり(詳細は後述)、型やバリューオブジェクトで制約を表現して不要なテストを避けたり *1 します。 テスト実行の適切なスコープを設定する テストの実行速度を上げるには、スコープを適切に狭めながら、必要十分なテストカバレッジを維持することが重要です。 単体テストの適切なスコープは一概に決められませんが、モジュラモノリスを採用している BASE では、1モジュール単位のテストが適切だと考えています。 そのため、私はモジュールごとのテストディレクトリ単位でテストを実行しています。 BASE大規模リアーキテクチャリング / base_rearchitecturing - Speaker Deck スコープを狭める他の方法として、ディレクトリ単位での実行や、 phpunit.xml の testsuites での設定も可能です。 テストを常に実行するメリット 最速でコードの不具合を検証できる PHPUnit を自動的に再実行する最大のメリットは、実装フェーズ以降で、 最速でコードの不具合を検証できる ことです。 これは Shift-left アプローチに通じています。 Shift-left とは、QA(Quality Assurance)の役割を開発プロセスの前方(左側)に移動させる考え方です。不具合の修正は開発プロセスの後工程になるほどコストが大きくなるため、できるだけ早い段階で不具合を発見し、早期にテストを実行することが重要です。 engineering.linecorp.com Shift-left の厳密な定義とは若干異なりますが、できるだけ早期にテストを実行したいという考え方は同じです。 実装フェーズ以降でテストを実行する最速のタイミングは コードを書いた直後 です。PHPUnit の自動再実行によってこれが実現できます。 自然とコードとテストをセットでコミットする これは副次的なメリットですが、自動的なテスト実行により、自然とコードとテストをセットでコミットするという良い習慣が身につきます。 *2 *3 なぜコードとテストをセットでコミットするようになるのかというと、コードを書いた直後に自動的にテストが実行されることで、 git commit を行う前にテストの結果を必ず確認することになるからです。 さらに、テストが失敗している状態ではコミットできない(心理的な)制約が生まれ、テストが通過することを確認してからコミットすることで、後から予期せぬ不具合が発見されるリスクも軽減できます。 おわりに この記事では、私が普段 PHPUnit をずっと実行している話と、その設定やメリットについて説明しました。 Mac がちょっと熱々になることがデメリットですが、今の時期は暖を取れてちょうど良いでしょう。 BASE では、テストが好きなエンジニア・アーキテクチャが好きなエンジニアが在籍していて、ローカル環境のみならず、様々な改善活動をしています。ご興味ある方お待ちしています! binc.jp *1 : 単体テストを書かない技術 #phpcon_odawara - Speaker Deck *2 : commitを積むとは「物語を書く」ことである - Speaker Deck *3 : https://x.com/t_wada/status/904916106153828352
はじめに こんにちは!Data Strategy teamでデータエンジニアをしているshota.imazekiです。 今回はBigQueryでのINFORMATION_SCHEMAを用いたBigQueryデータ監視というテーマでブログを書いていこうと思います。 BigQueryを利用していく上で「クエリが実行できなくなった」「データが古いまま更新されていない」「使われていないデータがある」などの様々な運用上の課題があるかと思います。それをINFORMATION_SCHEMAで使って簡単に解決していこうという話です。 BASEの分析基盤 まず前提としてBASEの分析基盤を簡単に紹介します。BASEではDWHにBigQueryを採用しています。BIツールとしてはLookerを導入していて、Dailyでデータが同期されるようになっています。ワークフロー管理にはAirflowを利用しており、データ連携部分にはEmbulkやS3 エクスポート機能などを使ってます。連携部分の詳細は こちら のテックブログに書いております。BigQueryはLookerから以外にもデータアナリストなどの分析者が直接クエリを実行することもあります。 BASEの分析基盤(簡略版) データ監視における課題 BASEにおけるデータ監視関連の課題は現状、以下の3点があります。 データが連携されていない、更新されないデータが連携され続けている 分析に使われていないデータが連携されている 重いクエリが実行されている 1. データが連携されていない、更新されないデータが連携され続けている データソース側やデータ連携部分で何かしらの障害が起きて、更新されるべきデータが更新されなくなっていることがあります。またこの逆で、プロダクト側での機能の廃止や外部サービスの利用停止などによって、既に更新されることのないデータを更新し続けているパターンもあります。同じデータで更新し続けている状況です。これらについてはユーザー側からの連絡や不定期の点検時に発見することが多く、対応が遅くなってしまうことが問題でした。 TABLE_STORAGE を使えば簡単にテーブル件数を確認することができます。 select distinct project_id, table_schema, -- データセット名 table_name, total_rows, -- テーブル件数 from [project_name].`region-asia-northeast1`.INFORMATION_SCHEMA.TABLE_STORAGE このテーブルを使ってDailyのデータ連携バッチ実行後などにテーブル件数を記録しておくことで、件数の変化を簡単に追うことができます。Window関数などを使って前日との件数比較を行い、同じ件数であればアラートを鳴らすということも可能になります。BASEではLookerのアラート機能を使ってSlackへ通知しています。 2. 分析に使われていないデータが連携されている BASEでは基本的にはELT形式で一度、BigQueryにデータを連携してからデータマートなどへの加工を行っています。その中には特定プロジェクトのために一時的に作られて、PJ自体は終了したがそのまま放置されているデータマートなどもあります(それによって不要になった元データもあるでしょう)。不要になったデータマートやその加工処理はコストパフォーマンスの観点から削除をした方がいいと考えていますが、こちらの発見も遅れることが多かったため、もっと早く簡単に検知できないかと考えました。 JOBS_BY_PROJECT (ORGANIZATIONなどもある), TABLES を使うことで使われてないテーブルを洗い出すことができます。 JOBYS_BY_PROJECTを使って実行されているクエリから、どのテーブルが利用されているかを見ることができます。クエリの実行履歴については一定期間以内(180日程度)までしか保持されないのでもっと遡りたい時は実行履歴を保存しておくと良いでしょう。 select j.user_email, j.query, t.project_id, t.dataset_id, t.table_id, ROUND (j.total_bytes_processed / ( 1024 * 1024 * 1024 ), 2 ) bytes_processed_in_gb, -- 処理されるGB数も分かる from [project_name].`region-asia-northeast1`.INFORMATION_SCHEMA.JOBS_BY_PROJECT j cross join unnest(j.referenced_tables) t where -- 成功したREAD文のみを取り出す j.job_type = ' QUERY ' and j.statement_type = ' SELECT ' and j.state = ' DONE ' and j.error_result is null その後、TABLESから全テーブルの一覧を取り出し、差分を見ることで使われていないテーブルを洗い出すことができます。 select distinct table_catalog, -- プロジェクト名 table_schema, -- データセット名 table_name from [project_name].`region-asia-northeast1`.INFORMATION_SCHEMA.TABLES 3. 重いクエリが実行されている 前提としてBASEでは以下のような制限をBigQueryに設定しています。 GCPプロジェクト単位でクエリ走査量の制限: Query usage per day ユーザー単位でのクエリ走査量の制限: Query usage per day per user またLookerからの1クエリ単位での走査量の制限も入れています。 これらの制限によって基本的には重いクエリが実行されてもコストが莫大に膨れ上がったり、コスト制限によって利用できなくなったといった問題は防げています。しかし、1ユーザー単位で見た時には数回重いクエリを実行しただけでBigQueryが利用できなくなってしまうこともあり、コスト的にも分析体験としても好ましくありません。 したがって重いクエリが実行された場合にそれを通知できるようにし、クエリの改善もしくはデータマートの構築などを提案できるようにしました。 2同様、 JOBS_BY_PROJECT (ORGANIZATIONなどもある)を利用することで重いクエリを洗い出すことができます。 select j.user_email, j.query, t.project_id, t.dataset_id, t.table_id, ROUND (j.total_bytes_processed / ( 1024 * 1024 * 1024 ), 2 ) bytes_processed_in_gb, -- 処理されるGB数も分かる from [project_name].`region-asia-northeast1`.INFORMATION_SCHEMA.JOBS_BY_PROJECT j cross join unnest(j.referenced_tables) t where -- 例: 100GB以上の走査量を取り出すクエリ (j.total_bytes_processed / ( 1024 * 1024 * 1024 ) > 100 Lookerのアラート機能 を用いることで、定期的に(数分単位や1時間単位など)重いクエリが実行されたかを確認し、Slackに通知することにしています。 またLookerからはサービスアカウントを用いてクエリを実行する都合上、誰か1人がLookerからQuery usage per day per userの上限に引っかかるくらい使ってしまうと他のLookerユーザーにも影響が出てしまいます。その検知も簡単に行えるようにしました。上記の JOBS_BY_PROJECT テーブルなどを日単位でtotal_bytes_processedをsumしたりすることで設定した閾値を超えた場合に通知することができるようになります。 おわりに INFORMATION_SCHEMAを用いることで分析基盤の運用がより便利になる事例を紹介してきました。正直、Data observabilityツール( elementary など)を用いた方がよりDWHの改善などには繋がると思いますが、気軽に始めるならINFORMATION_SCHEMAを使った方が良いかと考えています。BASEでもelementaryの導入などは進めていきたいとは思っていて、このような分析基盤の改善を一緒に行っていくメンバーを募集しています。ご興味のある方は気軽にご応募ください! A-1.Tech_データエンジニア / BASE株式会社 明日のBASEアドベントカレンダーはosashimiさんの記事です。お楽しみに!
この記事は BASE アドベントカレンダー 17日目の記事です。 はじめに Pay IDアプリチームの小林です。「ショッピングアプリ Pay ID」のAndroidアプリを開発しています。 今回はJetpack ComposeでConstraintLayoutを使った事例紹介をしたいと思います。 ConstraintLayoutとは まず、ConstraintLayoutとは、複雑なレイアウト、特に相対的なレイアウトを書きたいときに使うことになるクラスです。 始まりはComposeによるレイアウト作成の前、XMLにてレイアウトを書いていた頃に登場しました。 その頃はXMLだったので、複雑なレイアウトはネストして書かなければならず、分かりづらいものになっていました。 また、ネストしたレイアウトはパフォーマンス上の問題もありました。 フラットにネストなく書けるようになるConstraintLayoutは、設定のとっつきづらさはあったものの、ネストによる分かりづらさに比べたら有効なLayoutだったと感じています。 ConstraintLayoutだけで使えるレイアウト表現もありました。 その後、Composeが誕生しKotlinコードでレイアウトが書くことが可能になりました。 ネストによる分かりづらさはメソッド分け等のKotlinコードで書くことでできる手法である程度解消することができ、パフォーマンス問題もComposeはネストしても悪化しないようになりました。 注: View システムでは、大規模で複雑なレイアウトを作成する場合、 ConstraintLayout を使用することが推奨されていました。 これは、ネストされたビューよりもフラットなビュー階層の方が パフォーマンスに優れているためです。 しかし、深いレイアウト階層を効率的に扱える Compose では、 このような懸念はありません。 ( https://developer.android.com/develop/ui/compose/layouts/constraintlayout?hl=ja より転載) 前述したConstraintLayoutだけで使える表現も、RowやColumn等でも表現できるようになったりと、ConstraintLayoutを使わなければならない部分は大きく減りました。 (例: ガイドラインという表現方法の場合) 注: Rows と Columns で同様の効果を実現するには、Spacer の使用をご検討ください。 ( https://developer.android.com/develop/ui/compose/layouts/constraintlayout?hl=ja#guidelines より転載) ComposeでConstraintLayoutを使ったケース そのため、普段はComposeでConstraintLayoutを使わないのですが、Pay IDアプリで表現したいレイアウトを実現するためにConstraintLayoutを使っている箇所がいくつかあります。 1つのViewに合わせて、縦と横のラインを揃えたい場合 そのまま、ConstraintLayoutの用途である相対的なレイアウトを作りたかった場合です。 このようなレイアウトがあり、チェックアイコンの中央ラインに日付やラベルを揃えたいのとチェックアイコンの上下にある緑や灰色の棒UIも、チェックアイコンの縦真ん中に揃えたいものでした。 これらをコードに起こしてたものがこちらです。 ConstraintLayout( modifier = Modifier.fillMaxWidth(), ) { val ( topLine, checkicon, bottomLine, dateRef, // ... 他レイアウトの制約名 ) = createRefs() LowerHalfBar( // 棒の下半分 modifier = Modifier.constrainAs(topLine) { start.linkTo(checkicon.start) end.linkTo(checkicon.end) top.linkTo(parent.top) }, viewData = viewData, ) CheckBoxPaid( // チェックアイコン modifier = Modifier.constrainAs(checkicon) { start.linkTo(parent.start, margin = 16 .dp) top.linkTo(parent.top, margin = 16 .dp) bottom.linkTo(parent.bottom, margin = 16 .dp) }, viewData = viewData, ) UpperHalfBar( // 棒の上半分 modifier = Modifier.constrainAs(bottomLine) { start.linkTo(checkicon.start) end.linkTo(checkicon.end) bottom.linkTo(parent.bottom) }, viewData = viewData, ) Date( // 日付 modifier = Modifier.constrainAs(dateRef) { start.linkTo(checkicon.end, margin = 16 .dp) top.linkTo(checkicon.top) bottom.linkTo(checkicon.bottom) }, viewData = viewData, ) // ...他レイアウト } ConstraintLayout自体の書き方の説明は 公式の説明 が詳しくわかりやすいので省略しますが、各レイアウトのlinkToにチェックアイコンの上下やstart/endを設定することで、チェックアイコンに対しての中央に各レイアウトを配置することができています。 BoxやRow/Columnを使って表現しようとしたのですが、 チェックアイコン+日付等をRowで囲んで上下の棒を左寄せでアイコンの中央っぽくすると、スクリーン設定等でズレる チェックアイコン+上下の棒をColumnで囲んで、日付をそれらの中央に指定すると、微妙にアイコンの中央からはズレてしまうし、上下の棒がつながって見えない という問題があったりして、ConstraintLayoutを使うことで解決しました。 レイアウトを重ねたい場合 2つ目はUIを重ねて表現したいときです。 こちらのレイアウトなのですが の2つのレイアウトの後ろに、白背景のSpacerを重ねて表現しています。 ConstraintLayout { val (text1, image2, background3) = createRefs() Spacer( // 白背景を重ねる。先に定義する modifier = Modifier .background(Color.White) .constrainAs(background3) { top.linkTo(text1.top) // テキストと同じ高さにする bottom.linkTo(text1.bottom) // テキストと同じ高さにする start.linkTo(text1.start, 24 .dp) // テキストの少し右側から end.linkTo(image2.end, 12 .dp) // 画像の少し左側まで表示する width = Dimension.fillToConstraints height = Dimension.fillToConstraints }, ) Text( modifier = Modifier //...略 .constrainAs(text1) { top.linkTo(image2.top) //画像との中央揃えの設定 bottom.linkTo(image2.bottom) //画像との中央揃えの設定 start.linkTo(parent.start) end.linkTo(image.start) // 画像の左に配置 }, text = "最近の購入品" , ) Image( modifier = Modifier // ...略 .constrainAs(image2) { end.linkTo(parent.end) }, painter = //..., contentDescription = "画像" , ) } } このようにすることでテキストと画像の間の微妙な隙間を白背景で埋めることができます。 Spacerは何も定義しないと0dpでlinkToで幅や高さを設定しても描画されません。 そのため、width/heightでDimension.fillToConstraintsを設定することで、constrainAsで設定したそのUIが表示されていい領域いっぱいまで高さや幅を広げてくれます。 おわりに 今回はPay IDアプリでJetpack Compose上でConstraintLayoutを使っている事例を2つ紹介しました。 ここまで見ていただいた方の助けになったら幸いです。 明日は @meihei さんと shota.imazekiさんの記事が発表されます。お楽しみに!
はじめに PHPカンファレンス2024 公式ロゴより BASEのProductDev でエンジニア をしています 遠藤 です。 2024/12/22(日)の日程で開催される PHP Conference Japan 2024にてBASEは ゴールドスポンサーとして協賛します。 phpcon.php.gr.jp BASE は過去にPHP カンファレンスへの登壇並びに協賛をしております。 https://devblog.thebase.in/entry/phpcon2023-announcement https://devblog.thebase.in/entry/phpcon2022 https://devblog.thebase.in/entry/2021/10/05/110000 今年でBASEは通算8回目のスポンサーとなり、PHP コミュニティへの貢献を続けることができ大変嬉しく思います! BASEのスポンサーブースでは来場者の方に楽しんでもらえるような参加型の企画を用意しています! 企画に参加してくださった方にはオリジナルのノベルティと、BASEを実際に利用されていショップ様のコーヒーパックをご用意しております! ぜひ、BASEのスポンサーブースへお越しください! セッションの紹介について BASEで活躍されている Saki Takamachi さんがPHPカンファレンス2024にゲストスピーカーとして「PHP RMは何をする?コア開発者と兼任するメリット/裏話」というタイトルで登壇されます。 fortee.jp BASEの処理の多くはPHPで開発されており、Saki Takamachiさんを含む多くのコア開発とContributorの日々の改善により成り立っております。 そんなPHPの最新verであるPHP8.4でRelease ManagerになったSaki Takamachiさんのお話はなかなか普段聞けない話題なので、当日聞けるのがとても楽しみです! Saki Takamachさんのセッションは「トラック1 - 1F 大展示」で10:55から発表されます! ぜひ足を運んでください! おわりに 今年のPHP Conference Japan 2024の実行委員長が弊社で活躍されている @cocoeyes02 さんが担当しておりBASEで働くメンバー一同、今年の開催を心より応援しております! また、PHP Conference Japan 2024 の当日のチケットは下記よりまだお申し込みいただけます! PHPカンファレンス 2024 - connpass それでは当日、皆様にお会いできることを楽しみにしております。
本記事は BASEアドベントカレンダー2024 の16日目の記事です。 はじめに BASE FeatureDev3Group でWebアプリケーションエンジニア をしている Capi( @ysssssss98 ) です。自分は2024年の10月にBASEへ入社して今月で3ヶ月目になります。前職ではバックエンドエンジニアとしてWebAPIの開発をしたりインフラ(AWS)の管理、小規模チームの開発進捗管理やメンバー育成、オフショア開発、採用業務をしていました。 現在はBASEでプロジェクトに参画し、Webアプリケーションエンジニアとしてバックエンドだけでなく今まで実務経験がほとんどなかったフロントエンドにも挑戦しています!スクラム開発も初挑戦です! 今回は自分が新しい環境で新しい挑戦をする中で非常に効果があった開発チームでの取り組み、ペアプログラミングについて紹介します。ペアプログラミングのやり方や効果が認知され、もっとペアプログラミングが盛んになったら幸いです。 ペアプログラミングとは ペアプログラミング ( 英 : pair programming)はソフトウェア開発の手法の一つで、2人のプログラマが1台のマシンを操作してプログラミングを行う手法。 当初は、2人が1台のワークステーションに向かって作業するものだったが、現在では一人で複数台を同時に使ったり、一台に複数台のディスプレイを使うことも多くなり、具体的なやり方は変わっている。 実際にキーボードを操作してコードを書く人を「ドライバ」、もう1人を「ナビゲータ」と呼ぶ。30分ごとか、単体テストを1つ完成させる度に役割を交替するのがよいとされる。また、1日に一度の頻度でパートナーを変えるのがよいともされている。 引用: ペアプログラミング|Wikipedia ペアプログラミングは1人で実装しないところが特徴です。前職ではペアプログラミングをすることはめったになく基本1人で実装していることが多かったです。また、手を動かす人が定期的に変わるのも特徴です。複数の人間が同じことに向き合うため知識の共有や実装方法の教育によるスキル向上が見込めます。 どんな風にペアプログラミングをしているのか 次に自分のチームがどんなツールを使ってペアプログラミングを行なったのか紹介します。自分のチームが使ったツール以外でも同等の環境を準備できるので代替ツールも紹介します。 自分のチームが利用したツール 1. Gather バーチャルオフィスツールです。バーチャルオフィス内にアバターが出てきて会話する際は特定の場所にアバターが集まることで自動的にビデオ通話が開始します。画面共有やチャットもできます。 2. Figma(FigJam) オンラインデザインツールです。デザインを作るための使用が多いですが、今回私たちのチームはホワイトボートのような用途で使用しました。ペアプログラミング中のアプリケーション設計を視覚的に共有するためです。 3. Code With Me JetBrains社が提供するペアプログラミングツールです。リンクを共有すると複数のユーザーで同じコードを編集できます。どのユーザーがコードのどこを編集しているか表示できたり、ユーザーのコードを追従可能です。 代替ツール 自分たちのチームが普段使っているツール以外でもペアプログラミングをリモートで行うことは可能です。前職や過去に自分が使ったことのあるツールで今回の構成を代替できるものを紹介していきます。 1. Slack Slackのハドル機能が使えます。チャンネルに入ってるユーザーはいつでも入れますし、チャットも可能です。 2. Miro オンラインホワイトボードツールです。付箋や図形を使って視覚的に情報を共有できます。 3. Live Share Visual Studio Codeの拡張機能です。Code With Meと同様にユーザーがコードのどこを編集しているのか視覚的に確認することができます。 marketplace.visualstudio.com ペアプログラミングを通じて得られたもの 自分が持っていない実装の引き出しと技術の知識 自分が特にこのメリットを体感したのはフロントエンドの実装です。具体的にはCSSの書き方やReactのコンポーネント分割、TypeScriptによるデータフェッチ処理でした。 個人的には特にCSS実装がペアプログラミングの効果を発揮したと感じています。なぜなら自分はCSSに苦手意識を持っていたからです。タスク着手時はどうやったらデザイン通りのUIが実現できるのか、バックエンドから受け取ったデータの値によって変わる動的なUIをどこで記述するのかなど不安でいっぱいでした。 自分がドライバ(実際にコードを書く側)の時はフロントエンド経験豊富な方にナビゲータ(実装の助言を行う側)を担当していただき、どんなCSSを当てていくのかを丁寧に解説していただきながら納得感を持って手を動かしていきました。時には言葉だけでなくその場で実装を代わっていただくことでコードベースの共有をいただきました。また、自分がナビゲータの時は疑問に思ったらすぐに「なんでこのスタイルの当て方をしてるんですか?」、「このスタイルを当てると〇〇になるという認識であっていますか?」という質問を積極的に行い、経験豊富な方の思考を言語化しながら進めることができました。その中で自分の中に新しい引き出しが増えたり相手の考え方が共有されていったと考えています。 最初のフロントエンド実装タスクでは約半日ほどナビゲータの方に付きっきりで実装を見てもらっていました。しかし、2つ目のフロントエンド実装タスクでは1時間~1時間半の時間のみ見ていただくだけで他の時間は自力で実装を進められるようになりました。 個人的にはアウトプットよりインプットの方が少し多かったので今後のフロントエンド実装では自分がナビゲータになる頻度を増やし、アウトプット量の割合をより増やしていきたいです。 コードレビューの時間短縮 コードを書いてるだけでは認識を合わせるのが難しいものもあるかもしれません。自分が経験したのはバックエンドのクラス設計やクラス間でのデータの流れをペアプログラミング中のコーディングだけで伝えようとすることが難しかったです。 その時は対策としてペアプログラミング中にFigmaやMiroを使い、図解することで認識合わせを円滑に進めました。「ペアプログラミングなのだからコーディングをお互いに行う」というこだわりを持つ必要はないです。コーディングしながら別の方法で知識の共有をした方が有効な場合は別の方法を積極的に使うことも大事だと私は考えています。 心理的安全性の向上 ペアプログラミングをするとコミュニケーション頻度が増えます。実装をしながらお互いの技術知識の共有ができます。他にも休憩中に雑談をすることもあります。相互理解によってチームメンバー同士で会話することのハードルが下がります。 自分自身、最初はGatherに入ってペアプログラミングすることに躊躇してました。しかし最近は自分からGatherに入ることをチーム全体に共有できるようになったり開発メンバーがペアプログラミングしましょう!という提案を自然とできるようになりました。 また、チーム全員で開発のレトロスペクティブ(振り返り)を行った際に「ペアプログラミングを続けたい」という意見は多かったです。 ペアプログラミングの注意点 ペアプログラミングはたくさんのメリットがありますが、一方でデメリットもあります。次に自分が経験したデメリットを紹介します。 一時的にチーム全体でタスク消化に使える時間が減る ペアプログラミングは基本的に1つのタスクを2人以上で行います。つまり本来ペアプログラミングをしなければ2人で違うタスクを並行で進められるのにあえて2人で1つのタスクを対応することになります。知識の共有やメンバー間のスキル差分がなくなっていけばこのデメリットは相殺できますが、ペアプログラミング導入序盤は速度が落ちる可能性が高いです。 ペアプログラミング後の疲労 これは私が実際にペアプログラミングをして一番感じたことです。結論から言うと脳にかかる負担が大きいです。 ペアプログラミングはドライバ(実際にコードを書く側)、ナビゲータ(実装の助言を行う側)共に情報をインプットする量、アウトプットする量が多いです。コミュニケーション量が多いため相手の意見を理解する力、自分の意見を言う力、瞬発力も必要です。また、ペアプログラミング中は実装に詰まってもすぐ一緒に調べることができて解決スピードも速いです。そのスピード感からストレスフリーで実装を行うことができます。しかし、その分短期間で扱う情報量が多いです 個人的な所感として、ペアプログラミングは最長でも1時間に1回休憩を入れたほうが良いと思いました。 おわりに 以上がペアプログラミングをして自分が体験したものになります。「ペアプログラミングって聞いたことあるけどどうやったらいいんだろう?」と考えている方に何か1つでも参考になったら嬉しいです。また、新たにメンバーを受け入れる環境やメンバー全員の認識合わせを円滑にしたいチーム、メンバー同士のスキル向上を目指すチームにとってペアプログラミングの実施が有効な一手になったら嬉しいです。 BASEでは現在Webアプリケーションエンジニアを積極採用中です。興味ある方は是非ご応募ください。 採用情報 | BASE, Inc. - BASE, Inc. 明日はPay IDチームの小林さんによる記事です。お楽しみに!
この記事は BASE アドベントカレンダー 16日目の記事です。 はじめに こんにちは、CSE Group ※1 で社内の業務効率化の開発をしている上野です。 アドベントカレンダー15日目は @miyachin_87 さんの記事でした、みなさんもうお読みでしょうか?私は特に業務効率化の開発をしているので Notion での自動タスク生成の話はとても参考になりました。まだの方はぜひお読みください! devblog.thebase.in さて、アドベントカレンダー16日目の本日は、レポートシステムの安定稼働をするための取り組みについて紹介します! レポートシステムとは BASEではJ-SOX対応の一環として、 BASEショップの売上金と決済サービス側の入金データ BASEショップの売上金とBASE側の決済データ BASE側の決済データと決済サービス側の入金データ 決済サービス側の入金データと実際の入金額 これら4点の整合性が取れていることを確認することで財務報告上の信頼性を担保しています。 月初処理は月次でこれらの整合性が取れていることの確認を行うことを指しています。 (※2 より引用) この整合性を取るためのデータ収集と、各種確認のクエリ実行を行うのがレポートシステムです。 具体的には以下のような処理を行っています。 BASE の DB を日時で取得し Amazon Athena にデータを登録する 各決済システムからデータをダウンロードし、Amazon Athena にデータを登録する クエリでデータを突き合わせ、整合性が取れていることを確認する 簡単なアーキテクチャ図はこのようになります。 レポートシステムの問題点 上記の処理のうち 1. の DB のデータを Embulk で Athena に取り込む処理で、次のような課題がありました。 実行時間が長くなっていった ECS で分散処理をしていましたが、それでも午前2時に実行を開始して、午前9時過ぎまでかかることもあり、朝9時の日次レポートに処理が間に合わないことも多くなっていました。 また、何かしらで処理が詰まってしまい、翌日まで実行が完了していないなども目立ってきていました。 エラーが頻発するようになった 上記の遅延が目立つ様になってきたころから、エラーとなることが多くなっていました。(翌日までかかっても終わらず処理がバッティングする、コピーされた DB の削除処理が正常に動作できない、など) また、リカバリも再実行に 6~8 時間ほどかかってしまうため、朝にエラーに気づいて夕方にようやくデータが渡せる状況になってしまっていました。 Embulk のメンテナンスなどに工数が割けない そのため、処理速度の向上とメンテナンスコスト削減のため、マネージド・サービスを利用したETL機構に置き換え、安定稼働を目指しました。 技術選定 BASE では DB に Aurora MySQL を使用しています。 Aurora MySQL では 2022年に S3 にエクスポートする機能がリリースされていたため、これを使用することにしました。 https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/export-cluster-data.html https://dev.classmethod.jp/articles/aurora-cluster-export-s3/ その他、AWS Glue で Aurora のデータを出力する方法などもありそうでしたが、BASE 社内でも Data Platformチームでの利用実績 もあったことや、今までのレポートシステムが Step Functions と Lambda を SAM で管理をしており知見があったため、Lambda で Aurora S3 Export を実行する形を選定しました。 検討したこと データのエクスポートの技術を選定しましたが、具体的な方法について以下のようにな選択肢がありそれぞれ検討しました。 BASE の本番アカウントで DB を直接エクスポートする 今まで通り BASE の本番アカウントからレポートシステムにDBをコピーし、コピーされたDBからエクスポートする 1.の方法でのメリットは、2.の方法と比べるとレポートシステムのアカウントで DB を建てる必要がなくなるため、コストが削減できるという点です。対してデメリットは、レポートシステムのアカウント外での処理をワークフローに組み込む必要があることなど、複雑性が上がってしまう点でした。 2.の方法のメリットは1.の逆で、すべてレポートシステムのアカウント内で完結するため比較的複雑性が低く、CSE チーム内ですべて完結できる点で、デメリットは DB のコストでした。 しかし、2. の方法でのデメリットのコストについては、今までも同様に DB をコピーしているため大幅なコスト増加はないであろうこと、エクスポートの処理のほうが高速になるため、DB インスタンスの立ち上がっている時間の短縮ができ、結果としてある程度のコスト削減は見込めるため大きなデメリットにはならないだろうということで、2. の方法で実施することにしました。 最終的なアーキテクチャ 前述の事項を考慮した最終的なアーキテクチャは以下のようになります。 図式化する関係で簡略化しておりいくつか盛り込めていない点があるので、その点についてもいくつか紹介します。 コピーしてきた DB を一度 Snapshot を取得して、Snapshot に対して、エクスポートを実行している 原因がはっきりしていませんが、コピーしてきた DB クラスタに対して直接エクスポートをするとエラーとなってしまったため Snapshot を経由してエクスポートする処理になりました。 Snapshot からのエクスポートのため、Snapshot が完了した段階で並行処理で DB クラスタの削除処理を実行している DB インスタンスが立ち上がっている時間を極力減らしたい、処理時間を長くはしたくないということで並行して実行するようにしました。これが後述するコストにもある程度効いていそうです。 DB のコピー、Snapshot の取得、Glue crawler の実行など、ある程度時間がかかる処理は Step Functions で処理の完了を監視するような分岐を実装しました。 これらを盛り込んだ Step Functions の全体像はこのようになっています。またこれらのリソースはすべてSAMでコード化し、GitHub上で管理しています。 置き換えの効果 実行速度、安定稼働 約8時間 → 約1時間半 以前は実行時間が実質8時間以上(DB のコピーが午前1時開始、その後の Embulk の処理が2時開始のため)かかっていました。また、週に1回はエラーが出てしまう状態でした。 週に1回エラーになるので、Slackでは、そろそろエラーになると思ったらエラーだった、というやり取りもありました。 置き換え後は2時間かかることはほぼなく、エラーも置き換え直後の微調整以外なく安定して稼働できました。 コストの削減 コスト削減は前述の通りスコープ外だったのですが、実行時間が早くなったことにより Aurora のインスタンスが立ち上がっている時間が大幅に短くなったことや、Embulk での I/O がなくなったことなどの要因で RDS のコストが大幅に削減されました。エクスポートに関わる課金もあると思いますが、削減のほうが上回り、結果としてレポートシステムのアカウントでの全体コストも削減することができました。(9月以前の1年間の平均が$541、10月の途中に処理を置き換えたため10月は $114 とある程度削減され、完全に置き換えられた11月では 約$4 にまで下がっていました。1/100 以下になるとは驚きです。) RDS 単体でのコスト削減効果。脅威の 99% off アカウント全体のコスト削減効果。RDS 以外のコストには大きな変化はない事がわかる。 運用コスト削減 Step Functions と Lambda でワークフローを構築しているため、完全に運用コストがなくなったわけではありませんが、Embulk を完全に利用しなくなったことで、Embulk のバージョンアップなどを気にする必要がなくなりました。 まとめ レポートシステムのデータ取得処理を Embulk から Aurora S3 Export に変更したことで8時間かかっていた処理を1.5時間ほどまで高速化することができました。 それにより毎朝9時の通知にデータを間に合わせることができるようになり、またエラー頻度も削減でき安定的に運用できるようになりました。 また、副次的な効果として、主に RDS のコストを $500/月 から $4/月 と、99% 削減もすることができました。 おわりに 明日のBASEアドベントカレンダーは @eijenson の記事です、お楽しみに! また、BASE では Web アプリケーションエンジニアを積極採用しています。興味持っていただいたら、ぜひご応募ください! 採用情報 | BASE, Inc. - BASE, Inc. ※1 CSE チームについては少し古い記事ですが こちら をごらんください。 ※2 レポートシステムについては以前の こちら や こちら の記事もごらんください。
本記事は BASEアドベントカレンダー2024 の15日目の記事です。 はじめに Pay IDチームでプロダクトマネージャーをしているMIYACHINです。 Pay IDは、BASEで作られたショップでのお買いものを楽しむためのショッピングサービスで、クイックでスムーズな決済機能と、ショッピングアプリを提供しています。 本記事では、Notion FormとAutomation機能を活用して、定型タスクを自動生成するツールを作った話をします。 背景 新しい機能をリリースするときには、「BASE」のショップオーナーや「Pay ID」の購入者に向けて、メールやブログなどを通じて告知を行います。 BASEでは、ユーザーとのコミュニケーションの窓口を担当しているチームがあり、告知をする際は、そのチームと連携をとりながら準備を進めていく必要があります。具体的には、例えばメール配信の場合は、告知日時の調整、原稿の作成、原稿のレビュー、テスト配信などです。 しかし、開発業務に注力するあまり、これらの準備が告知日ギリギリになってしまうことがあります・・・。 一方で、これらの準備は決まりきったタスクの消化であるため、告知日から逆算して自動的にタスクを生成できれば、より計画的に準備を進められるのではないかと考えました。 また、新しいメンバーが入社した際も、これらの複雑なフローを一から覚えるのは負担が大きいため、この課題も含めて解決できる方法を模索することにしました。 検討した実現方法 Webアプリ + Notion API 開発自体は楽しそうだけど、メンテナンス可能な人材が限られることと、Notion APIキーの管理体制が未確立であることから却下。 Google Form + GAS + Notion API Webアプリより実装はシンプルだけど、GASを扱える人材が限られるため、これも却下。 Notion Form + Automation 社内のNotion相談Slackチャンネルで提案されたアイディア。Notion Formは誰でも操作可能で、Notion APIも不要であるためベストな選択肢に。 Notion Formとは? 「Notion Form」とは、Notion内でフォームを作成・管理できる機能で、2024年10月にリリースされた機能です。従来のNotionのデータベース機能に加えて、外部からの情報収集をよりスムーズに行えるようになりました。 これを活用することで、フォームの回答結果をNotionのデータベース上でリアルタイムに管理・分析できるようになります。回答データは自動的にデータベースに反映され、他のNotionページやデータベースと連携することも可能です。また、フォームのデザインも用途に応じて柔軟にカスタマイズすることができ、質問の種類や回答形式の選択、必須項目の設定など、Google Formと同等の運用をすることができます。 作り方 それでは実際にどのようにツールを作ったか、簡単にご紹介します。 1. Notion Formを作成する。 フォームには、告知日、告知媒体、告知コードを入力できるようにします。(「告知コード」が何かはこの後説明します) このフォームの回答は自動作成されるDBに入ってきます。(一旦このDBを「フォーム回答出力先DB」と呼びます。) 2. タスク出力先DBを作成する 「フォーム回答出力DB」とは別に、実際に消化していくタスクを生成する先のDBを作成します。 カラムはタスク名(タイトル)、締切(日付)、担当者(ユーザー)、ステータス(ステータス)、カテゴリ(セレクト)、メモ(文字列)、告知コード(文字列)で作成します。 3. 「フォーム回答出力先DB」にAutomationを設定する 1.で作成したフォームで選択する告知媒体ごとに”Automation”を作成します。 NotionのDBにはAutomationというものが指定でき、Automationにはトリガーと実行内容を定義できます。 トリガーと実行内容は以下のようなものが定義できます。 トリガー 実行内容 DBに新しい行が追加された時 新しい行のプロパティが〇〇だった時 特定のDBに新しい行を追加 メール / Slackで通知 Webhookを送信 このツールでは、トリガーと実行内容を以下のように設定します。 トリガー 実行内容 「フォーム回答出力先DB」の新しい行の「告知媒体」に〇〇が含まれていた場合 「生成タスク出力先DB」に特定の締切日でタスクを生成 具体的な例を挙げると、 新しい行の「告知媒体」に「メール」が含まれている場合、「タスク出力先DB」に「原稿のレビューを受ける」というタスクを告知日から3日前を締切にして作成、といった感じです。 その際に、告知日から逆算してタスクの締切日を設定しないといけないため、Notionを関数を活用して計算します。 if(day(トリガーページ.告知日.dateSubtract(5, "days")) <= 5, トリガーページ.告知日.dateSubtract(3, "days"), トリガーページ.告知日.dateSubtract(5, "days")) ※ このタスクは締切日は告知日から3日前に設定するよう計算しています。締切日が土日になる(day関数の結果が6以上)場合、締切日をさらに2日前倒しして設定しています。(変数が使えないので辛い) 上記のようなAutomationを告知媒体ごとに作成することで、フォーム回答出力先DBに行が追加されるたびに、タスク出力先DBに定型タスクが自動生成されていきます。 運用方法 タスク出力先DBに消化すべきタスクが生成されたら、このDBを各プロジェクトページから読み込みます。しかし、そのまま参照すると他の人が生成したタスクも一緒に読み込まれてしまうため、ここで使うのが最初に設定した「告知コード」です。 自分が生成したタスクには、フォームで回答した「告知コード」が紐づけられているので、その告知コードでフィルタリングして表示することで、自分に必要なタスクだけを参照することができます。 また、メールを配信するためにはどんな作業が必要か、ツールが自動生成してくれるので、新しく入ったメンバーも簡単に告知準備が進められるようになりました。 おわりに Notion FormとAutomationを使って定型タスクの自動生成する方法についてご紹介しました。 今回ご紹介したような定型タスクの消化は、どのチームでも発生するものだと思うので、参考になれば幸いです。 僕の所属しているPay IDチームではエンジニアやプロダクトマネージャー、デザイナーなど幅広い職種で積極採用中なので、Pay IDチームの話を聞いてみたい!という方はぜひカジュアル面談やX(  @miyachin_87  )などでお声がけいただけると嬉しいです! 採用情報 | BASE, Inc. - BASE, Inc. 明日はuenokaさんの記事です!
本記事は 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