TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

576

はじめに この記事は BASEアドベントカレンダー の四日目の記事です。 こんにちは!私は @shiiyannn と申します。現在、メンバーシップ Appの開発に携わっています。メンバーシップ Appはショップオリジナルの「メンバーシップ」(会員制度)を作成することができる機能です。 2023年9月、メンバーシップ Appは大幅な 機能アップデート を遂げました。今回のアップデートでは、ショップオーナーが商品購入時に独自のポイントを付与できるようになりました。付与されるポイントの量は、注文金額にショップオーナーが設定したポイント付与率を掛け合わせて計算されます。 この記事では、ポイント付与機能の開発中に直面した、浮動小数点計算の問題とその解決策についてお話しします。この問題を深掘りすることで、料率計算や金額処理に取り組む開発者の皆さんに有益な情報を提供できればと考えています。 リリース直前に発見した浮動小数点問題 エラーの発生 メンバーシップ Appのリリースをスムーズかつ安全に行うために、私たちは一般公開に先立ち社内で限定公開するという二段階リリース戦略を取っています。社内限定公開中の機能検証テストで、予期せぬエラーが発生しました。具体的には、ショップオーナーが7%や14%の付与率を設定した場合、注文時の付与予定ポイントの計算処理が失敗し、結果として注文が完了できない状況が起こりました。 エラーの内容は、ポイント計算に利用される7%の付与率と、ショップオーナーが設定可能な7%の付与率がシステム上で不一致ということでした。メンバーシップポイントの計算をするために、ポイント付与率というバリューオブジェクトを構築する必要があります。計算用の付与率がコンストラクタの引数として渡される際、バリデーションエラーが発生しました。 以下に示すソースコードは説明のためのもので、本番環境でのメンバーシップ Appと同じ実装ではありません。 class PointRate { private const ALLOWED_VALUES = [6.0, 7.0, 8.0]; function __construct (private float $value) { if (!in_array($value, self::$ALLOWED_VALUES, true)) { throw new Exception ('ポイント付与率が不一致'); } // ... } } エラーの解決 このエラーの根本的な原因は、浮動小数点の精度に関連するものでした。PHPで使用される浮動小数点数(float)は、IEEE754フォーマットを使っていて、その精度には限界があります。例えば、0.7という値は二進数の浮動小数点数として正確に表現することができず、その結果、丸めの誤差が発生します。 私たちのケースでは、ポイント計算のために小数点形式の付与率をパーセント形式に戻す処理があります。in_array関数を使って、パーセント形式に戻した値を元々の7.0と比較すると、(7.0 / 100) * 100 != 7.0という丸めの誤差により予期せぬ結果が生じました。 エラーの解決策は、PHPの公式マニュアルにも記載されている通り、浮動小数点数の比較に一定の誤差を許容することです。具体的には、私たちはイプシロンという丸めの単位を決めて、その許容範囲内であれば、2つの浮動小数点数を等しいと見なす方法を採用しました。 https://www.php.net/manual/ja/language.types.float.php class PointRate { private const ALLOWED_VALUES = [6.0, 7.0, 8.0]; private $epsilon = 0.00001; function __construct (private float $value) { foreach (self::ALLOWED_VALUES as $allowedValue) { if (abs($value - $allowedValue) < $this-> epsilon) { return; // 許容範囲内であればOK } } throw new Exception ('ポイント付与率が不一致'); } // ... } 今回の課題点 リリース直前に発見したこのエラーは、チームメンバー全員の協力により迅速に対処され、幸いにもメンバーシップ機能のアップデートを予定通りにリリースすることができました。エラーが解決された後日、私たちはこのエラーの背後に潜む課題点を改めて考えました。 画面上の表示とシステム出力の内容が一致せず、エラーメッセージを正確に理解するのが困難でした。 エラーの原因を特定するためのデバッグ範囲が広く、問題発生の箇所を効率的に特定することが難しかったです。 リリース直前まで行われていたユニットテストや機能検証テストでは、このエラーを検出できませんでした。 具体的に説明します。 まず、エラーメッセージは直感と矛盾していました。ポイント付与率を設定する画面では、付与率が正確に7%として表示されているにもかかわらず、システム上では、付与率が不一致と判定されていました。そして、注文時のポイント計算においては、なぜか7%と14%の付与率を設定した場合のみ計算が失敗し、他の付与率では注文が正常に処理できました。問題の解決には、まずエラーメッセージの内容とシステム振る舞いとの間にあるギャップの正体を突き止める必要がありました。 リリースの直前に発見されたこの問題は、限られた時間内での対応が必要でした。特に挑戦的だったのは、デバッグの対象範囲が非常に広かったことです。リリース前の機能検証テストでは、メンバーシップポイント付与の全機能を対象としていたため、問題が発生しうる箇所が広範囲に渡ります。例えば、フロントエンドからリクエストされる際、データベースから設計された付与率を取得する際、あるいはアプリケーションロジックでポイント計算する際、これらのいずれかの段階でも付与率が変更された可能性があります。 私たちは、メンバーシップ Appの開発中、アジャイルテストの4象限理論に基づき、ユニットテストと機能検証テストを実施してきました。具体的に、クラスごとに単体テスト、ユーザーストーリーごとに機能検証テストと受け入れテストを含めました。問題が発生した付与率のバリューオブジェクトとポイント計算のユースケースに対しても、ユニットテストが定義されていました。しかし、これらのテストは全て問題なく通過していたにも関わらず、問題の発見と対処は遅れました。 これらの課題点を解決するために 浮動小数点数を扱う際のベストプラクティス 私たちは、今回のエラーを理解できなった主な原因の1つは、浮動小数点数の丸め誤差に対する理解が足りなかったと分析しました。そのため、エラー対処を終えた直後、浮動小数点数の丸め誤差の発生原因とその対処方法に焦点を当てた学習資料をまとめ、グループ内で浮動小数点数(float)に関する知識を補強しました。さらに、将来的に同じようなミスを避けるために、浮動小数点数を扱う際のベストプラクティスを以下のように定めました。 小数形式ではなくパーセント形式を利用するや、小数点以下を切り捨てるなど、可能な限り整数型(int)を使用します。 PHPにおいて、float同士の比較や計算時には、丸め誤差を考慮して、イプシロン(許容誤差)の使用を推奨します。 データベースでは、固定精度のDECIMAL型を利用し、必要な精度を明確に指定します。 問題の発生箇所を効率的に特定する工夫 問題の発生箇所がすぐに特定できない場合、特に広範囲にわたるデバッグが必要となります。そのような状況に対応するために、私たちのチームでは、PhpStormと連携したXDebugを活用してリモートデバッグを行うようにしました。これにより、エラーの発生箇所を特定する際に、PhpStormというIDEからブレークポイントを設定し、ステップイン(step into)やステップオーバー(step over)の機能を効率的に使用することが可能になりました。私たちの開発者のローカル環境では、XDebugはDockerで管理されており、DockerやPhpStormの更新によって機能しなくなる可能性もあるため、私は個人的に月に一度、XDebugが正常に機能するかどうかを確認するようにしています。 今回のエラーを特定する作業を困難にしたもう一つの要因は、バリューオブジェクトの値に対してバリデーションするタイミングに統一されたルールがなかったことでした。例えば、以下のようにバリデーションメソッドをstatic化し、任意のタイミングで実行するような実装が散見されていました。このアプローチでは、初期の有効な値がバリデーションを通過した後でも、後に不正な値に置き換わるリスクがあります。 class A { static function validate($value) {} } A::validate($valid); new A($invalid); この問題に対処するため、私たちのチームではオブジェクトのライフサイクル全体を通じてバリデーションを実施する方法を採用しました。これにより、オブジェクトが常に正しい状態でのみ生成されるようになります。この対処の一環として、staticなvalidateメソッドの使用を禁止するPHPStanルールを定義しました。これにより、バリデーションに失敗した場合でも、直前のインスタンス生成時に利用された値に問題があると素早く特定でき、問題のある値を探す範囲を効果的に縮めることが期待されます。 テストデータのカバレージも重要視 私たちのチームはこれまで、新規クラスを実装する際には必ずユニットテストを書くという文化を築いてきました。しかし、今回の件を受けて、ソースコードのカバレッジだけでなく、テストデータのカバレッジにも注目するようになりました。テストデータのカバレッジを上げるために、特に有効なアプローチとして、等価分割(Equivalence Partitioning)、境界値分析(Boundary Value Analysis)、状態遷移テスト(State Transition Testing)が挙げられます。 メンバーシップポイント付与率を例に取ると、等価分割の思想に基づいて、以下のように様々なグループに分けてテストすることが可能です。 低い付与率のグループ:0.5% 中間のグループ:7% 高い付与率のグループ:15% あるいは、 二進数で表現できるグループ:0.5% 二進数で表現しきれないグループ:7% このようにグループを分けて機能検証テストのテストケースを作成・実施することで、問題をより早く発見する可能性があると考えています。ただし、テストパターンが増えると、当然テストコストも増加します。この点に対処するために、Playwrightで作成した自動E2Eテストも導入しています。今回の問題が発生した付与率はすでに自動E2Eテストでカバーされており、今後も時間とリソースを効率的に活用しながら、自動テストのパターンを増やす計画があります。 終わりに この記事では、メンバーシップポイント付与機能開発中に発見した浮動小数点計算の問題とその解決策についてお話ししました。明日は、@Ayako Tanakaさんの記事です。お楽しみに。
アバター
<この記事はHatena-Blog-Workflows-Boilerplateによって作成されました> 皆さんこんにちは! BASE株式会社 BASE BANK Divisionにて、Dev TOPとしてBASE BANKのエンジニア組織全体を見ている傍ら、このBASE product blogの編集長も務めている @applepine1125 です。 今回は2023年9月に公開された Hatena-Blog-Workflows-Boilerplate をつかって、BASE product blogの新たな執筆フローを作ってみました。 今のBASE product blog 執筆の流れ 現在、BASEでは社内のドキュメントツールとして Notion を使っています。 Notionのデータベース機能などを駆使し、以下のような流れで執筆、レビュー、公開を行っています。 Notion上で下書き記事を執筆 下書き記事のステータスを変更すると自動でブログ編集部員に通知 Notion上でレビュー LGTMをもらったらはてなブログに転記 公開 改善点 Notionを使った記事公開のフローでは、ステータスの変更により自動で通知が飛んだり、コピーすればmarkdown形式でコピーされます。 はてなブログへの転記も比較的簡単なため、それなりにスムーズにフローが回っています。 しかしそもそも転記が面倒だったり、転記してフォーマット崩れの確認、修正作業が多少必要など、もっと記事執筆にフォーカスできるようにしたいなという声がちらほらありました。 そんなときに今回紹介するHatena-Blog-Workflows-Boilerplateが公開されたので、アドベントカレンダーネタがてら新しい執筆フローを試してみるか〜と思い、触ってみました。 Hatena-Blog-Workflows-Boilerplate Hatena-Blog-Workflows-Boilerplateは、株式会社はてなさんが公式で公開しているはてなブログ執筆、運営支援用ワークフローのボイラープレートです。 このボイラープレートを使ってリポジトリを作成し、簡単な設定をすることで、PRの作成、レビュー、マージを通じてWebエンジニアの手に馴染んたフローで記事の執筆、公開、編集を行うことが出来ます。 github.com 本記事執筆時点でベータ版であり、さらなる機能拡充も予定しているそうです。 ぜひ皆さんも利用して、改善点があればissueを立てたりコントリビュートしてみてください。 Hatena-Blog-Workflows-Boilerplateを使ってみる セットアップ https://github.com/hatena/Hatena-Blog-Workflows-Boilerplate/blob/main/README.md に非常に丁寧に記載してあるため、何の問題もなくセットアップすることが出来ました。圧倒的感謝! 記事の作成 README によると、下書きの作成方法が2つあります。 下書きの作成方法は以下の2通りの方法があります。 ブログメンバーが個人のアカウントで投稿する(記事の署名は個人のアカウントになります) ブログオーナーのアカウントで投稿する(記事の署名はブログオーナーアカウントになります) BASEの場合、記事は個人のはてなアカウントで署名されています。 その場合、 ブログメンバーが個人のアカウントで投稿する場合 の項にあるように、一度ブログの編集画面上で空の下書きを作成し、Actionsから下書きをpullしてきてPRを作成、記事の編集を行う必要があります。 画像の投稿 staff.hatenablog.com にもあるように、2023/12時点で画像の投稿機能はHatena-Blog-Workflows-Boilerplateでは提供されていません。編集画面やはてなフォトライフへ手動で画像をアップロードをしてURLを取得、記事に貼り付けて対応します。 レビュー 実際に作成されたPR 上記画像のようにPRが作成されます。 pushした時点で下書きがはてなブログに同期され、PR上に記事編集画面のURLが記載されます。 そこから下書きプレビューのURLを発行してPRに追記するか、レビュワー/レビュイーが記事編集画面経由で下書きプレビューの確認を行います。 公開 記事内のDraft設定を削除しmainブランチにマージすると記事が公開されます。簡単! 修正 通常の開発のように、対象記事を修正、PRの作成、レビュー、マージによって修正が反映されます。 執筆フローを構築してみた所感 BASE Product blogは様々な職種のメンバーが記事を書いていますが、メインの執筆者であるエンジニアが普段慣れ親しんだGitHubでのレビューワークフローに則って公開できるのは強みだなと思いました。 記事のレビューも、内容だけでなく実際の表示までまとめて確認ができるので楽だなと感じました。 また、このボイラープレートを導入したからといって、このフローにロックインされるわけではありません。 これまで通りNotionや直接はてなブログの下書きを書いてレビューするフローは残せるので、エンジニアに限らず他執筆者の手に馴染んだフローで執筆することが可能です。(あまりフローの種類が多いと大変なのである程度絞る必要はありますが・・・) とはいえ画像の投稿は一気通貫でできるようになるとより執筆効率をあげられそうですね。今後の予定として記載されているので楽しみです。 幸い、はてなフォトライフには API があるため、自前で画像アップロードCLIなどを作ることはできそうです。今回間に合いませんでしたが、また暇なときに作ってみます。 下書き作成に関しては、記事が個人のはてなアカウントに紐づく運用の場合に一旦編集画面で空の記事を作成するフローはうまいことやって自動化したいです。どうやるといいんだろうか・・・ あとは予約投稿機能もよく使うので、その設定もできると嬉しいな〜と思いました。 おわりに まだあくまでベータ版ではありますが、普段慣れ親しんだレビューワークフローで執筆ができるのはストレスフリーでとてもよいOSSだな〜これが公式から提供されるのはとても意義があるな〜と思いながら構築しました。 今回は一旦自分が試験的に導入しましたが、もうちょっと整備して正式なフローの一つとして加えられるようにしていきたいです! 今後の機能拡充もとても期待しています。最後になりますがこのOSSを開発してくださった皆様、本当にありがとうございます! ぜひこの記事を読んだ皆様も導入してはいかがでしょうか? さて、明日は @shiiyannn さんによる、「小数点の罠:メンバーシップポイント計算の裏側」です。お楽しみに!
アバター
この記事は BASE Advent Calendar 2023 の2日目の記事です。 こんにちは!BASE株式会社でエンジニアをしている大津(@cocoeyes02)です。 今回は自分たちが運営しているプロダクトにおいて障害対応をする中で、インシデント発生が観測されてから暫定対応をするまでの初動にあたるフェーズの動きについて書いていきます。 インシデント対応全体に関わる話は別の記事にありますので、そちらも併せてご覧ください! devblog.thebase.in インシデント発生が観測されてからいかに早く対応に参加できるか インシデント対応の初動フェーズにおいて、いかに早く対応に参加できるかというのは非常に重要なポイントとなります。その理由はいくつかあります。 今開発している機能を将来使うかもしれないユーザが減ってしまうリスクがあるから インシデント発生時は、今現在困っているユーザ(BASEのサービスでいうとオーナーさん、購入者など)がいて、今すぐ対応をしないとどんどん体験を損なってしまう状況であることが多いです。 普段のユーザからのお問い合わせ対応も、インシデントほど切羽詰まっているわけではありませんが、やはりこちらも対応をしないとどんどん体験を損なってしまいます。 体験を損ない続けた先に待っていることは何でしょうか?そう、 ユーザがプロダクトから離れてしまうということです。 その前提で考えると、原則業務の優先度としては、 インシデント対応 > お問い合わせ対応 > 普段の開発 の順であると考えています。ユーザが離れてしまうということは、今実装している機能を将来使う可能性があった人も減ってしまうということです。そうなるとせっかく新しい機能をリリースしても、価値を届けられるユーザの数が減ってしまいます。 インシデント対応には、ユーザがプロダクトから離れてしまうことで発生するリスクと戦う人々が集まっているのです。 そもそもインシデント対応において途中から対応に参加するのは、後になればなるほど難しい インシデント対応は遅くなればなるほど、インシデント発生時間(ユーザ体験を損なっている時間)も伸びてしまいます。なので、スピードを求められる状況であることが多いです。 今どこまで対応したのか、今なんの対応をしているのか、どこまで情報を手に入れているのか、インシデント対応にまつわるログは時間が経つにつれて爆増していきます。参加するのが後になればなるほど、インシデント対応にまつわるログの量も多いため、キャッチアップだけでも大変です。 その点インシデント発生が観測されてからすぐ対応に参加することができれば、キャッチアップは難しくはありません。 ちなみにBASEではインシデントが起きた時にインシデント対応専用のチャンネルをSlackで作成しており、インシデント対応専用のチャンネルが作成された瞬間希望者を全員チャンネルへ自動的に参加させるツールがあります。 自動的に参加されたタイミングでSlackの通知がくるので、多くの人がインシデント発生に気づき、すぐ対応に参加することができます。 初動フェーズにおけるインシデント対応の具体例 ここにいくつか具体例を書きますが、これらの対応は実際にやってみないと上手くなりません。 現時点の状況やインシデントのまとめ MTG等で途中から参加してきた人がインシデント対応へ参加しやすくするため、今起こっている事象をまとめるだけでも立派な対応になります。 インシデントのまとめはインシデント発生からの流れを一通り追っていないと書けないことが多いので、インシデント対応に慣れていない人はまずはここから取り組んでみると良いのではないかと思います。 必ず全部1人で書かなければいけないわけではありません。少しでも良いから書いてみるところから始めましょう。 インシデントの事象を再現しよう インシデントの種類によっては、どのOS・ブラウザ・バージョンで起きたか重要になる可能性もあります。 その場合、どのOS・ブラウザ・バージョンで再現したのか少しでも多くの情報が必要になります。 実際に自分の環境で試してみて、再現した / しなかったかを書きましょう。 本番環境で再現するとエラーが出てしまう類のものであれば、ステージング環境等で確認するのも良いでしょう。 今回のインシデントの影響範囲をクエリやログなどで算出しよう Xやメールなどでお知らせを出すため、今回のインシデント対象となったユーザ(のID)や購入者(のメールアドレス)などが必要になります。 ただしどのケースでも、テーブル構造の知識やクエリを書く力が必要です。 不安がある人は、社内のデータ抽出ログを一通り読んでみると良いでしょう。 事象に関連したエラーを社内で使用しているツールより確認しよう インシデントの原因を見つける上で重要なのが、どんなエラーやログが吐かれていたのか確認することです。 そのために、社内で使用しているツール(BASEであればNew RelicやSentryなどを使用しています)の使い方について日頃から慣れておくと良いでしょう。 また、各ツールの通知を見れるチャンネルに入っているのも重要です。 すぐ検知できるように設定しておくと良いでしょう。 インシデント発生日にリリースしたものの中に怪しいものがないか確認しよう インシデント発生日が分かれば、原因がインシデント発生日にリリースしたものの中にある可能性が高いです。 その日何が本番環境へリリースされたのか確認してみましょう。 関連してそうなリリースがあればPRのリンクを共有、さらにPRを読んで該当のソースコードを共有するなどできるとGOODです。 revert PRマージやロールバックの判断を促そう 何度も言いますが、インシデント発生時は、今現在困っているユーザがいて、今すぐ対応をしないとどんどん体験を損なってしまうという状況が多いです。 その今すぐ対応というのは必ずしも修正対応(≒根本対応)とは限りません。revert PRマージやロールバックすることで一時的にも復旧して体験が損なわれるのを止められるなら、その対応が優先です。 後から根本対応も必要になりますが、一番優先しなければいけない目的を見失わないようにしましょう。 revert PRや修正PRを作ろう、あるいはレビューしよう 暫定対応であればrevert PRを作る必要があります。原因がわかるのであれば、慌てずにかつ急いで修正のPRを作る必要があります。 また、自分がPRを作っていなくても、レビューすることも重要です。少しでも早くリリースできるよう、その辺りの感度も高めておくと良いでしょう。 インシデント対応はインシデント対応をやることでしか上手くならない こんな記事を書いておいて身もふたもないですが、読むだけではやはりインシデント対応は上手くなりません。何故なら、実際にインシデント対応をしてみると上手くいかないことが多いからです。 思ったより時間が掛かっている間に、インシデント対応に慣れている他のメンバーがパパッと対応してしまった ツールの使い方に手間取ってしまった 知識不足でどうすれば良いかわからなくなってしまった 単純に慌てて頭が真っ白になったり(深呼吸しよう) など… こればかりは数をこなして徐々に上手くなっていくしかありません。設計も設計の数をこなさないと上手くならないと思いますが、インシデント対応も同じです。 また、自チームに関係するドメインのインシデント復旧対応だけ参加しても、なかなか上手くならないこともあると思います。なぜなら、都合よく自チームに関係するドメインのインシデントばかり起こるとは限らないからです。 自チームはもちろん、可能であれば自チームに関係しないドメインのインシデント復旧対応にも参加して数をこなしていきましょう。 また、インシデント対応は対象ドメインの中でも深い知識を要するケースが多く、対象ドメインを理解するきっかけになります。実は インシデント対応はドメイン知識獲得のチャンス なのです。悠長にしている暇はないのは確かですが、ピンチはチャンスだと思って取り組んでみましょう。 最後に ここに書いてあることはあくまで入門が目的なので、インシデント対応に必要なことはもっとあります。例えば、あらかじめソースコードを広い範囲で読む、テーブル・インフラ(AWS)構成を把握するなど... ただ、今回の記事に書いてあることが一通りできる人は、次何が必要なのか自ずと見えてくるものではないかと思います。なので、入門が目的であれば十分かと思っています。 また、今回は初動フェーズの話なので触れませんが、再発防止策を考えたりインシデント対応のふりかえりといったポストモーテムも必要な動きになります。 色々書きましたが私もできていない部分が多く、自戒の意を多分に含んでいます。 とはいえインシデント対応する人が1人でも増えると、会社・サービス・組織どの主語においても嬉しいことがたくさんあると思います。失敗を恐れずにインシデント対応をやっていきましょう。 明日のアドベントカレンダーは @takashi_matsuyuki さんの「HatenaBlog Workflows Boilerplateを試してみた」です!お楽しみに!
アバター
おや... このページにも雪がふってきましたね... これは BASE Advent Calendar 2023 の1日目の記事です。 なんだかさいきん寒いなぁと思っていたら、ついに雪がふってきてしまいましたね! このはらはらと舞い落ちる雪の結晶、HTML と CSS で作れちゃいます。 そして BASE には HTML 編集 App という機能があります。この機能を使うと...? snowrry.base.shop ↑のデモショップのように、ショップに雪をふらせることができちゃいます!是非これからの季節に試してみてください ⛄ BASE ショップに雪をふらせる方法 まず HTML 編集 App の使い方については、こちらの BASE U の記事を参考に設定してください。 baseu.jp 今回は「テーマを編集する」機能を使ってショップに HTML と CSS を追加します。 HTML と CSS をテーマに追加する 「テーマを編集する」機能のエディタ画面 まずは CSS を追加します。head タグの中に以下の style タグをコピー&ペーストして貼り付けてください。 <!-- === snow ここから追加 === --> < style > .snow-container { display : flex ; position : fixed ; width : 100 vw; height : 100 dvh; z-index : -1 ; } .snow-container .snow span , .snow-container .snow :: before , .snow-container .snow :: after { position : absolute ; width : 100% ; height : 100% ; color : snow ; text-align : center ; } .snow-container .snow.small { font-size : 1.2rem ; opacity : 1 ; } .snow-container .snow.medium { font-size : 1.4rem ; opacity : 0.8 ; } .snow-container .snow.large { font-size : 1.6rem ; opacity : 0.6 ; } .snow-container .snow.small span { display : block ; animation : fall- small 7s linear infinite ; } .snow-container .snow.small :: before { content : "❄" ; left : -30% ; animation : fall- small 9s linear infinite ; } .snow-container .snow.small :: after { content : "❄" ; left : 30% ; animation : fall- small 13s linear infinite ; } .snow-container .snow.medium span { display : block ; left : 10% ; animation : fall- medium 8s linear infinite ; } .snow-container .snow.medium :: before { content : "❄" ; left : -40% ; animation : fall- medium 10s linear infinite ; } .snow-container .snow.medium :: after { content : "❄" ; left : 40% ; animation : fall- medium 6s linear infinite ; } .snow-container .snow.large span { display : block ; left : -10% ; animation : fall- large 5s linear infinite ; } .snow-container .snow.large :: before { content : "❄" ; left : -35% ; animation : fall- large 6s linear infinite ; } .snow-container .snow.large :: after { content : "❄" ; left : 35% ; animation : fall- large 7s linear infinite ; } @keyframes fall-small { 0% { top : -20px ; } 3% { transform : translateX( 5px ) ; } 7% { transform : translateX( 5px ) ; } 18% { transform : translateX( -5px ) ; } 22% { transform : translateX( -5px ) ; } 38% { transform : translateX( 13px ) ; } 42% { transform : translateX( 13px ) ; } 58% { transform : translateX( -13px ) ; } 62% { transform : translateX( -13px ) ; } 78% { transform : translateX( 13px ) ; } 82% { transform : translateX( 13px ) ; } 100% { top : calc( 100% + 20px ); } } @keyframes fall-medium { 0% { top : -200px ; } 3% { transform : translateX( 5px ) ; } 7% { transform : translateX( 5px ) ; } 18% { transform : translateX( -5px ) ; } 22% { transform : translateX( -5px ) ; } 38% { transform : translateX( 13px ) ; } 42% { transform : translateX( 13px ) ; } 58% { transform : translateX( -13px ) ; } 62% { transform : translateX( -13px ) ; } 78% { transform : translateX( 13px ) ; } 82% { transform : translateX( 13px ) ; } 100% { top : calc( 100% + 20px ); } } @keyframes fall-large { 0% { top : -300px ; } 3% { transform : translateX( 5px ) ; } 7% { transform : translateX( 5px ) ; } 18% { transform : translateX( -5px ) ; } 22% { transform : translateX( -5px ) ; } 38% { transform : translateX( 13px ) ; } 42% { transform : translateX( 13px ) ; } 58% { transform : translateX( -13px ) ; } 62% { transform : translateX( -13px ) ; } 78% { transform : translateX( 13px ) ; } 82% { transform : translateX( 13px ) ; } 100% { top : calc( 100% + 20px ); } } </ style > <!-- === snow ここまで === --> 次に HTML を追加します。body タグの中(できれば要素の一番最初)に以下の div 要素をコピー&ペーストして貼り付けてください。 <!-- === snow ここから追加 === --> < div class = "snow-container" > < div class = "snow small" >< span > ❄ </ span ></ div > < div class = "snow medium" >< span > ❄ </ span ></ div > < div class = "snow large" >< span > ❄ </ span ></ div > </ div > <!-- === snow ここまで === --> これで追加する HTML と CSS は以上です。 プレビューモードをみてみましょう。 「テーマを編集する」機能のプレビュー画面 はらはらと雪の結晶が落下していたら成功です。 保存して、現在ご利用中のテーマであれば変更内容をショップページに反映させましょう。 テーマに合わせて CSS を微調整する 例えばショップの背景が白っぽかったりする場合は、CSS を調整して雪の結晶の色を変えたりすることができます。 .snow-container .snow span , .snow-container .snow :: before , .snow-container .snow :: after { position : absolute ; width : 100% ; height : 100% ; color : snow ; /* <- ここの色を好きなカラーコードに変える */ text-align : center ; } 元のCSSでは snow (カラーコード: #fffafa )が指定されていますが、ここを #87ceeb のような水色に変えることで雪の結晶の色を変えることができます。 落ちてくる速さや雪の結晶の大きさを変えたりなど、より高度なカスタマイズがしたいときも同じように CSS のプロパティの値を変えることで柔軟にスタイルを変えることができます。 以上がショップに雪をふらせる方法になります! ここから下は実装についての細かいお話になるので、興味のある人は読んでみてください 🥳 HTML / CSS の解説 簡単に説明すると実要素としては3つ、それに ::before と ::after の疑似要素をプラスした合計9つの「❄」が、アニメーションで上から下にひらひらと落ちてくるといった内容になります。雪の結晶はランダムな感じにしたかったので small / medium / large を用意しました。 .snow-container の CSS .snow-container は雪の結晶をまとめた親要素です。画面いっぱいに広がっています。 .snow-container { display : flex ; // 子要素の配置のために flex にする position : fixed ; // 要素は固定! width : 100 vw; // 横幅いっぱいにして height : 100 dvh; // 縦幅も dvh をつかっていっぱいに z-index : -1 ; // 背景にふらせたいので -1 を指定 } .snow の CSS .snow の中の span 要素と疑似要素が実際にふっている雪の結晶です。 .snow-container .snow span , .snow-container .snow :: before , .snow-container .snow :: after { position : absolute ; width : 100% ; height : 100% ; color : snow ; text-align : center ; } .snow-container .snow.small { font-size : 1.2rem ; // 雪の結晶の大きさ opacity : 1 ; // 小さい雪は遠くにあるのではっきりと } .snow-container .snow.medium { font-size : 1.4rem ; opacity : 0.8 ; } .snow-container .snow.large { font-size : 1.6rem ; opacity : 0.6 ; // 大きい雪は近くにあるので薄めに } small / medium / large それぞれの snow にアニメーションをつける animation で指定している秒数と left で指定しているパーセンテージは全体でランダムな感じになるようにキメ打ちしてあります。JS を使うとここがもっとランダムな感じになったり、foreach とか使って端的に書けたりするのかなぁ。今回は HTML / CSS だけで作りたかったのでけっこう愚直な感じで書いていきました。 .snow-container .snow.small span { display : block ; // span は inline なので block に animation : fall- small 7s linear infinite ; // animation をランダムな速さでつける } .snow-container .snow.small :: before { content : "❄" ; // 疑似要素の雪の結晶 left : -30% ; // ランダムな場所に設置 animation : fall- small 9s linear infinite ; } 落下アニメーションの中身 こちらも small / medium / large で0%のときの内容を変えています。これで落下タイミングが変わってきます。 ひらひら舞い落ちるアニメーションがカクカクしてしまわないような工夫を少ししています。 @keyframes fall-small { 0% { top : -20px ; // 少し上らへんから落ちてくるように。ここも small / medium / large で開始位置を変えてランダムにしている } 3% { transform : translateX( 5px ) ; // 5% と 10 の倍数の % から± 2% のあたりで同じ位置になるようにゆらゆらと } 7% { transform : translateX( 5px ) ; // ↑の 3% と同じ位置 } 18% { transform : translateX( -5px ) ; // 20% の± 2% で、↑の 5px と逆の -5px } 22% { transform : translateX( -5px ) ; // ↑の 18% と同じ位置 } 38% { transform : translateX( 13px ) ; // 最初より大きめな揺れ幅に } 42% { transform : translateX( 13px ) ; // ↑の 38% と同じ位置 } 58% { transform : translateX( -13px ) ; } 62% { transform : translateX( -13px ) ; } 78% { transform : translateX( 13px ) ; } 82% { transform : translateX( 13px ) ; } 100% { top : calc( 100% + 20px ); // 少し下らへんまで落ちきるように } } と、このような感じで CSS を書いていきました。CSS 書くの楽しいね ☺ BASE でショップを作るとこのように HTML / CSS が編集できるので、コーディング次第でカスタム性の高いサイトを作ることができます。 以上、最後まで読んでいただきありがとうございました!今年の冬はぜひインターネットに雪をふらせてみてください ❄ 明日は 大津さん の記事です。お楽しみに〜!🎄 ❄ ❄ ❄ article.entry { background-color: #00000000; } .snow-container { display: flex; position: fixed; top: 0px; width: 100vw; height: 100dvh; z-index: -1; } .snow-container .snow span, .snow-container .snow::before, .snow-container .snow::after { position: absolute; width: 100%; height: 100%; color: #87ceeb; text-align: center; } .snow-container .snow.small { font-size: 1.2rem; opacity: 1; } .snow-container .snow.medium { font-size: 1.4rem; opacity: 0.8; } .snow-container .snow.large { font-size: 1.6rem; opacity: 0.6; } .snow-container .snow.small span { display: block; animation: fall-small 7s linear infinite; } .snow-container .snow.small::before { content: "❄"; left: -30%; animation: fall-small 9s linear infinite; } .snow-container .snow.small::after { content: "❄"; left: 30%; animation: fall-small 13s linear infinite; } .snow-container .snow.medium span { display: block; left: 10%; animation: fall-medium 8s linear infinite; } .snow-container .snow.medium::before { content: "❄"; left: -40%; animation: fall-medium 10s linear infinite; } .snow-container .snow.medium::after { content: "❄"; left: 40%; animation: fall-medium 6s linear infinite; } .snow-container .snow.large span { display: block; left: -10%; animation: fall-large 5s linear infinite; } .snow-container .snow.large::before { content: "❄"; left: -35%; animation: fall-large 6s linear infinite; } .snow-container .snow.large::after { content: "❄"; left: 35%; animation: fall-large 7s linear infinite; } @keyframes fall-small { 0% { top: -20px; } 3% { transform: translateX(5px); } 7% { transform: translateX(5px); } 18% { transform: translateX(-5px); } 22% { transform: translateX(-5px); } 38% { transform: translateX(13px); } 42% { transform: translateX(13px); } 58% { transform: translateX(-13px); } 62% { transform: translateX(-13px); } 78% { transform: translateX(13px); } 82% { transform: translateX(13px); } 100% { top: calc(100% + 20px); } } @keyframes fall-medium { 0% { top: -200px; } 3% { transform: translateX(5px); } 7% { transform: translateX(5px); } 18% { transform: translateX(-5px); } 22% { transform: translateX(-5px); } 38% { transform: translateX(13px); } 42% { transform: translateX(13px); } 58% { transform: translateX(-13px); } 62% { transform: translateX(-13px); } 78% { transform: translateX(13px); } 82% { transform: translateX(13px); } 100% { top: calc(100% + 20px); } } @keyframes fall-large { 0% { top: -300px; } 3% { transform: translateX(5px); } 7% { transform: translateX(5px); } 18% { transform: translateX(-5px); } 22% { transform: translateX(-5px); } 38% { transform: translateX(13px); } 42% { transform: translateX(13px); } 58% { transform: translateX(-13px); } 62% { transform: translateX(-13px); } 78% { transform: translateX(13px); } 82% { transform: translateX(13px); } 100% { top: calc(100% + 20px); } }
アバター
こんにちは。BASE株式会社上級執行役員SVP of Developmentの藤川です。2023年のアドベントカレンダーも実施したいと思っており、この記事が1日目になります。 自分自身がBASE社に正式ジョインしたのは2014年8月、取締役CTOとして入社しました。僕は2代目のCTOですが、その後、3代目にCTOを渡し、今では上級執行役員SVP of Developmentというちょっと珍しい肩書で仕事をしています。組織としてはCTOの上長でもあり、自己紹介では技術担当役員と表現することもあります。 自分がBASE社に入社した段階ではシリーズBを迎えていました。象徴としては藤田ファンドから出資をいただいてから、上場を意識した組織に変えていくという空気感だったと思います。 BASE社には正式ジョインする前から技術顧問として関わっていて、週一だけ会社にあらわれるおじさんだったのですが、そのタイミングでは39歳でした。40代はBASE社に捧げたわけですが、先日50歳を迎え、自分自身の節目も含めて、2023年のタイミングで振り返ってみたいと思います。 入社直後の初期スタートアップ感 入社直後の開発チームはデザイナーもPdMの役割もセットで10人ちょっとぐらいのチームでした。専門職の集まりというよりはそれぞれがデザイン、フロントエンド、バックエンド、PdMなどを0.5ずつの役割を持っているようなタイミングだったとも言えます。 この規模の入社当初のCTOの役割というのは、経営チームにジョインしたものの実態は開発マネージャみたいな役割であったり、他社であれば、ほぼテックリードという組織が多いのではないでしょうか。 自分自身の入社時には、すでに開発が回っているチームになっていたので、自分はコードは書かずに開発チームの皆さんを支えますと言ったことを覚えています。今で言うエンジニアリングマネージャとして動きますという宣言ですね。 現実には、BASEのネイティブアプリのコードを見ていたり、当時かCTOがデプロイ作業をやっていたのでリリースマネジメントを手動でやっていたり、redmineで不具合や要望管理など現場の仕事は続けていました。アップル社のお仕事でエンジニアとしてクパティーノにお伺いしたこともあります。 当時は渋谷の道玄坂の途中にある新太宗ビルという飲食店やマッサージ屋さんも入っているビルにオフィスがあって、会社の目の前には飲み屋や男性向けのマッサージ店、道玄坂の客引きトラックの音が聞こえてくる猥雑な環境で、我々の働き方はすでにスタートアップ企業でしたから牧歌的とまでは言わないですが、どこかのんびりしていた環境下で自由に仕事をしていた時期だったと言えます。 のんびりと書くと語弊がありますね。 BASEの初期フェーズだと、例えば競合他社とのリリース競争というものがありました。他社様が機能をリリースしたら、我々もすぐさま追いついて、次の日にリリースを出すような時期もありました。ある種のプレスリリース競争とでも言いますか、ミート戦略とも世の中では言うそうなのですが、MBAで学ぶような動きを鶴岡が自然にやっていたのが印象的でした。 こういうのをのんびりというのもおかしいのですが、仕事的な緊張感というよりは、生き残るためにはそれをやらねば!というグルーブ感の中で動いていて、明日をも知れぬスタートアップの初期タイミングとして、サラリーマン的なお仕事のあり方とはちょっと違うような働き方をしていた時期だったとも言えます。 ただ、あまりに過度なミート戦略の追求は、現場の疲弊をもたらすので、あるところから自分たちのプロダクトをしっかり作っていこうとシフトしたのも覚えています。 後から考えると、まだこの頃は、代表の鶴岡が思うBASEというサービスのビジョンがわかっていなかったタイミングでした。例えば、実はfintech企業であったこと。BASEという無料のECのストアフロントに込められた想いというのは、その後、理解していくことになります。 いろんな課題の意思決定の議論の中で、本当に鶴岡が目指しているもの、大事にしているものなど、彼自身の価値観というのが少しずつ見えてきたタイミングでもありました。 初期スタートアップから会社としての成長 その後、ほどなくしてGスクエアという道玄坂交番前のビルに移転します。アメーバビルと言うと知っている人もいるかもしれませんが、以前はサイバーエージェント社が一棟借りされていた縁起の良いビルでした。 オフィスが強化され、当時、メルカリさんが株主になっていただいたこともあり、技術面はもちろん採用面でもアドバイスをいただくことになります。当時のメルカリさんと言えば、採用をブイブイやっていて、他社のCTO経験者クラスをバンバン入社させていくというスーパーエリートスタートアップだったわけですが、採用の手法について聞くと、本当に地道に声をかけて入社していただくということを聞いて、どこか開眼したと言うか、甘えがなくなったことで、我々の採用ペースも加速されることになりました。 BASE社はBASEとしてのビジョン 「Payment to the People , Power to the People」に共感していたける方々を探すべくひたすら自社でイベントをやったり、技術カンファレンスのスポンサーでBASEやPAYという名前を知っていただいて、その人が転職する際、転職エージェントから紹介されるリストからの選択肢に入れていただくことを目指して技術ブランディングを行っていったり、スカウト活動やエージェントとのリレーション強化などをやっていたように思えます。 当時の面接記録を見返すと、2016年では220人以上の面接記録があり、これが多いのか少ないのかわかりませんが、丁度、個人としても大学院の博士論文の佳境のタイミングと被り、ものすごくハードだったのを覚えています。カジュアル面談はこちらが主体に喋るので、喉が辛かったですね。ちなみに、ざっくり25人ぐらいの方に入社いただきました。一人で内定判断をしている時期でもあったので、今より内定基準が緩かったとは思いますが、面接に対する内定承諾率が、10%ぐらいでしょうか。 個人的に思うところとしては、この採用コミットはコードを書くとか技術に携わるという行為とは非常に相性の悪い行為だと思っています。感覚的な表現で伝わらないかもしれませんが、採用活動が自分自身の思考をBASE社のアピールという面に広げて考える活動であれば、技術に向き合う活動はサーバの動きに深く深く沈ませる行為だと思っています。 具体的には、コードを書いている時に面接の時間が来ると、心のスイッチングコストが高くて、面接のパフォーマンスが出ないという時期がありました。結果として、どっちつかずになるところから、技術に深く向き合うのをやめたという経緯があります。 それこそ、2016年、2017年の入社組は後のマネージャのリーダーやCTO、プリンシパルテックリードなど今のBASEを支えている最上位のメンバーになってくれた時期で、開発を彼らに任せるというか、片手間では彼らのスピードに到底ついていけなくなったのがこの時期です。チームが30人いたら、1人日の30倍ぐらいのスピード感で何かが変わっていくわけですから追いつけなくなりますよね。 それらの後にBASEというサービスの技術トップとしてCTOの役割を再定義し、現状の川口に肩書を譲るわけですが、技術に手を付けられなくなったためにCTOという肩書を移譲するという流れになるのは、権限委譲を前提としてチームそのものを大きくすることにコミットするからというのが一番の理由です。 その段階で僕の役割は、一般的な言葉で言うと「VPoE的なマネジメント役を含め、サービス以外の技術経営的は仕事はなんでもやる」という形になりました。 時期は前後しますが、それ以外にもスタートアップCTOとしてやらなきゃならないことが増えていくステップは共通してあるのではないかと思います。 1.初期ではCTOでやれていたAWSやリリースの管理がままならなくなってSRE専門職を採用する。SREにインフラを委ねることで自分自身から手離れしていく (サービスに関わるポイントが減っていく) 2.人が増えて無線LANが繋がらなくなる。ネットワークの整備をちゃんとしないといけないところで情シスチームが生まれ始めて、インフラやPCの管理が手離れしていく (情シスタスクの増加) 3.上場に向けてIT内部統制の整備が始まり、サービス技術以外への集中力が取られる (上場準備に伴うコーポレートガバナンスの整備) 4.サービスとしてはクレジットカード番号を保有していないのに、ECプラットフォームという理由で、クレジットカードを取り扱うセキュリティ基準であるPCIDSS対応を求められる (関係業界との連携) などなどが起きていき、サービス技術以外の仕事が増えていくことになり、技術の意思決定については権限移譲していく流れになります。もしかしたらここら辺で向いてなくて離脱する初期CTOの人もいるかもしれないですね。 上場から今 その後、2019年に無事上場を迎え、上場後のさらなる成長のためにチームの醸成、採用の強化、権限移譲などを進めていき、コロナ禍を超えて今に至るわけですが、2023年は、会社のフェーズが変わり、個人的にはコーポレートガバナンスを実現するオペレーションの必要性を強く意識するようになったタイミングであったと思っています。 あ、もっとちゃんとやらないとまずいんだということを知らしめられた一年でした。 雑な表現をすると発生確率が0.0035ぐらいのものが、スタッフ100人の会社だったら1を超えないけど、300人だったら1を超えて実際に問題が発生するってことが見えました。あらゆることを予見しながら、しっかりリスク管理をしないと躓くことが増え始めることがよくわかりました。 また他社様の事例を見てみても、同時期に上場したとある会社さんでCTO役だった方が、より技術にコミットできる開発部長みたいな形に変化したと言う事例があり、やはり役員陣の役割がよりコーポレートガバナンスが求められる方向に変わり、それによる最適配置だったという話を聞いて、自分の直感と感覚があってると共感しました。 組織としても、それまでたくさんの仕事をやりすぎていた自分自身の兼務体制も整理していく流れの中で、より分化された組織として技術マネジメントをしていくという構造に変化していくと思っています。 特にBASE本体の開発チームだけでなく、新規事業寄りであるPayID開発チームやBASE BANK開発チームのサイズが順調に大きくなっていき、それまでのBASE開発チームを中心としていた世界観から間違いなく、それぞれがしっかり組織として成立させていくフェーズに入ると思いますので、そこに携わるエンジニアマネージャの方々との間での全社的な統一感を作っていく必要性を感じています。 それでありながらアグレッシブさをうまく両立させる形で、BASE社の成長を作っていくんだなという感覚が強くありまして、チームマネジメント、セキュリティ、リスクマネジメントなどをしっかりしながら、当社の行動指針の一つであるmove fastを実現するというバランス感ある組織にしていく必要があるな、と思うわけです。 自分語りにしかなっておらず、あまり汎用的な事例としては書けなかった文章ですが、一人でも何か共感などをもっていただけたら幸いです。 BASEアドベントカレンダー 2日目もお楽しみに! devblog.thebase.in
アバター
こんにちは!BASE product blog編集部です。みなさまそろそろ年の瀬ですが、いかがお過ごしでしょうか。 今年も恒例のBASEメンバーによるアドベントカレンダーを開催します! 毎年公開しているアドベントカレンダーも今年で6回目を迎えます。 過去の様子 2022年のアドベントカレンダー 2021年のアドベントカレンダー 2020年のアドベントカレンダー 2019年のアドベントカレンダー 2018年のアドベントカレンダー 今年も毎日記事を公開する予定です。1日に2記事リリースされる日もあります! 下記の記事カレンダーも随時更新していきますので、ぜひお楽しみに! 記事カレンダー 日付 テーマ、タイトル 12/1 創業期CTOが残っている会社が上場するとどうなるのか 12/1 HTML / CSS でショップに雪をふらせましょう ⛄ 12/2 インシデント対応入門 〜初動フェーズ編〜 12/3 Hatena-Blog-Workflows-Boilerplateつかって BASEのブログ書いてみた 12/4 小数点の罠:メンバーシップポイント計算の裏側 12/5 Notion導入について(前編) 12/5 開発チームで取り組んだ働き方の実験10選(前編)〜 出社とか雑談とか 12/6 Notion導入について(後編) 12/6 開発チームで取り組んだ働き方の実験10選(後編)〜 スクラムイベントとか GitHub とか 12/7 現代メカニカルキーボード用語の基礎知識 12/7 “ゆるく、途中からでも” 学べる、夜な夜な.goというイベントをやっています 12/8 LocalStack/MinIO を導入して開発者体験が捗った話 12/9 フルサイクルエンジニアリングの第一歩を進める - BASE BANKでの新たな挑戦 12/10 われはセキュリティの子 12/11 180件のPRを遡って、良いレビューコメントをLintのルールに組み込んだ 12/11 Figma to STUDIO(β)体験記 〜STUDIOでLPを作るって、実は簡単? 〜 12/12 職場を明るくする 12/13 AI機能開発とインボイス制度対応を同時にやったら、意外と似ていた 12/14 SMS OTP で使われるメッセージの形式の歴史 12/15 LLMを利用したテキストアノテーションのツール化 12/16 ドメイン知識を素早くキャッチアップする時に心がけている事 12/17 Pay IDアプリのWebViewにReact + Viteを導入した 12/18 Android WebViewで少し厄介だった実装の紹介 12/18 Mintで管理しているSwiftライブラリを定期的に更新する 12/19 社内でのデータ活用を推進する取り組みの紹介 12/20 社内公募制度を使ってみました!〜Youは何しにBASE BANKへ?〜 12/21 カスタマーサポートと連携してコスパよく問い合わせ件数を減らした話 12/22 ぼくらがPMMをやる理由 12/22 組織のUXライティング力ボトムアップへの挑戦 12/23 元インターンがインターン採用やってる話 12/24 単体テストの考え方/使い方 社内読書会をしました 12/25 CTOに必要なものとは
アバター
はじめに BASE でプロダクト開発をしている @rry です。 さいきん BASE では 予約販売 App にて「長期の予約商品」を設定できるようになりました! baseu.jp 🗓️予約販売期間が1年先まで延長可能 🧵製作や入荷に時間がかかる商品も取り扱いやすく 🎄季節の限定商品を事前に販売開始も◎ 今までの予約販売 App では最長で8週間までしか予約設定することができず、オーナーさんからのフィードバックでも「8週間先も設定できるようにしてほしい」という要望が多かったため、このたび 予約販売長期化 PJ として機能改修を行いました。 この PJ の裏側を開発目線からご紹介していこうと思います。 PJ 概要 PJ キックオフから10/19リリースまでの期間:約8ヶ月 PJ メンバー:開発エンジニア 2-6人、QA エンジニア 1人、PM 1人、デザイナー 1人 アジャイル・スクラムで開発 1スプリント:1週間 デイリースクラム:毎日15分 スプリントレビュー・レトロスペクティブ・スプリントプランニング・バックログリファインメント:まとめて毎週火曜日 自分はこの PJ では PjM と主にフロントエンドの開発という役割で PJ をリードしていきました。 どんなことをやったのか 長期の予約注文は内部的には、決済時に一度取得した オーソリ(与信) をキャンセルし、時が来たら再度オーソリを取り直す、という方法で長期化を実現しています。 また、長期の予約注文が発送開始予定日を迎えるまでは注文を発送させないようにしなくてはいけません。そのため以下のような開発が必要でした。 バッチの作成 注文時のオーソリをキャンセルするバッチ オーソリを再取得するバッチ このときにオーソリが取れずキャンセルしなければならない注文はキャンセル処理もする オーソリ取得に成功して時が来たら注文の配送ステータスを「対応開始前」から「未対応」に変える 注文管理画面から長期の予約注文が確認できるようにする・配送ステータス「対応開始前」のときは発送できないようにする オーソリを取り直す必要があるため、長期の予約商品では Pay ID ログインが必須。そのためカートを改修する 注文の配送ステータスに今回新しく「対応開始前」が追加されたため、配送ステータスが影響している箇所すべてを改修する BASE API や他の Apps に影響がありました 予約販売 App 画面と商品登録画面から長期の予約商品が設定できるようにする 商品詳細画面で長期の予約商品は発送時期が上旬・中旬・下旬表記に変わり、注意文言が増える なんかいっぱいありますね! ザックリ時系列の流れ 2-4月: DB・アーキテクチャ設計、影響範囲調査、概算見積もり 設計についてはいろいろありましたが4月あたりから開発着手できるところから開発着手していきました 5-9月: 開発 カート -> バッチ -> 予約販売 App -> 注文管理 -> 商品管理 -> その他... みたいな流れで進めていきました 10月: リリース前 QA -> リリース このように開発が進んでいきました。 PJ を通して大変だったこと 半年以上の PJ をやってみて、いくつか反省点や大変だったことがありました。 インシデントはトータル5件 ユーザーに影響がないような小さいインシデントからユーザーに影響があるインシデントまで、この8ヶ月間でトータル5件起こりました。 ユーザーに影響があるインシデントが起こった箇所としては「商品管理画面」「ショップの商品詳細画面」で起こり、同じような不具合を起こさないように対策&周知してこれからも気をつけて開発していきたいと思います。 PJ 初期の段階でポイント見積もりに時間をかけすぎてしまった感がある スクラムをしていく上で、開発がいつ頃に終わるのか予測を立てるために、開発前に概算のポイント見積もりをしていました。 が、全体の仕様が決まりきる前に全体のポイント見積もりをしてしまったり、開発していくとポイントの重みが初期の重みから変わってしまったりしてしまいました。こうなってしまうと初期につけたポイント見積もりはあまり意味のないポイントになってしまい、見積もりにそこまで時間をかけるよりも開発に時間をかけたい、となってしまいます。 この反省を生かして、次から全体のポイント見積もりを行うタイミングは、 どこまで開発するかの要件や仕様と設計が定まってから 数回スプリントを回してみてポイントに対する肌感が開発エンジニアの中で揃ってきたときに するのが良いのではないかと思いました。 次回の PJ では、開発初期は「ここまでにこれを開発していたい」というざっくりとしたマイルストーンを置くくらいに留めて、全体の概算見積もりは仕様と設計が固まってからにしたいです。 色々あった結果既存コードが多く残っているリポジトリでの開発で DDD を採用しないことにした こちらについては長くなるのですが、誤解されてしまうのを恐れずにあえてザックリめに書きたいと思います。 BASE では現在、既存コードが多く残っていて昔からあるリポジトリから、DDD + クリーンアーキテクチャで作られた新しいリポジトリにリアーキテクチャをしていっている最中です。カートのバックエンドなどは新しいリポジトリにコードがあります。いずれはバックエンドのコードはこの新しいリポジトリにコードを移していく予定です。 ですが今回は既存の注文まわりのコードが新しいリポジトリにないため、昔からあるリポジトリ上でメインの開発をすることにしました。 では、昔からあるリポジトリで今回の開発をする場合は、どういう設計にするのが適切でしょうか? 昔からあるリポジトリでも一部コードはドメインモデリングがされていて DDD のアーキテクチャが採用されていたり、はたまた軽量 DDD のような設計で構成されている状態ではありますが、まだまだ大部分のコードは MVC + S のアーキテクチャです。 当初予約 PJ では、 新しく書くコードの設計は DDD でいこう、いやでも昔からあるリポジトリでは DI や DB トランザクションまわりの基盤機能が十分ではないしな... それなら軽量 DDD にするか...?いやでもドメインモデリングがしきれないのに DDD のアーキテクチャパターンを少しだけ囓ったような設計ははたして「軽量 DDD」と呼べるのか... 否、呼べないな... そして社内でチーム内外含めて議論する日々... と、このようなことが起こりました。 そこで話し合った結果、以下の内容でチーム内外含めて全体での合意がとれました。 境界づけられたコンテキストで明確に分けられることが分かっているドメインであること ドメインモデリングをしており、尚且つそのドメインモデリングを保守・運用していくこと DDD による設計が生きてくる程度にはある程度複雑性のあるドメインであること この3つが満たされる場合のみ、昔からあるリポジトリでも DDD で開発することができます、満たせない場合は DDD で開発するのはやめましょう。 そして予約 PJ の場合は注文に関係するドメインと密接に関わっていて、「境界づけられたコンテキストで明確に分けられることが分かっているドメイン」ではなかったため、昔からあるリポジトリでのメインの開発にて DDD は採用しないことにしました。 ではどんな設計にしたのか? 基本は MVC + S のアーキテクチャに則っています。ただし、開発するボリュームを考えるに Service が肥大化するのが目に見えています。 そこで、以下のような設計を行いました。 Entity ... Model 層が実質2層に分かれている フレームワークの Model の責務はデータアクセス(DB クエリビルダー) 連想配列ではなく、ORM 的なオブジェクトとビジネスロジックは Lib/Entity へ(ValueObject のようなもの) Shared Service ... Service 層を2層に分ける Service ... Controller(もしくは shell)から1処理あたりに call できる Service は原則1つ、いわゆる MVC + S の S Shared Service ... Controller(もしくは shell)などから直接 call してはいけない、Service 層もしくは Facade 層から call する Facade ... いわゆる Facade パターン。外から Entity や Shared Service を呼ぶときは Facade を使う ここまで大変だったことを長々と書きましたが、PJ を通して良かったこともありました。 PJ を通して良かったこと 当初の設計から大幅な仕様変更や、影響範囲の考慮漏れなどがなく PJ を終わらせることができた 前回自分が担当した PJ では、開発終盤で影響範囲の考慮漏れがみつかり阿鼻叫喚、みたいなことがありましたが、今回はその反省を生かしてあらかじめ影響範囲調査をしっかりとやったので今のところ考慮漏れバグみたいなものはみつかっていないです。 バックエンドのテストカバレッジ結果が良かった これはバックエンドエンジニアの方がメインでテストを手厚く書いていったことによる功績ですが、今回新規追加したバックエンドコードのテストカバレッジ結果が他コードと比較してとても良かったです。 リリースしてから大きな不具合なく済んでいることに影響していると考えます。 フィーチャートグルを使ってビッグバンリリースにせず小出しにリリースできた 半年以上かかる規模の PJ だったので、初期からフィーチャートグルの仕組みを使ってスプリント中に開発したものをメインのブランチにマージしていきながら開発できたのは開発体験が良かったです。そしてリリース当日はフィーチャートグルを全ショップ公開にするだけでリリースすることができました。 QA エンジニアに PJ 開始時から入ってもらえた 今回の PJ では、PJ 当初から QA エンジニアの方に Join してもらうことができ、スプリント中に機能ができあがり次第随時機能単位での QA をしてもらうことができました。 また手厚い QA 項目があったおかげで、リリース前の複雑なバッチの QA なども手分けして行うことができました。 おかげで安心して開発を前に進めることができ、クリティカルな不具合がなく済んでいることに影響しています。 以上、これらをやることによって開発エンジニアとしては PJ を前向きな気持ちで進めることができました。 おわりに 長くなりましたが、以上が予約販売長期化 PJ の開発の裏側になります。 今回は開発にフォーカスしましたが、PdM やデザイナーの方など優秀な人たちの力を借りてようやくリリースできた PJ だと思っています。 これからも BASE のプロダクトをより良いものにしていくような開発を心がけてやっていきたいです! 🚀
アバター
ogp CTOの川口 ( id:dmnlk ) です。 2024年2月10日に行われるYAPC::Hiroshima 2024にBASE株式会社としてPlatinumスポンサーをさせていただくことにしました。 ついでと言ってはなんですがスポンサーLTもします。話す内容はまだ未定です。 YAPCはPerlを軸としたカンファレンスですが、Perlに限らず多様な技術について語られるカンファレンスです。 タイムテーブルを見ても多分Perlネタの方が少ないんじゃないかっていう気がするくらいには多様です。 fortee.jp キーノートに杜甫々さんが登壇されることが決定したタイミングで、個人的に行こうかなと思っていました。 blog.yapcjapan.org 2000年頃からインターネットに触れ、「とほほのWWW入門」にお世話になっていた自分にとってはヒーローに近い存在です。 ちなみに「とほほのKubernetes入門」など最近の技術についても未だに更新されています。凄い。 その後、都内某所吉祥寺であったとある会において、実行委員長のkobakenさんにお会いしてその熱い想いに感銘し会社としてスポンサーをしてみることにしました。 去年のYAPC Kyotoには行けずだったので久々のオフラインYAPCが今から楽しみです。 お会い出来る方、飲みに行ける方、そして登壇者と運営チームの皆様、当日よろしくお願いします。
アバター
ogp CTOの川口 ( id:dmnlk ) です。 2024年2月10日に行われるYAPC::Hiroshima 2024にBASE株式会社としてPlatinumスポンサーをさせていただくことにしました。 ついでと言ってはなんですがスポンサーLTもします。話す内容はまだ未定です。 YAPCはPerlを軸としたカンファレンスですが、Perlに限らず多様な技術について語られるカンファレンスです。 タイムテーブルを見ても多分Perlネタの方が少ないんじゃないかっていう気がするくらいには多様です。 fortee.jp キーノートに杜甫々さんが登壇されることが決定したタイミングで、個人的に行こうかなと思っていました。 blog.yapcjapan.org 2000年頃からインターネットに触れ、「とほほのWWW入門」にお世話になっていた自分にとってはヒーローに近い存在です。 ちなみに「とほほのKubernetes入門」など最近の技術についても未だに更新されています。凄い。 その後、都内某所吉祥寺であったとある会において、実行委員長のkobakenさんにお会いしてその熱い想いに感銘し会社としてスポンサーをしてみることにしました。 去年のYAPC Kyotoには行けずだったので久々のオフラインYAPCが今から楽しみです。 お会い出来る方、飲みに行ける方、そして登壇者と運営チームの皆様、当日よろしくお願いします。
アバター
エントランスで集合写真を撮るBASE株式会社メンバーの様子 こんにちは。 最近めっきり寒くなってきて布団が恋しい季節になってきましたね。 皆さんはいかがお過ごしでしょうか。 さて、この度BASE株式会社は2023/10/28(土)に開催された Vue Fes Japan 2023 にシルバースポンサーとして協賛させていただきました。 今回は参加レポートとして会場の様子やセッションの感想についてお届けします。 Vue Fes Japan 2023とは Vue Fes Japan は 2018 年に誕生した日本最大級の Vue.js カンファレンスです。 今年は4年ぶりとなるオフライン開催となり、当日のセッションは立ち見が出るほど盛り上がっていました。 今年はゲストスピーカーとしてVue.js/Viteの作者であるEvan You氏やVueエコシステムのコアメンバーの方が来日し、キーノートやセッションを行っていました。 Vue.jsのこれからやエコシステムに関する発表が多かった印象で、大変興味深く聴かせていただきました。 オフライン会場の様子 BASEのブースではVue.jsに関するアンケートを実施し、答えていただいた方にBASEのショップで販売されているコーヒーをプレゼントさせていただきました。 BASEのブース アンケートの最終結果は以下のようになりました。 Vue/Nuxtのバージョンは2系を使っている方がまだまだいらっしゃる印象で、テンプレートについてはほぼSFC一択のように感じました。 アンケート最終結果 メドピアトラックでのkeynoteの様子 セッションの感想 当日に聴くことができたセッションを一部ご紹介します。 ※当日のメモをもとに作成したので、内容に間違いが含まれている可能性があります。ご了承ください。 Keynote: Vue & Vite Updates (by Evan You) Vue.js作者のEvan You氏によるVue.js/Viteのアップデートについてのセッションでした。 Vue3の成功として、全てをTypeScriptで記述できたこと、Composition API、DXに投資できたこと(ドキュメントの刷新, Vite, Volar等)が挙げられていて、逆に失敗したこととして、2→3で破壊的変更が多すぎてエコシステムやコミュニティが追いつくのに時間がかかったことが挙げられていました。デザイン上の失敗を反省した大きな講演といえば、Node.js開発者のライアンダールが失敗を語った(そしてdenoを作った)Design Mistakes in Nodeを思い出した人も多いと思います。 https://www.youtube.com/watch?v=M3BM9TB-8yA 今後は破壊的変更を一度に多く含めないようにし、またdeprecated→optin→removeという流れをとっていくことと、Vue本体のCIにvue-routerやpiniaの確認が含まれるようになるなど、エコシステムを大切にしていくとおっしゃられていました。 Vue.jsをプロダクトで使用する企業として、これはとてもありがたいですね。 社内UIコンポーネントライブラリがエンジニアチームにもたらした本当の価値 (by 山本 一将) 社内のプロダクト向けのUIコンポーネントライブラリを作成しUIの均一性を高めただけでなく、デザインに対する意識改革やレビューの運用フローなど多岐に渡って良い影響を起こすことができた。という発表でした。 新しい機能を作成するとき、一般的にはバックエンドのコードと併せてフロントの画面やUI全てを一つのPull Requestとしてコードレビューが開始されるかと思いますが、本格的に機能レビューが始まる前に、このライブラリのみでのコードレビューを挟むことでUIの必要性、ページ構成の方針、デザインレビューに議論をフォーカスすることができ、より深いレビューを行うことができるとおっしゃられていました。 また、この手法はデザイナーとの協業などで力を発揮する運用方法でもあります。 別のセッションでVue.jsを導入するキッカケに「デザイナーと協業するために選択した。」という話題を挙げていた方もいらっしゃったので、Vue Fesにふさわしい内容だったのではないでしょうか。 Vue Language Serverから生まれたVolar.jsと、それが秘める可能性 (by mizdra) Volar.jsというVue.jsから生まれたライブラリがVueの枠を超えて発展していっていることについての発表でした。 Volarといえば、VSCodeでVue.jsを書いたことがある人ならかなりの方がご存知だと思いますが、Vue.jsのLanguage Serverとしてシンタックスハイライトやコード補完を提供してくれるVSCode拡張です。 Vue.jsは一つのファイル内にHTML, CSS, JSを書ける仕様となっているので、Vue.js用のLanguage Serverの開発は大変であり、 また、AstroやSvelteのような同一ファイル内で複数言語が書けるライブラリも同じ悩みを抱えていたそうです。 そこでVolarのコア機能がVolar.jsとして切り出され、少ない記述で高品質・高機能なLanguage Serverを作れるようになったそうです。 Volarは知っていましたが、Volar.jsはこのセッションで初めて知りました。 Vue.jsの枠を超えて発展しているライブラリの存在を知ることができて大変勉強になりました。 パネルディスカッション Vue.js 日本ユーザーグループ代表のkazupon氏を始めとする豪華なメンバーでVue.jsについてのパネルディスカッションでした。 Vue.jsを採用したきっかけ、Vue.jsのいいところ、イマイチなところ、今後のVue.jsに期待することなど興味深い話を聴くことができました。 一番印象的だったのは「今後のVue.jsに期待したいこと」という話題で、「ViteやVolarのようにVue.jsの枠を超えてエコシステムが発展していってほしいし、この辺りをもっともっとアピールしていってほしい」という発言があり、ここでもVue.jsのエコシステムの強さが語られていて、Vueコミュニティの良さを改めて感じることができました。 Vue.js クリニック Vue.js に関する質問が事前募集され、Evan Yuなどコミッター陣に質問できるセッションでした。 一番人気は「reactive vs refはどちらを使うべきか」という質問で、 Evan Yuが「ref統一がおすすめ」とお墨付き をくれたことがありがたかったです。 元々ドキュメント上ではreactiveがrefより先にありましたが、最近refを上にしたのも、refを使おうというメッセージとのこと。 reactiveはデストラクチャでリアクティビティが失われる、など間違いを犯しやすいことが理由の一つですが、 reactiveが今後なくなることはないとのこと。 例えば、一回アサインしたらその後変数を置き換えない、2つのobjectで共通のstateを必ず担保したい、必ずそのobjectがあるという状態を担保したい場合などでは有用とのこと(ex, 認証のステータス管理(今ログイン中かどうか)など) 謝辞 協賛を通じてVue.jsコミュニティのコミュニティの盛り上がりに貢献できたことを大変嬉しく思っています。 カンファレンス当日、BASEのブースにもたくさんの方がお越しいただけました。本当にありがとうございます。 このような場を通じてBASEのこと少しでも知っていただける方が増えると嬉しいです。 また、カンファレンスを企画・開催してくださったVue.js 日本ユーザーグループの方、並びにスタッフの方にこの場を借りて御礼申し上げます。
アバター
Vue Fes Japan 2023 公式サイトのロゴ 2023/10/28(土)に開催される Vue Fes Japan 2023 に BASE がシルバースポンサーとして当カンファレンスに協賛します。 vuefes.jp こんにちは。 @gatchan0807 です。今回は BASE のスポンサリングについてのお知らせ記事です。どうぞ最後まで読んでいただけると嬉しいです。 BASE が Vue.js のカンファレンスに協賛するのは、昨年度オンラインで実施された Vue Fes Japan Online 2022 に続いて今回で2度目となります。 Vue Fes Japan Online 2022 に BASE からエンジニアが登壇及び協賛、スタッフ参加しました! 当プロダクトチームブログでの Vue.js に関する記事の寄稿などに加え、引き続きVue.js コミュニティへの貢献することができ、大変嬉しく思います。 当日のスポンサーブースについて 今回 BASE は 1階コミュニティルーム内にスポンサーブースを設置する予定です。 リフレッシュメントスペースやタトゥースペースにご用の際に合わせて BASE のサービスロゴのティピを目印にぜひお立ち寄りください! スポンサーブースでは 「Vue.js、あなたはどう使ってる?アンケート」 という企画を実施予定で、 Vue.js についての簡単なアンケートにご回答いただくと BASE オリジナルデザインのコーヒーバッグをお配りします。 こちらのコーヒーバッグはネットショップ作成サービス「BASE」をご利用いただいている、 COZY COFFEE さんのコーヒーバッグで、大変香り高い深煎りコーヒーになっていますのでコーヒー好きの私としては、ぜひ皆さまにも味わっていただきたいです! cozycoffee.thebase.in また、スポンサーブースには昨年度の Vue Fes Japan Online 2022 で「 BASEのフロントエンド組織の人数が2.5倍になって起きた変化 」というスポンサーセッションで発表した私、 @gatchan0807 もおりますので、前回発表時点から1年間でどんな変化があったのかなど、お話できると嬉しいです! 他にも Vue Fes のスポンサーブース全体で行われる企画もあるとかないとか…なので、ぜひ気軽にブースにお立ち寄りいただけますと幸いです! それでは、当日皆様にお会いできることを楽しみにしております!
アバター
BASEのスポンサーブースの様子 こんにちは!すっかり秋らしくなり、涼しくなってきましたね。皆さんいかがお過ごしでしょうか。 さて、この度は、2023/10/08(日)に開催された PHP Conference Japan 2023 にゴールドスポンサーとして協賛し、BASEのエンジニアが登壇しました。 今回は、登壇者からコメントと、会場やスポンサーブースの様子についてお届けします! PHP Conference Japan 2023とは PHP Conference Japan 2023は、2023/10/08(日)に開催された国内最大級のPHPカンファレンスです。 BASE はこれまでにも開催されている PHP カンファレンスへの登壇並びにスポンサーをコミュニティ貢献活動として行って参りました。今回はゴールドスポンサーとして当カンファレンスに協賛しています。 画面左側にBASEのコーポレートロゴ 登壇者のコメント glassmonkey ( @glassmonekey ) イベントお疲れ様でした。永野です。 PHPというテーマを題材にみなさんと交流できて最高に楽しい1日でした。 同時に見たいセッションが複数あったりで体が複数あったら!!と思う1日でした。 登壇内容自体は業務では現状使ってないものの、今後使う可能性が高いWasmについてお話をさせていただきました。 いかがでしたでしょうか? 私自身、Wasmはブラウザ上の技術という固定観念があったのですが、 学習していくうちにバックエンドエンジニアとしてキャッチアップしておいたほうが良い技術であるという学びがありました。 今回のトークで、その一端を皆さんに少しでもお伝え出来たのなら嬉しく思います。 ぜひ、良きWasmライフを!! speakerdeck.com スポンサーブースの様子 ここからはスポンサーブースの様子をお届けします! 今年は「BASEからのPHPerへの挑戦状」というクイズ企画を開催しました。 「BASEからのPHPerへの挑戦状」を解いている参加者の様子 スポンサーブースに来て頂いた方に、ネットショップ作成サービス「BASE」をご利用いただいている COZY COFFEE さんのコーヒーパックを配布しました。 cozycoffee.thebase.in コーヒーパックとBASEロゴにもあるtipi 「BASEからのPHPerへの挑戦状」の解説 ここからは「BASEからのPHPerへの挑戦状」にて出題したクイズの正解と、正解率、解説をします。 問題1 (正解率: 71.4%) PHP8.0.xのセキュリティサポートの終了日はいつでしょうか? 次の選択肢から1つ選んでください。 2022年11月28日 2023年11月26日 2024年11月25日 2025年12月8日 正解と解説はこちら 2. 2023年11月26日 解説 メジャーバージョン、マイナーバージョンのセキュリティサポート終了日は以下のページで知ることができます。 PHP: Supported Versions (サポート中のもの) PHP: Unsupported Branches (サポート終了したもの) こちらによれば各日付は以下のPHPバージョンのセキュリティサポート終了日になります。 2022年11月28日はPHP7.4 2023年11月26日はPHP8.0.x 2024年11月25日はPHP8.1.x 2025年12月8日はPHP8.2.x 問題2 (出題ミスがありました。詳しくは「正解と解説はこちら」に記載) (参考正解率: 54.3%) empty の挙動で正しいのはどれでしょか? 正しい説明だと思うのを1つ選んでください。 変数が存在し、かつ空文字列またはゼロまたはfalseの場合にtrueを返します。 変数が存在し、かつ空でない文字列またはゼロまたはfalseの場合にtrueを返します。 変数が存在するかどうかを確認し、変数が存在しない場合にtrueを返します。 変数が存在しない場合にtrueを返し、それ以外の場合にfalseを返します。 正解と解説はこちら 出題側で事前に想定していた正解は 2. 変数が存在し、かつ空でない文字列またはゼロまたはfalseの場合にtrueを返します。 でした。 しかしクイズに挑戦いただいた何名かの方から異議がありましたので問題文と答えを見直したところ、正解が異なっていそうだという話になりました。 申し訳ありませんでした。 正しい説明1つを選択する問題でしたが、正解は 選択肢1. と 選択肢3. になります。 解説 empty() で、 true を返すパターンは以下のPHPマニュアルに解説があります。 PHP 型の比較表 empty($x); が true を返すのは以下のパターンです。 パターン1. $x = ""; (空文字列) パターン2. $x = null; (null) パターン3. var $x; (PHP5.0未満でも使用可能な、クラスのプロパティを宣言する方法。) パターン4. $x が未定義 パターン5. $x = []; (空の配列) パターン6. $x = false; bool型のfalse パターン7. $x = 0; 数値の0 パターン8. $x = "0"; 文字列の0 こちらを踏まえて、それぞれの選択肢が empty の正しい挙動かどうかみていきます。 選択肢1, 2, 3は、 empty()がtrueを返す場合 の説明、選択肢4は empty()がtrueを返す場合とfalseを返す場合 について説明していますが、 選択肢1. 変数が存在し、かつ空文字列またはゼロまたはfalseの場合にtrueを返します。 パターン1〜8 を網羅してはいませんが、パターン1, 7, 6 の場合に true を返す、とのことなので、 選択肢1は正解 。 選択肢2. 変数が存在し、かつ空でない文字列またはゼロまたはfalseの場合にtrueを返します。 パターン7, 6については正しい説明になっていますが、 パターン8に関連した説明「空でない文字列で true を返す」のは "0" の場合のみであり、 それ以外の文字列では false を返すので、 選択肢2は不正解 。 選択肢3. 変数が存在するかどうかを確認し、変数が存在しない場合にtrueを返します。 https://www.php.net/manual/ja/function.empty.php には以下の記述があります。 つまり、 empty()  は本質的に  !isset($var) || $var == false  と同じことを簡潔に記述しているだけです。 こちらの記述に合致しているため empty() は、変数の存在を確認していますし、 選択肢3は正解 と言えます。(パターン4の説明をしている) 選択肢4. 変数が存在しない場合にtrueを返し、それ以外の場合にfalseを返します。 こちらもパターン4に合致していますが、他に true を返すパターン1〜3, 5~8について間違った説明になっているので 選択肢4は不正解 。 PHP: empty - Manual PHP: PHP 型の比較表 - Manual 問題3 (正解率: 8.6%) PHP8.3からできるようになった Random\Randomizer クラスの挙動で正しいのはどれでしょうか? 正しい説明だと思うのを2つ選んでください。 echo $randomizer->getBytes(10); echo $randomizer->getInt(0, 100) echo $randomizer->getBytesFromString('abcdefghijklmnopqrstuvwxyz0123456789', 16); echo $randomizer->nextFloat(); echo implode(',', $randomizer->shuffleArray(['佐藤', '鈴木', '高橋'])); 正解と解説はこちら 3. echo $randomizer->getBytesFromString('abcdefghijklmnopqrstuvwxyz0123456789', 16); 4. echo $randomizer->nextFloat(); 選択肢1,2,5はPHP 8.2からできるので不正解です。 選択肢3,4はPHP 8.3からできるようになったので正解です。 PHP: Random\Randomizer - Manual PHP: Random\Randomizer::getBytesFromString - Manual PHP: Random\Randomizer::nextFloat - Manual 問題4 (正解率: 14.3%) PHP8.2で書かれたサンプルコードがあります。インスタンスメソッド、「 Sample::func() 」の仮引数の型に指定してもPHPエラーが発生しないものを3つ選んでください。 (※PHPバージョンは8.2で、PHPコードで定義されているクラスは Sample 以外にないものとします) int|string null namespace void self static never 正解と解説はこちら 1. int|string 2. null 5. self 解説 正解の選択肢1, 2, 5 は仮引数の型に指定できます。選択肢3. は型として指定できません。 選択肢4, 6, 7は、戻り値型には指定できますが、仮引数の型には指定できません。 PHP: 型 - Manual PHP: 型宣言 - Manual PHP: クラス内での関係を示す相対型 - Manual 問題5 (正解率: 25.0%) あなたはBASEに入社して、プロダクトコードを読んでいます。 以下のコードがあったとき、最終的な商品の金額はいくらになるでしょうか?次の選択肢から選んでください。 なおPHPバージョンは8.2で、今回の問題で表示されているクラス以外は無いものとします。 ¥1,045 ¥1,815 ¥2,035 ¥2,145 ¥2,585 https://gist.github.com/cocoeyes02/a51d1855d31d7b85f6b2ec9973b12c36 (コードの行数が長いため、gistに表示しています) 正解と解説はこちら 4. ¥2,145 以下のリンクより、2145が出力されていることが確認できます。 php-play.dev 解説 この問題は、商品、オプション、送料、クーポンを使って税込の合計金額を算出する問題です。 計算の順番は 税抜の合計金額を算出する 税込の合計金額を算出する というようになっています。 税抜の合計金額を算出 まずProductクラスのtotalメソッドを見ると、税抜の合計金額が商品の金額 、オプション料金の合計金額 、送料、割引額で構成されていることがわかります。 <?php public function total () : Price { $ optionTotalPrice = $ this -> optionGroup -> total () ; $ discount = $ this -> coupon -> getDiscount () ; $ totalPrice = new Amount ( 0 ) ; $ totalPrice = $ totalPrice -> add ( $ this -> productPrice -> value ) // 商品の金額 -> add ( $ optionTotalPrice ) // オプション料金の合計金額 -> add ( $ this -> fee -> value ) // 送料 -> sub ( $ discount ) ; // 割引額 if ( $ totalPrice -> isNegative ()) { throw new Error ( 'total price is negative' ) ; } return ( new Price ( $ totalPrice )) -> calculateTaxIncluded ( ItemTax :: getTaxPercentageByItemType ( $ this -> itemType )) ; } 一つずつ算出してみましょう。 まず商品の金額は、Productクラスのconstructで代入された $productPrice をそのまま使っているだけなので1,000になります。 <?php public function __construct ( public ProductName $ name , public Price $ productPrice , // 商品の金額 ... ) { } <?php $ price = Price :: fromInt ( 1000 ) ; オプション料金の合計金額は、OptionGroupクラスのtotalメソッドより、constructで代入されたOptionの金額を合計しています。 今回はオプションを2つ追加しており、100 + 50 = 150になります。 <?php public function total () : Amount { $ total = new Amount ( 0 ) ; foreach ( $ this -> options as $ option ) { $ total = $ total -> add ( $ option -> price -> value ) ; // 各Optionの金額の合計値を算出 } return $ total ; } <?php $ optionGroup = new OptionGroup ( [ new Option ( 'ラッピング' , new Price ( new Amount ( 50 ))) , new Option ( '名前入れ' , new Price ( new Amount ( 100 ))) , ] , ) ; 送料は、FeeクラスのcreateFromSubtotalメソッドより、商品の金額とオプションの合計金額の合計値から、クーポンの割引額を差し引いた小計を使って算出しています。 今回の小計は、1,000 + 150 - 200 = 950 です。 それから、FeeTypeクラスのcreateFromIntメソッドにある対応表に算出した小計をあてはめて、送料を算出しています。 今回は小計が1,000を下回るので、送料は1,000です。 <?php public static function createFromSubtotal ( Price $ price , OptionGroup $ optionGroup , Coupon $ coupon , ) : self { $ subtotal = new Amount ( 0 ) ; // 小計 $ subtotal -> add ( $ price -> value ) // 商品の金額 -> add ( $ optionGroup -> total ()) // オプションの合計金額 -> sub ( $ coupon -> getDiscount ()) ; // クーポンの割引額 $ fee = FeeType :: createFromInt ( $ subtotal -> toInt ()) ; return new self ( new Amount ( $ fee -> value )) ; } <?php enum FeeType : int { case Free = 0 ; case FiveHundred = 500 ; case OneThousand = 1000 ; public static function createFromInt ( int $ value ) : self { return match ( true ) { $ value >= 1500 => self :: Free, // 金額が1500円以上だったら送料無料 $ value >= 1000 => self :: FiveHundred, // 金額が1500円未満、1000円以上だったら送料500円 $ value >= 0 => self :: OneThousand, // 金額が1000円未満だったら送料1000円 } ; } } 割引額は、クーポンの割引額より算出しています。 ただし、ItemTypeがデジタルコンテンツである場合は割引が効きません。 今回は、ItemTypeが予約商品であるため割引が効きます。 よって割引額は200です。 <?php public function getDiscount () : Amount { if ( !$ this -> isAvailable ()) { return new Amount ( 0 ) ; // クーポンが使えない場合は割引額は0円 } return new Amount ( $ this -> discountAmount -> toInt ()) ; } public function isAvailable () : bool { return match ( $ this -> itemType ) { ItemType :: Lottery, ItemType :: PreOrder => true , ItemType :: DigitalContent => false , } ; } <?php $ coupon = new Coupon ( $ itemType , new Amount ( 200 )) ; これで税抜の合計金額を求める上で必要な金額が全て算出できました。 改めて書くと 商品の金額:1,000 オプション料金の合計金額:150 送料:1000 割引額:200 です。 よって計算すると、1000 + 150 + 1000 - 200 = 1,950になり、税抜の合計金額は¥1,950です。 税込の合計金額を算出 税抜の合計金額を算出したら、税込の金額を算出します。 税込の金額は、商品タイプによって税率が決まっています。今回は予約商品を扱っているので、税率は10%になります。 <?php return ( new Price ( $ totalPrice )) -> calculateTaxIncluded ( ItemTax :: getTaxPercentageByItemType ( $ this -> itemType )) ; <?php public function calculateTaxIncluded ( TaxType $ taxType ) : self { $ amount = $ this -> value -> multiply ( $ taxType -> getPercentage ()) ; return new self ( $ amount ) ; } <?php class ItemTax { public static function getTaxPercentageByItemType ( ItemType $ itemType ) : TaxType { return match ( $ itemType ) { ItemType :: DigitalContent, ItemType :: PreOrder => TaxType :: Ten, // 予約商品の税率は10% default => TaxType :: Eight, } ; } } 税抜の合計金額は1,950なので、計算すると 1,950 × 1.1 = 2,145 税込の合計金額は¥2,145になります。 今回はtotalメソッドの値をそのまま出力しているため、totalメソッドの返り値である税込の合計金額が答えになります。 よって答えは、選択肢4の¥2,145です。 <?php echo $product->total()->value->toInt(); 謝辞 協賛・社員のスピーカー参加を通して PHP コミュニティの盛り上がりに貢献でき、弊社としても大変有意義な時間となりました。 スタッフの方々には業務でお忙しいにも関わらず、多くの時間をカンファレンス準備へ注いでいただいたかと思います。この場を借りて御礼申し上げます。 それでは、来年もお会いしましょう!
アバター
PHPカンファレンス2023公式サイトのロゴ 2023/10/08(日)の日程で開催される PHP Conference Japan 2023 で BASE に所属するエンジニアが登壇する他、ゴールドスポンサーとして当カンファレンスに協賛します。 BASE はこれまでも PHP カンファレンスへの登壇並びに協賛をしています。 PHPカンファレンス2022に5名のメンバーが登壇・ゴールドスポンサーとして協賛しました PHPカンファレンス2021に5名のメンバーが登壇・プラチナスポンサーとして協賛しました PHP Conference Japan 2020に4名のメンバーが登壇!プラチナスポンサーとして協賛しました! PHP Conference Japan 2019に3名のメンバーが登壇・プラチナスポンサーとして協賛しました 「PHP Conference 2018」にゴールドスポンサーとして参加しました - BASE Book(ベイスブック) PHPカンファレンス2015 - #phpcon2015 今年で通算7回目のスポンサーとなり、PHP コミュニティへの貢献を続けることができ大変嬉しく思います。 また、スポンサーブースでは、「BASEからのPHPerへの挑戦状」というクイズの催しを行います。ぜひお越しください! セッションの内容について PHP で始める WebAssembly 入門 13:10〜 Track6 レギュラーセッション(25分) 所属:BASE, Inc. / NEW Division / BASE BANK fortee.jp こんにちは @glassmonkey こと永野です。 みなさんはWebAssembly(Wasm) はご存知でしょうか?触っていますか? 私自身、業務でWasmを使っているわけではありませんが、 ここ数年でブラウザ上の技術を飛び越えて、様々なユースケースが実現できる技術になってきました。 まさか ブラウザ上でPHPが動かせる なんて夢にも思いませんでした。 php-play.dev ブラウザ以外の用途も広まってきたこともあり、PHPerもとい、バックエンドエンジニアとしても抑えておくべき技術になってきたように感じます。 ぜひ皆さんとWasmが仲良くできるように取り持つことができたらと思います!! そして、現地で皆さんにお会いできることを楽しみにしています!! 最後に PHP Conference Japan 2023 の当日のチケットは下記よりお申し込みいただけます。 phpcon.connpass.com それでは当日皆様にお会いできることを楽しみにしております!
アバター
Cloud Operator Days Tokyo のロゴ こんにちは。BASE でエンジニアをしている 02 です。 このたび、 Cloud Operator Days Tokyo 2023 に登壇し、「輝け!クラウドオペレーターアワード2023」において審査員特別賞(変革編)を受賞しました。 今回は、登壇についてのコメントと、クロージングイベントでの様子についてお届けします! Cloud Operator Days Tokyo 2023 とは Cloud Operator Days Tokyo 2023 公式サイトより Cloud Operator Days Tokyo 2023 とは、クラウドの運用者をテーマとした技術イベントです。 cloudopsdays.com 2023/08/21(月)も配信開始した約60ものオンデマンドセッションと、2023/09/14(木)に開催されたオフラインクロージングイベントで構成されており、運用にまつわる技術やノウハウ、事例や悩みを共有していくイベントになります。 登壇についてのコメント 今回は業務でやったしくじりと、AWS Lambdaを使ったアーキテクティングによる運用改善の話をしました。 アーキテクティングというのは全体解に当たるアーキテクティングと、局所解にあたるアーキテクティングの2種類があると思います。今回の改善はいわゆる局所解に当たるものです。局所解にあたるアーキテクティングは、一見何故こんなアーキテクチャに・・・?と思えても、背景を知るとそうせざるを得なかった様々な理由が見えてきます。 これは持論ですが、同じアーキテクチャや技術の選定結果でも、より質良く、より多く理由を言える方がより良い意思決定であると考えています。今後もビジネスを守るため、局所解となるアーキテクティングであってもより良い意思決定ができるよう精進していきたいです。 オフラインクロージングイベントの様子 ここからはオフラインクロージングイベントの様子をお届けします! 午前はディスカッション形式で進行していく Unplugged セッションからスタートしました。 各セッションごとにオブザーバビリティ、GPUクラスター、AIOps、ハイブリッドクラウドなどのテーマが決まっており、セッションリードと参加者の双方向コミュニケーションによって、テーマについて理解を深めていきました。 会場はこのような感じで、皆さん積極的に議論しております! pic.twitter.com/bA9g80VMc5 — Cloud Operator Days (@cloudopsdays) September 14, 2023 「モニタリングは既知の未知を扱いますが、オブザーバビリティは未知の未知を扱うのです。」 この4象限のうちの、未知の未知を無くそうね、という話(右下から右上へ、右下から左下へ) #CODT2023 https://t.co/s2nrUcOojt — 02 (@cocoeyes02) September 14, 2023 午後になるとオープニングが始まり、Keynoteセッションが始まりました。 クロージングセッションが始まりました! 開始の挨拶は実行委員長の長谷川からです。 pic.twitter.com/6js6S9c3s5 — Cloud Operator Days (@cloudopsdays) September 14, 2023 モニタリングツールを選定する時はアプリケーション開発者とインフラエンジニアの両者で選定するのが重要。 #CODT2023 pic.twitter.com/xfGCJGFjAt — Cloud Operator Days (@cloudopsdays) September 14, 2023 休憩時間では、スポンサーの皆様が出しているブースにて、交流を深めたりグッズが貰える催しが開催されていたりしました。 #CODT2023 New Relicさんのブースでもろた! pic.twitter.com/QTbquZOmCl — 02 (@cocoeyes02) September 14, 2023 休憩が終わると、次は「輝け!クラウドオペレーターアワード2023」の表彰式が行われ、そこで審査員特別賞 変革編を受賞しました。 BASE株式会社 大津様 審査員特別賞 変革編のご受賞おめでとうございます! #CODT2023 pic.twitter.com/fRAW7jk80l — Cloud Operator Days (@cloudopsdays) September 14, 2023 正直に言うと、受賞できるとは思っていなかったので、とても驚きました。 受賞時のコメントでも述べましたが、今回の経験から学んだことは、クラウドの知識がビジネスを守る上で非常に重要であるということです。しかし、それ以上に私たち自身がクラウドの運用者であるというオーナーシップがさらに重要だと考えています。 今回受賞できたのは、このオーナーシップを持っていたチームメンバーや、相談に乗ってくれた同僚の皆さんのおかげだと思っています。この場を借りてお礼申し上げます。 今回の改善に満足せず、ビジネスを守り続けるためにさらなる改善を進めていきたいです! 謝辞 スタッフの方々には業務でお忙しいにも関わらず、多くの時間をイベント準備へ注いでいただいたかと思います。この場を借りて御礼申し上げます。 それでは、来年もまた皆様にお会いできることを楽しみにしております!
アバター
こんにちは。9月に入り、最近は少しずつ涼しくなってきましたね。 今回は、2023/09/13(水)に開催されたFutureStack Tokyo 2023のGame Dayへの参加レポートをお届けします。 FutureStack Tokyo 2023 とは 今回は実に4年半ぶりの開催とのことでした。New Relic社主催の、デジタルサービスや情報システムのオブザーバビリティに関する最新テクノロジーや活用例を豊富なトークセッションや基調講演で紹介するカンファレンスです。トークセッションの他にも、スポンサーブースやハンズオントレーニング、 New Relic University特別講座などコンテンツが豊富で参加メンバー一同とても楽しく有意義な時間を過ごすことができました。 newrelic.com Game Day とは カンファレンスの中で、4-5人のチームでさまざまな障害の課題に対しNew Relicを使って解決していくGame Dayと呼ばれるイベントに弊社から開発メンバー5名が参加してきました。 弊社では、2020年からNew Relicを活用してサービスのモニタリングに取り組んでいます。 newrelic.com devblog.thebase.in アラートやダッシュボードなど日頃からNew Relicを開発で利用しているエンジニアとして、今回は腕試し & 新しい学びと刺激を得られるといいなと思い参加させていただきました。 参加レポート @tac_tanden こんにちは。BASE株式会社でエンジニアリングマネージャーしている  @tac_tanden です。 自分は開発チームのマネージャーとして、日頃からサービスの安定性とパフォーマンスに関心を持っています。今回、Game Dayにおいて実際の問題に直面しチームがどのように対処するかをシミュレーションできたことはよい体験でした。また、何よりとても楽しい時間を過ごすことができました。 自分が担当しているチームでは、普段の業務の比重が開発にどうしても寄ってしまうという課題があります。 そんな中、Game Dayで体験した New Relic で課題のトリアージをしながら問題を解決していく作業は、自分たちの普段の機能開発の優先度を決める進め方に近いものでした。 New Relic を使うことで機能に手を入れる優先度を判断する精度を上げることができると改めて感じ、自分たちもまだまだ日常の業務の中で New Relicを使ってやれることは沢山あるなと刺激を得る貴重な機会となりました。 準備をしていただいたNew Relic社の方々にこの場を借りてお礼申し上げます。ありがとうございました。 @OgasawaraYuki こんにちは、BASE株式会社でバックエンドエンジニアをしている小笠原です。 今回は改めて普段業務でも利用しているNew Relicに対する理解を深め、日々の業務でより活用していきたいという思いでGame Dayに参加しました。 前半戦ではAPM、Dashboard、Infrastructure、Basic Alertなど、New Relicのさまざまな機能を使ってアプリケーションをモニタリングする方法を課題形式で学びました。私は今までダッシュボードを使用することが多かったのですが、APMを活用すればエラー分析やボトルネックの調査などはより詳細な情報を簡単に分析できるとことを知りました。特に印象的だったのは、Instant Observabilityやサジェスト機能によって数クリックで実践的なAlert設定やダッシュボードを構築できたことです。New Relicの簡単さ、便利さを改めて実感しました。 後半戦では、前半戦で準備したモニタリング基盤を駆使し、架空のサービスに発生する問題に対処する課題に取り組みました。「掲げられたビジネス目標を達成する」というとても実践的な課題で、アプリケーションの障害検知だけでなくビジネスメトリクスも可視化し、問題の影響度や優先順位を総合的に判断して対処することが求められました。実際に、問題を解消した直後にビジネスメトリクスに改善が現れた時にチームで歓声が上がっていたのが印象的に残っています。ビジネスとサービス開発のメトリクスを一貫してNew Relicで管理できることで、エンジニアの日々の開発が如何にビジネスに影響を与えているかを意識できるようになるのもNew Relicを活用する上での大きな利点なのではないかと感じました。 今回のイベント参加を通して初めて触った機能も多く、新しい発見がいくつもありとても勉強になりました。今回学んだことを業務にも取り入れて、BASEが提供するサービスの向上に活かしていきます。 @zawa Game Dayはチーム対抗戦で、前半戦では課題を通してAPM画面の解説やダッシュボードの基本的な作成方法を学びました。後半戦では応用編として、New Relicを駆使して仮想のECサイトの問題を分析・改善をするという内容でした。 ゲーム感覚で学ぶことができた上に、実務に直接役立つ知識も得られたので、とても有意義な内容でした(惜しくも優勝を逃したので次こそは....)。 イベント中にはリアルタイムで得点が集計され、会場のメイン画面に表示されていたのですが、New Relicのダッシュボードを利用していたとのことで、活用の幅が本当に広いなと感じました。 私たちのチームでは、これまでもNew Relicのダッシュボード機能を活用してアプリケーションのパフォーマンス監視やエラー状況の監視などは取り組んでいました。しかし、アプリケーションだけではなく、インフラやDBも状況を監視したり、それらを使ってアラートを作成したり、まだまだ活用できる部分はあるなと感じました。 今後はNew Relicの活用方法をさらに深め、もっと業務に役立てていきたいなと思いました。 @oliver(FurukawaToshiaki) BASEに入社してから本格的にNew Relicを使用するようになりました。当初、私はNew Relicについてほんの少しの知識しか持っていませんでした。Game Dayというイベントは、午前と午後の2つのセッションに分かれており、それぞれが異なる内容を提供しています。 午前のセッションでは、APM画面やWeb performance monitoring画面の使い方、ダッシュボードの作成方法など、New Relicの基本的な機能を習得するための課題が提示されました。これにより、初心者である私でもNew Relicの機能に迅速に慣れることができました。 午後のセッションでは、午前中に作成したダッシュボードやError Logを使用して、デモ用のECサイトで発生したエラーのトラブルシューティングを行いました。NRQLを用いてリアルタイムに情報を取得する技術だけでなく、効果的なダッシュボードの準備やAPM、Logsの使用方法の重要性も学ぶことができました。 ソフトウェアエンジニアとして必要なスキルは多岐にわたりますが、Game Dayに参加したことで、New Relicの知識と経験も非常に価値があると実感しました。この経験を基に、今後はさらにNew Relicを深く学び、スキルアップを図っていきたいと考えています。そして、来年もGame Dayに参加したいです。 (写真は、いただいたノベルティです) @gan.seki BASEでバックエンドエンジニアをしている @gan.seki と申します。New Relic株式会社が主催するFutureStack Tokyo 2023のGame Dayというトレーニングプログラムへ参加してきました。 Game DayはNew Relicの各種機能を活用してシステムのモニタリング基盤を作成する前半とその基盤を利用して、システム障害を解消する後半という2パーツがあります。前半戦で、特に印象に残っているのはNew Relicの便利さです。実戦レベルで利用可能なサービスレベルやダッシュボードでも、New Relicが用意してくれる instant observability を使えば、数秒程度で自動作成してくれます。 後半戦で、システム障害を解消する動きがダミー ECショップの売上増加へ直結することを実感しました。前半戦で購入金額などのカスタム属性を利用して作った売上監視チャートから、 hot fixがデプロイされるたびにショップ売上が劇的に上昇することをリアルタイムで観察できました。これは、New Relic社CEOのビル・ステイプルズ氏が基調講演の中で言及した、 DXとオブザーバビリティがビジネス価値をもたらすことにもつながります。 加えて、日々のプログラミングの中で、ペアプロや共同作業を練習しているこそ、コンテストの本番でもよいチームワークができ、個人的に良い経験となりました。今回のGame Dayへ参加する同チームの方々に感謝いたします。 終わりに カンファレンスへの参加を通してオブザーバビリティやNew Relicへの理解や熱量が高まり、非常に有意義な時間となりました。 また、Game Dayでの挑戦のしがいのある & 勉強になる課題や、当日のイベント進行を含め快適なイベント環境を準備していただいたNew Relic社の皆様に感謝いたします。 また次回の開催があればぜひ参加させてください。ありがとうございました。
アバター
導入 BASEでは、2023年3月頃に「あと払い(Pay ID)」というBNPL(Buy Now Pay Later)のサービス提供を開始しました。BNPLとは、いわゆる後払い決済のことで、今回、BNPLのシステムを一部内製化した上で、世の中にリリースしました。BASEとしては「決済手段を内製化する」ための第一歩であり、ありがたいことに国内の決済業界で、少しばかり話題になりました。 リンク先:2023/4/11 日本経済新聞 今回は、BNPLという決済システムの開発において、どのような困難があり、どう克服していったのかについて、開発に携わったPay IDチームのエンジニアの視点で書きます。※ なお、このテックブログの読み手として2つのセグメントを想定しています。 ドメイン知識が複雑なアプリケーションを開発をする人 決済システムの仕事に携わる業界の方々 先に結論を書くと、伝えたいことは「ドメイン知識に精通したエンジニアが1人いると、開発スピードが上がるよ」ということです。 ドメイン知識を論点に置いた背景には、大きな時代の流れがあります。 肌感覚として、webシステムの潮流を10年スパンで捉えると、アプリケーションの実装に求められるドメイン知識が、より高度になっていると感じています。特に、2010年代後半以降は、BtoB向けのSaaSが台頭し、結果として開発に求められるドメイン知識が飛躍的に難しくなっているように見えます。このため、実装に携わるエンジニアが複雑なドメインと向き合う話は、今後、徐々に増えていくのかな、とも思っています。 それでは、さっそく、BNPLの開発を紐解き、複雑なドメイン知識との向き合い方を言語化していきます。 ※ 執筆者:とあるBackend Engineer ドメイン知識を蓄積する 開発スピードを早めるために ドメイン知識と向き合う上で、単に「業務知識を知ろう」という意識だけでは不十分だと思っています。所作としては正しいものの、向かうべき目的が定まっていないため、ゴールが見えない迷宮路を彷徨うことになりかねないからです。 そこで、BNPLの開発にあたっては「コミュニケーションの円滑化」を最終目的に据えました。 業務上のコミュニケーションが円滑化すれば、初期段階の要件定義、中期段階の仕様策定のスピードが早まりますし、思っていた物とは機能が違うという「手戻りの悲劇」を減らすことができます。全ては「開発スピードを早めることに効いてくる」わけです。 この目的設定には、全社的な理由があります。2022年2月の時点で、BASEは上場企業として、プレスリリースを通じて経営方針を発表する中で「BNPLへの参入」を公表していました。すなわち、投資家に向けた約束があり、それは裏切ってはいけないわけです。 [画像]BASE : 2021年12月期 第4四半期決算説明会資料(2022年2月15日公表) このため、BNPLの開発にあたっても「開発スピード」を早めることは至上命題であり、社内ではリリースターゲットが2023年3月ごろに設定されていました。そして、BNPLのプロジェクトが社内で立ち上がり、実際に私自身が「要件定義」「仕様策定」が関わり始めたのは2022年7月1日のことでした。 すなわち、2022年7月〜2023年3月という「約9ヶ月間」で、BNPLをリリースすることが、必達KPIに設定されたわけです。この期間で決済の内製化を実現するためには、とにかく極限までスピードを速めることが至上命題となりました。 2022年2月 :BNPLへの参入を公表 2022年7月 :BNPLのプロジェクトが本格立ち上がり 2023年3月頃:リリースのターゲット目標 つまり、ドメイン知識を獲得して、コミュニケーションを円滑化し、実装スピードを最速化することは、「あったらいいな」ということではなく「何がなんでも、絶対にやらなければならない」ことだったのです。 ドメイン知識をキャッチアップする 私自身は、2022年7月ごろに、BNPLの開発に参画することが決まり、すぐさまドメイン知識のキャッチアップを行いました。 少し調べていくと、BNPLという言葉は流行語的な側面もあり、その本質的な実態は「後払い」に分類されることが見えてきました。そこで、後払いに関するドメイン知識をキャッチアップすべく、下記3つの観点からインプットを行いました。 後払いの会計知識 後払いの法律知識 後払いの業界知識 とはいえ、あまり時間をかけることもできないので、BNPLの開発に参画して、最初の約1ヶ月で集中的にドメイン知識のキャッチアップを行なっています。この間も、PMやデザイナーと一緒に、要件定義・仕様策定を並行で行っており「実装の手を動かさずに考えること」「コミュニケーションをとって要件と仕様を固めること」「ドキュメントを書くこと」が、最初の1〜2ヶ月の仕事の全てでした。 会計知識について BNPLは「サイクルの長いお金の流れを生み出す」プロダクトであるがゆえに、その前提として、深いお金の知識が必要になります。 単純に「売上が上がった」「利益が出た」「成長率が〜%」という話ではなく、PL(損益計算書)・BS(貸借対照表)・CF(キャッシュフロー計算書)に、それぞれどのような影響を与えるかを、BNPLが提供する各機能ごとに認識する必要があります。 加えて、BASE社内だけではなく、商品を購入したお客様、BNPLの提携パートナー企業様、商品を販売するショップオーナー様に、それぞれ、どのようなお金の流れが発生するのかを理解すると、ここで初めて「お金の流れ」を腹落ちすることになります。(これらの利害関係者様の仕訳も空論で想定しつつ、流れを理解していきます) すると、BNPLに関わるお金の流れが見えてきて、そのワンサイクルがとてつもなく「息の長い」ものであることが見えてくるわけです。 すなわち、簿記や仕訳といった会計の基礎を理解しつつ、BNPLが、利害関係者様の財務状態にどのような影響を与えるかという理解に努めました。 持論ですが、BNPLは、究極的にBS(貸借対照表)が大事なプロダクトであり、PLだけを見て満足してはいけないと思っています。取引で発生した債権(債務)の最終着地点と、それがもたらす意味について、よく考えなければならないからです。ここまで咀嚼できれば、利害関係者様が何を求めているかがわかるため、コミュニケーションが円滑化してきます。 なお、これは極論なのですが、勘定科目の金額を見ると、その背景にある人や、関係者の想いが見え透くこともあるので、ごく稀に勘定科目に感情移入して想いを馳せることがあります。BNPLに関連する勘定科目は、特に、さまざまな人間ドラマを抱えた科目です。 勘定科目に感情移入できるほどに解像度が高くなれば、会計のドメイン知識としては十分だと思っています。 法律知識について システム開発と法律というのは、非常に遠い存在であるように見えますが、今後はより一層、非常に密接な関係になってくると予想しています。法律というのは、問題が顕在化し、世論の事情によって、事後的に「制定」されたり「緩和」されたりする制度であり、静的ではなく、動的な存在です。したがって、webシステムが社会に与える影響が大きくなればなるほど、法律が制定されたり、改正されるのは、自然な流れです。 加えて、BNPLは「金融」というジャンルで取り扱われるプロダクトであり、業界ごとの特別法を守る必要があります。 法律の専門家ではないので、詳細な言及は避けますが、一般的な金融・決済領域に絞ってみても、信用販売であれば「割賦販売法(1961年施行)」、前払いであれば「資金決済法(2010年施行)」、お金の貸し借りでは「貸金業法(1983年施行)」や「出資法(1954年制定)」といった具合に、行きすぎた行為を抑止するために、先人の知恵としての法律が存在しています。 そして、改正貸金業法(2006年可決)のように、法律改正によって、業界の趨勢(引当金計上による財務状態の悪化など)に大きな影響を与えることもあります。 つまり、BNPLという「金融プロダクト」を開発するにあたって、法律は避けて通れない道であり、要件定義に強い影響を与えるので、よく理解する必要があると考えています。 業界知識について BNPLは単なる流行語に過ぎず、その実態は後払いです。商品を決済した後に、支払いを行うという商行為であり、本質的には、ただそれだけのことです。BNPLという言葉に、意味はありません。 にもかかわらず、不思議なことに、この「後払い」という業界では「月賦」「信用販売」「クレジットカード」「後払い」「BNPL」といった具合に、さまざまな呼称が乱立しています。加えて、それぞれの呼称ごとに、業界のトッププレーヤーが個別に存在しており、全てを兼ね備えるような超大企業が存在しません。考えてみれば摩訶不思議なことです。 この不思議さに隠された論点があるような気がしたため、『CardWave(1987年12月創刊)』などの業界雑誌でインプットを図りました。ここでは、最新の雑誌ではなく、その業界雑誌の「創刊号」や、新しい決済手段が出た時の「特集号」を中心に読破していきます。当時の批判、問題点、仮説を検討することで、現在に至るまで、それが解決されたのか、残存したままなのかを検討し、本質的な課題を探っていきます。 そして、後払いという業界の成り立ちを踏まえつつ、各領域のトッププレーヤーが入れ替わってきた変遷と、その要因を、マクロでは経済性(および規制)の観点から、ミクロでは競争優位性の観点から検討し、その一部を、約1万字の社内ドキュメントとしてまとめました。この辺りの話は、特定の本に書かれているわけではないのですが、公開情報を調べれば(業界各社の有価証券報告書を注釈含めて全ページ読むなど)、いろいろなことがわかります。 また、業界内では競争優位性があるとされる「ある機能(ここはセンシティブなので、ぼかしておきます)」が、実は、ほとんど効いてこないのではといった仮説も提唱してみたりと、事業の勘所が何かを日々考えています。 このように、業界知識を通じて、いろいろな仮説をフラグとして立てておくと、事業を執行する人とのコミュニケーションが円滑になると思っています。エンジニアとしても、BNPLという機能価値において、どうやって(技術的に)競争優位性に貢献するのかを考える良いきっかけになりました。 注意すべきは、業界知識というのは、単に「業界の常識」を知るということではないことです。 あくまで、事業における論点、すなわち「競争優位性を決定づけるものは何だったのか?」という論点を探ることに注意を向けています。キーファクターを探り当てて、そこに貴重なリソースを集中すべきと考えているからです。 ドメイン知識の使い所 ここまで「ドメイン知識とは何か」を見てきました。それでは、ドメイン知識があることによって、どのように開発スピードに効いたのでしょうか? BNPLの開発事例をもとに、3つのフェーズに分けて、明らかにしていきます。 要件定義・仕様策定 開発チーム組成・詳細設計・実装 QA・リリース 要件定義・仕様策定 要件の変更を提案する 1つ目のメリットは「要件定義を変更できる」ことです。 BNPLでは、要件定義を開始した直後の2022年8月頃に「発送時におけるショップ様への入金」における要件で、開発の視点から、要件の変更を提案しました。これは何かといえば、従前の要件が「商品の発送後、着荷をもってショップ様が売上金を引き出せること」であったのに対して、「商品の発送を持って、ショップ様が売上金を引き出せる」という代案を提言することにしたのです。 この要件のメリットを一言で言えば「ショップ様のキャッシュフローの改善」です。商品を発送してから着荷には数日かかるのが一般的であることを考えると、発送段階で売上入金ができることは、キャッシュフローにおける入金サイクルを数日早めることを意味します。 もう一つの隠れたメリットは、システム面における複雑性の解消です。発送時点ではなく、着荷時点にイベントを発生させた場合、新たにバッチ処理などの実装が必要になるため、実装の工数が増大します。加えて着荷を考慮すると、キャンセルにかかる処理がかなり複雑になってきます。「発送後〜着荷前におけるキャンセル処理」を考慮して実装を行うと、それなりの工数増大が予想されますし、実装の量が増えれば「バグ」の潜在的なリスクも高まります。 したがって、エンジニアの視点で「メリット・デメリット」をまとめ、会計のドメイン知識を前提に、要件を変更することを提案しました。 そしてPMを通じ、最後は執行役員の判断で要件変更が決まり、このときは、かなり嬉しかったことを覚えています。「着荷」というヘビーな要件の重しが取れたことで、BNPL全体の実装はシンプルになり、リリースへの手応えを感じることができたからです。 要件の優先度を決める 2つ目のメリットは、「優先度を考慮したリスクヘッジ」です。 BNPLでは当初、実装予定だった「A」という重たい機能があり、最初のリリースにおける要件に含まれていました。 しかし、要件定義と仕様策定を進めていき、2022年9月初旬に実装の詳細設計に入った段階で「どうもこれは、かなり重くなりそうな機能」という香りが漂ってきました。この段階で、そのまま実装に入る選択肢としてありましたが、不確実性が高いと判断し、この機能「A」については、実装を遅らせることをPM に相談したのです。 このとき、ドメイン知識があったため、この機能「A」については、BNPLのファーストリリースに存在しなくても成立し得るということを理解できており、これをもとに説得のロジックを組み立てて相談しました。 「・・・と考えたんですが、これって、どうですかね?」 結果としては、この機能については、リリースを遅らせる社内合意を得ました。この動きによって、現実的な開発のスケジュールを立てることができ、不確実性がかなり軽減したのです。BNPLの期日内リリースという動きの中で、間違いなく、一つの大きなブレイクスルーでした。 要件を所与のものと考えるのではなく、機能が実現したいことに対して、事業全体として「MUST・WANT」なのかを切り分けることができていたからこそ、リスクヘッジが実現したと思っています。  議論して仕様を素早く決める 3つ目のメリットは「スピーディーな仕様策定」です。 BNPLでは、要件に基づいて仕様を考える際に、デザイナー、PM、エンジニアが一体となって具体的な方向性を決めていきました。ユーザー様にとっての「メリット・デメリット」、開発面における「不確実性の高さ」を考慮しつつ、最終的な仕様に落とし込む議論をするわけですが、ここでのコミュニケーションのスピードはかなり早かったと感じています。イメージとしては、1回60分のMTGで、1画面の仕様を大方、決めていくような速度感です。 BNPLの開発における仕様面の複雑さの例をあげると、その一つは、購入者様におけるステータスの多さに起因しています。購入から実際にお支払いをするまでのスパンが月をまたぐほど長く、加えて、その間のステータス(請求完了やお支払い済みなど)も目まぐるしく変化することから、あらゆる決済手段の中でも、そのステータスの種別は群を抜いて多い部類に入ります。だからこそ、細かい仕様をスピーディーに決めることによる生産性は高く、前提としてのドメイン知識が活きることころです。 また、仕様策定の段階では、社内はもちろん、社外とも連携するため、提携パートナー様とも頻繁な打ち合わせを行なっています。ここでも、その場、その場でAPIなどの仕様を決めていくため、スピードを出すうえで、ドメイン知識が活きた形になりました。何らかの要望を受けた場合は、心の中に手を当てて、5秒で概算見積もりを出し(自分で実装する前提で出します)、ドメイン知識を前提として、落とし所を探っていきます。 他にも、仕様が固まる段階で、経理チームとの連携が取れた点でも、スピードを発揮しました。会計知識をベースにして、BASEの財務に影響がある実装箇所を共有し、早めのレビューで合意が取れたことで、手戻りリスクを最小化できたかなと思います。 このように、実装の着手前の段階から、ドメイン知識はコミュニケーションを促進しました。実装前の段階で「いかに不確実性を最小化できるか」は、リリースの期日を間に合わせる上で最重要の課題であり、最良の手は打てたかな、と考えています。 チーム組成・設計・実装 実装範囲を明確にする 要件定義と仕様策定が佳境に入った段階で、開発チームの組成を行いました。BNPLは新規実装ですが、すでに稼働している既存コードへの影響箇所が広いため、実装がピークを迎えた12月頃には10名前後が開発(アプリ=iOS/Android、フロントエンド、バックエンドなど)に携わっており、開発のサイクルを効率よく回す必要がありました。 まず、チーム組成の段階で、第一に、ドメイン知識が生きてくるのは「各開発者が実装する範囲」を明確にするという点です。 BNPLの実装にあたっては、ドメイン知識をもとに、ある程度の粒度で領域を切り分けて、それぞれが各範囲の実装を進めるスタイルで遂行することにしました。この時に「どの粒度で範囲を区切るか」という点でドメイン知識が必要になります。 基本的に「実装者数名につき、1つのドメイン」を担当するという分業の方式で、実装を割り振っています。これは、実装する人が「BNPL」という機能の全体像を把握しなくても、その領域の実装を完遂することで、結果としてリリースに間に合うということを意図しました。 もちろん、理想論を言えば、全員が「BNPLの全て」について知った方が良いわけですが、現実的にはタイムロスとなります。何事も選択と集中が大事であり、実装に携わる人が「各担当ドメイン」に集中できる環境を、チーム組成の前提としました。 不確実性を認識する とはいえ、ドメインに区切った時の落とし穴があります。それは「ドメインに区分しにくい領域」や「異なるドメインをまたぐ範囲」が生じるということです。ここは、実装上、不確実性を帯びることになります。 具体的には、外部パートナー様のAPIを実行する箇所であったり、いろいろなドメインから参照されるテーブルの設計、月1回行う請求の締め処理バッチなどが、BNPLにおける「不確実性の高い領域」に該当しました。これらは、複数のドメインをまたぐため、BNPLの要件と仕様を理解し、さらには、各ドメインを担当する開発者と十分な意思疎通を行う必要があります。この点に関しては、私自身が実装を担当することで、カバーすることにしました。 2022年8月中旬という、チームとして実装に入る直前の段階で、私の方で、大方のテーブル設計を完了し、外部APIの呼び出し口についても、社内専用のライブラリー(SDK)を0から作ることにして、最優先で完了しました(型厳密な実装に仕立てたので、とっても楽しい実装でした)。共通箇所の実装を先に終えてしまうことで、各ドメインの実装を担当した人は、これらの利用に徹することができ、結果として実装の生産性が高まったと思います。 一方で、月1回の請求締め処理の実装だけは、関連する機能への外部依存が相対的に少ないため、実装を遅らせる判断をしました。このように、機能の依存性によっても、優先度を組み替えています。 9月以降は、開発チームのスケジュール管理、仕様のドキュメント化、実装レビュー(BNPLのバックエンド実装については、簡易的ながらも全てに目を通します)、社内・社外とのコミュニケーションを都度行いつつ、不確実性の高いタスクを巻き取るように動きました。 この手の開発では、必ず「予想外」のことがあるため、自らの実働は70%を目安としつつ、常に30%の余力を設けることで、いざという時に備える形で時間配分を設定しました。余力があれば、ひたすらに各種ドキュメントを書き、仕様や開発Tipsの属人化をできるだけ避けることに、時間を投下した形です。 実装の優先順位を決める そして、実装が佳境に入った段階で、ドメイン知識が生きてくるのは「各機能の実装の優先順位」を明確にするという点です。 2022年12月頃にBNPLの実装は佳境を迎え、自身を含めて10名前後のエンジニアが実装を着々と推し進める状態になりました。ここで問題になるのが、本当にリリース予定日に間に合うかということです。この頃は、かなり不安だったのも事実で、なかなかに胃が痛い日々(比喩表現)でした。 [画像] 実装が佳境に入った時のガント このフェーズでは、チーム内の実装の進捗把握を、徹底するようにしていました。 ここで重要なのは「ある機能を実装するのに何日かかるか」を把握することではなく、「実装を進める上で、不確実性はどの程度あるか」「機能間の依存関係に変更が生じ得るか」ということを、常に(毎日)コミュニケーションを取るということです。その上で、実装の不確実性が高そうであれば、担当する機能の優先順を組み替えてみたり、それでも難しそうであれば、私の方で実装(もしくはミニマムのペアプロ)を行ったり、頻繁に実装のMTGを入れたり、ということをしていました。 そして、この時も優先度の問題が出てくるので、その場合はドメイン知識と照らし合わせて、どの程度、温度感の高い機能なのかで判断をしています。例えば、エラー処理1つをとっても、頻出度が低いと想定されるならば、実装の後半に持っていくなどの工夫をし、実装順を組み替えることで不確実性を極小化していくわけです。特に、不確実性の高い機能の実装と向き合う場合は、その想定工数を「楽観ケース」「通常ケース」「悲観ケース」に分けた上で、各シナリオの影響変数を考え、現実的な対策を考えていきます。 結果論として、実装は、ほぼ無事にスケジュール通りに完了しました。開発が佳境だった時期でも、多忙を極めたというわけではなく、比較的、余裕を持って進められたと思います。 今思えば、さまざまなケースを考慮し、優先度を元に着手順を組み換えながら進めており、いずれもドメイン知識が拠り所になっていたな、と思います。 QA・リリース 不測の変更に備える BNPLでは、2023年1月ごろまでに大方の実装を完了し、本格的なQAに着手しました。ここまでくると不確実性はかなり小さくなっており、いかにQAの精度を高め、リリース時のリスクを最小化するかという観点が重要となります。 この終盤フェーズにおいては、ドメイン知識については、ほぼ役目を終えたと言えますが、それでも大小の仕様変更は発生するもので、その都度、対策を練っていく必要があります。 実は、BNPLでは、本番環境へのリリース直前(数週間前)に、重大な仕様変更が発生しました。その機能はぼかしますが、かなり大きな変更で、どう足掻いても避けることが難しいものでした。ビジネス側を担当する方が「顔を真っ青にするほどの変更」だったと言っても、言い過ぎではないレベルの変更だったわけです。実際に関係者が一同に集まったMTGは、それは、とてもすごい空気感だったのを鮮明に覚えています。 とはいうものの、開発面において、その変更に対応する上で要した時間は、数日程度であり、重大なリリースの遅延をもたらすものではありませんでした。MTG後すぐに、バックエンドおよびアプリエンジニアで緊急MTGを行なって、PMとデザイナーを交えて変更方針を決定し、対応にめどを立てたからです。当時のSlackを見ると、下記のような時系列でした。 13:45 仕様変更の噂がたつ 14:00 緊急会議で仕様変更が正式決定 15:00 PM、デザイナー、エンジニアで対策MTGを実施して、対策案を練る 15:42 デザインの変更対応が完了(Figmaへ反映) 15:52 API仕様書の変更対応が完了(OpenAPIの修正) この後、数日で実装とQAを行い、対応完了 このあたりは、細かい技術的な話になるのですが、実装の初期段階で、その機能について「将来、このようになるよね」と話した上でOpenAPIでAPI仕様書を作成し、バックエンド側の内部実装で疎結合なクラス設計にしており、意外なことに「こうなるタイミングが早くやってきた」ため、結果として、迅速な対応が可能になったわけです。ここでも、あるべき姿から逆算して仕様を策定したことや、ドメインに対応したクラス設計が功を奏しており、ドメイン知識が生きた形となりました。 とはいえ、リリース直前の要件変更なので、冷静に今思えば、とても綱渡りだったと思います。 その後、2023年3月頃に無事にリリースを行い、初めてコンビニでPay IDアプリを使って、BNPLの代金を支払えた時は、嬉しかったのをよく覚えています。リリース直後、BNPLの購入トラフィックをプロダクトチームのみんなで集まって監視した時は、達成感がありましたし、その日のことは走馬灯のように記憶が蘇ってきます。 幸いにも、現在に至るまで、BNPLへは大量のアクセスがある中で、重大な障害なく安定稼働しています。もちろん、幸運にもうまくいったのは、ドメイン知識以外にも、さまざまな要因が重なっていますし、何よりもプロジェクトに携ったチームの力だと思っています。 0.1%の改善を積み重ねる BNPLを世の中に送り出すことができ、なんとかスタート地点に立った感があります。とはいえ、本番はこれからで、追加機能をどんどん出していき、ABテストで試行錯誤を繰り返し、安定稼働を続け、最後は、購入者様やショップオーナー様に喜ばれてこそ、事業が成り立ちます。 そのためにも、ドメイン知識を活かして、チームの生産性を高めていくことが、課せられた仕事だと思っています。いまは、重要なKPIを24時間ごとにSlackへBotで流し、0.1%単位の変化を見逃さないようにして、BNPLに関わるチーム全員で仮説検証を行っています。実装面ではスピーディーに改善を行うべく、多頻度でデプロイを繰り返しています。 そして、何よりもPay IDチームとしては、BASE社全体における粗利率の改善に貢献したいですし、個人的には「投下資本利益率(ROIC)※」を意識して、末長くBASEを応援してくれる方々の期待に応えたいと思っています。 ※ ROIC : 個人的に好きな経営指標にすぎず、BASEが掲げる経営目標とは一切関係ありません 最後にいつもの宣伝です。まだまだ、やりたいことに対して、プロダクトチームの人手が足りない状態です。良いプロダクトを一緒に作っていけたら嬉しいです! open.talentio.com
アバター
はじめに どうも。BASEで開発をお手伝いしている 林( @intele ) です! 今回はBASEという大きなプロダクトでドメインを移行した話を、エンジニア目線で、振り返りながらお話したいと思います。 何をやったか BASEで使用しているドメインを thebase.in から thebase.com へ移行を行いました。 ただ、BASEではサブドメインを多用しているため、移行するドメインは thebase.in だけではなく、複数のサブドメインを含め、移行しました。 なぜ移行する必要があったか ****.thebase.in というサブドメイン運用されているショップで広告の効果計測ができなくなったため Apple が推進する App Tracking Transparency(以下ATT) の影響を受けて、Facebook広告の計測手法が変更され、効果計測ができなくなってしまったため ATT で効果計測ができる条件として Public Suffix List(以下PSL) に thebase.in 登録することが必須条件となる PSL に登録したドメインは、Cookie を持つことができなくなるため Cookie を持つことができないと、ログイン状態などを保持できなくなるため この5つの理由から、ドメインを移行することになりました。 移行することで、PSL に登録すること・Cookie を持つことができるようになるということを目指しました。 ※ATT と PSL の説明については省略します。 最初の見積 最初の見積は概算で期間6ヶ月程度とかなり甘く見積もっていました。 というのも 影響範囲が大きすぎて、短期間での詳細な見積は難しいだろう レビューを必ず行っているので、品質は一定レベルは担保されているだろう ユニットテストが充実しているので、修正を加えても動作の確認がしやすいだろう 設定ファイルにドメインが記載されているため、コードと深く結びついていないだろう という理由から、6ヶ月程度と見積もっていました。 この予測が甘かったと知るのは、実際の作業が始まってからのことでした… 問題の発覚 開発が進めば進むほど、問題が発覚していきました。 ドメインがコードに直接記載されていたり、ロジックに深く結びついている箇所が多数存在する ユニットテストが足りない、そもそも存在しない 特定の環境でしか動作しないロジックが存在する コードは存在するが動作はしてないデットコードが存在する など、開発が進むにつれて、見積が甘かったことが明らかになっていきました。 プロジェクトの仕切り直し このままでは、開発もリリースが難しいと判断し、開発チーム内で協議した結果、プロジェクトの仕切り直しをする事となりました。 仕切り直し後は、以下のような方針で開発を進めました。 01. 根幹改修 ドメインを切り替えるために必要な改修の根幹となる改修を行います。 具体的にはURLやドメインを扱ったりするClassの整備やアプリケーション全体に影響するRouting周りの改修を行います。 02. 部分改修 各ページやApps単位で改修を行い都度リリースを行います。 この段階ではドメイン変更は行わず、根幹修正で行った、URLやドメインを扱うClassを使用するようにアプリケーション全体を改修しました。 また、動作検証が難しい部分は、ユニットテストを追加することで、動作を担保し、それでも難しい場合は後続のQAで動作を担保するようにしました。 03. 全リリース 部分改修でリリースが行えなかった箇所をまとめて、アプリケーション全体のQAを行います。 QA完了後にリリースを行い、アプリケーションの振る舞いは変わらないが、ドメインの変更が容易に行える状態となります。 他にも、インフラ側で行う必要があるリダイレクト整理や設定を行います。 04. ドメイン切替 ドメインが容易に切り替えられる状態となったので、ドメインを切り替えた状態でQAを行います。 QA完了後にリリースを行い、ドメインの切り替えが完了となります。 リリース リリースは、深夜メンテナンスにてサービスを止めて行いました。 その当日も問題が発生し、リリースが出来ないかもしれない危機に直面しましたが、 なんとか問題を解決し、リリースまでこぎつけました。 また、大規模な変更を行ったため、大きな問題が起こる可能性も危惧し、万全の体制をとっていましたが、結果としては小さな不具合が10件程度で収まり、ユーザーの皆様に不便をかけることなく終えることが出来ました。 感想 これまでお話した通りリリースに至るまでの過程では色々と問題がありましたが、 プロジェクトのキックオフから、リリースまでの期間は約1年となり若干短い期間となりました。 短い期間で行えたのは常日頃からレビューやテストを徹底しているBASEの開発チームの努力の賜物だと思っています。
アバター
Tech Dept. 基盤グループエンジニアの @tenkoma です。 BASEには50以上のPHPプロジェクトのプライベートリポジトリがあります。 (アプリケーションは十数個で、残りの多くが、アプリケーションが依存するライブラリです) 過去4年ほどの間に新規に作られたリポジトリにはほぼ最初からPHPStanが導入されていますが、それ以前から開発していたリポジトリには導入されていないものが多数ありました。 それらのリポジトリにPHPStanを導入していったので、なぜ導入したか、導入方法、得られた効果について紹介します。 PHPStanとは PHPコードを実行せずに、実行時にエラーになりうる箇所を検出するツールです。PHPStanを利用しCIに組み込むと、テスト実行せずに検出できるバグの一部は、PHPStan解析で指摘してくれるので、コードレビューの負担が減ることが期待できます。 なぜPHPStanを既存のプロジェクトに導入したか 直接的なきっかけは、PHP 7.3で実行していたアプリケーションをPHP 8.0対応作業中、切り替え前の最終的な検証中にFatal Errorが発生したことです。 以下のようなコードでした。 <?php class ToaruService { public function __construct () { /* ... */ } public function make () : self { return new self ( /* ... */ ) ; } } class ToaruApiController { public $ toaruService ; public function endpoint () { $ toaruService = $ this -> toaruService ?? ToaruService :: make () ; $ toaruService -> apply ( /* ... */ ) ; // } } ToaruService クラスの make() はインスタンスメソッドですが、 ToaruApiController からは static 呼び出ししています。 実行すると以下のようなエラーが発生します。 PHP Fatal error: Uncaught Error: Non-static method ToaruService::make() cannot be called statically in /path/to/ToaruApiController.php:34 これは、PHP 7.0で非推奨になりPHP 8.0で削除された機能、「非staticメソッドのstatic呼び出し」で発生しています。 非 static メソッドに対する static 呼び出し - PHP 7.0.x で推奨されなくなる機能 - PHP 5.6.x から PHP 7.0.x への移行 その他の下位互換性のない変更 - 下位互換性のない変更点 - PHP 7.4.x から PHP 8.0.x への移行 ToaruService クラス、 ToaruApiController::endpoint() ともにテストコードはありましたが、テストコードではPHPUnitのモック機能によりテストダブルに置き換えられていました。 また、Deprecated Errorを抑制していたため、将来的に使えなくなる機能を見過ごしてしまっていました。 PHP バージョンアップ手順は大きく分けて (1) CircleCI でPHP 8.0でテスト実行や文法チェックを実行させる (2) 検証環境で PHP 8.0で動かして、E2Eのリグレッションテストを行う (3) ステージング環境・実運用環境を PHP 8.0に切り替える という手順で行いましたが、(2) の、ある程度終えたと思っていたタイミングで、基本的なエラーが検出できなかったため、社内のリポジトリで導入実績のあったPHPStanを導入することにしました。 今までPHPStanを使っていなかったプロジェクトへの導入方法 今回は以下の手順で導入しました。 なぜ導入するか、PHPStanとは何か、をドキュメントに書いて社内に共有する プロジェクトリポジトリに composer コマンドで、PHPStanを追加する PHPプロジェクトに合わせた設定ファイルを追加する level=0でベースラインファイルを生成する CIでも実行されるよう、設定する level=0でも無視できないエラーがある場合コードを修正する、もしくは問題ない場合は、解析対象から除外する これまでの変更をメインのブランチにマージする Slackに相談用チャンネルを用意し、PHPStanで相談できるようにする 今回重視したのは、プロダクションコードを網羅的に検査してPHPバージョンアップ作業の安全性を高めることでした。 そのため、level=0で導入し、既存のエラーはベースラインで一旦無視するようにしました。また相談用Slackチャンネルで導入直後のトラブルを素早く解決できる環境を用意しました。 なぜ導入するか、PHPStanとは何か、をドキュメントに書いて社内に共有した PHPStan導入は事前に計画していなかったため、当然社内への情報共有が0でした。 作業を始める前に、導入目的の言語化や、情報共有も兼ねて社内Wikiにドキュメントを追加して、なぜやっているのかわかってもらえるようにしました。 プロジェクトリポジトリに composer コマンドで、PHPStanを追加する Getting Started | PHPStan を見ながら、プロジェクトにPHPStanを追加しました。 $ composer require --dev phpstan/phpstan PHPStanはComposerパッケージとして配布されていて、いくつか別のComposerパッケージに依存しています。しかし、それらの依存パッケージは、 phpstan.phar ファイルにまとめられているので、 composer require --dev で追加したときに問題が起きにくくなっているようです。 参考: PHPStanをどうやってインストールするか この後、 First run のように、まず解析を実行してみて、PHPStanインストールが成功しているか確認しました。 $ Vendor/bin/phpstan analyse Controller --memory-limit=1536M # ...(中略)... [ERROR] Found 4078 errors なかなかの数のエラーが出ました。これらが全てアプリケーションの潜在的問題とは限りません。解析対象を /path/to/project/Controller のみで実行しましたので。 今回導入したプロジェクトには、他にも社内で開発したコードを含むディレクトリがあり、それらをPHPStanで解析してもらうために設定ファイルを書きました。 また、 --memory-limit=1536M で、使用メモリの上限を指定しています。プロジェクト全体(テストコードは除く)を解析すると、1GBでもメモリ確保に失敗するため、1.5GBを指定する必要があありました。 # memory_limit=128M で実行した時のエラー Child process error (exit code 255): PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in ... PHPプロジェクトに合わせて設定ファイルを追加する プロジェクトのルートディレクトリに設定ファイル phpstan.neon.dist を用意します。 今回導入したリポジトリでは以下のような設定になりました。 # phpstan.neon.dist parameters: level: 0 phpVersion: 80000 parallel: processTimeout: 1200.0 paths: - Config - Controller - Model - Vendor/foo - Vendor/bar - View - webroot # - Test # プロダクション・コードの非互換コード検出を優先して、テストコードは解析除外 scanDirectories: - Vendor 設定ファイルの要点をまとめると paths には、解析対象(PHPStanに問題を指摘してほしいPHPコード)のあるディレクトリを列挙します Vendor ディレクトリにも、リポジトリで管理しているファイルがある場合は、ここで明示します PHPアップグレード作業の安定性を高める目的で導入するため、まずはテストコードを除外しているので、コメントアウトで意図を明示しています scanDirectories には、 Vendor を指定しました。Composer でインストールしていて、自分たちで開発していないコードは scanDirectories や scanFiles に指定します。 となります。設定ファイルを書くことで、PHPStanがプロジェクトの構成を理解しますので、設定ファイルを少し付け足しては Vendor/bin/phpstan analyse --memory-limit=1536M を実行し、その結果を見て、 paths や scanDirectories を調整して、リポジトリに合った設定にして行きました。 ベースラインファイルを生成する 設定ファイルもある程度形ができたら解析を実行してみます。 $ Vendor/bin/phpstan analyse --memory-limit=1536M ------ ----------------------------------------------------------------------------- Line SomeApi/ApiClient.php ------ ----------------------------------------------------------------------------- 15 Access to an undefined property BaseInc\\WebApp\\ApiClient::$client. ------ ----------------------------------------------------------------------------- ------ ------------------------------------------------------------------------------------------------------------------ Line Services/SomeService.php ------ ------------------------------------------------------------------------------------------------------------------ 128 Access to an undefined property BaseInc\\WebApp\\Servises\\SomeService::$User. ------ ------------------------------------------------------------------------------------------------------------------ ... (以下、とてもたくさん省略) ... [ERROR] Found 2596 errors なかなかの数、エラーが出ました。中身をざっとみてみると、動的プロパティ(未定義のプロパティ)や、 @property アノテーションの記述ミスが大半でした。しかし、それ以外の指摘も多くあるようです。有用な指摘かもしれませんが、全て解消するとなると骨の折れる仕事になりそうです。 エラーがたくさん指摘されていてもアプリケーションが実運用できているなら、指摘されるエラーの多くは不具合につながらない指摘と推定できます。そのようなエラーを一括で除外してPHPStanを導入しやすくしてくれる機能が ベースライン です。今回は静的解析の導入を優先するため、ベースラインで既存のエラーの多くを一旦無視するようにしました。 ベースラインファイルを生成するには、 --generate-baseline オプションを付けて解析実行します。 $ Vendor/bin/phpstan analyse --memory-limit=1536M --generate-baseline phpstan-baseline.php Note: Using configuration file /path/to/project/phpstan.neon.dist. 4182/4182 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% [WARNING] Baseline generated with 2381 errors. Some errors could not be put into baseline. Re-run PHPStan and fix them. 不穏な警告が表示されましたが、 phpstan-baseline.php が生成されました。 生成されたファイルは、以下のようになっています。 <?php declare ( strict_types = 1 ) ; $ ignoreErrors = [] ; $ ignoreErrors [] = [ 'message' => '#^Unsafe usage of new static \\\\ ( \\\\ ) \\\\ .$#' , 'count' => 1 , 'path' => __DIR__ . '/SomeApi/Request.php' , ] ; // ... (以下、とてもたくさん省略) ... 'path' に 'message' のエラーが 'count' 個ある、と読めます。 'path' が指定されているので、このPHPファイル以外で同じエラーは無視されませんし 'count' があるので、 '/SomeApi/Request.php' で同じエラーが増えた場合は、エラーが出るようになります。 設定ファイルで、生成したベースラインファイルを読み込むようにして、解析を再実行します。 # phpstan.neon.dist に以下の行を追加する includes: - phpstan-baseline.php % Vendor/bin/phpstan analyse --memory-limit=1536M Note: Using configuration file /Volumes/src/github.com/baseinc/web/phpstan.neon.dist. 4182/4182 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100% ------ --------------------------------------------------------------------- Line BaseInc/App/Services/SomeService.php ------ --------------------------------------------------------------------- 7 Class SomeService extends unknown class AbstractService. 💡 Learn more at <https://phpstan.org/user-guide/discovering-symbols> ------ --------------------------------------------------------------------- ... (以下、たくさん省略) ... [ERROR] Found 212 errors エラーが1/10以下に減りましたが、まだまだエラーが出ています。これらのエラーはベースラインでは無視できないようです。しかしベースラインは生成できたので、 phpstan-baseline.php , phpstan.neon.dist をコミットします。 level=0でも無視できないエラーがある場合コードを修正する、もしくは問題ない場合は、解析対象から除外する 200件あまりのエラーは、ベースラインで無視できないものでした。 これらのエラーに対して取れる選択肢は、以下の2つです。 エラーを修正する 解析対象から除外する エラー出力をながめてみると、その多くが Class SomeService extends unknown class AbstractService. というエラーでした。 以下のように定義している AbstractService を use を使わずに読み込んだ場合にエラーになりました。 <?php namespace BaseInc\\Services; class_alias ( AbstractService :: class , 'AbstractService' ) ; abstract class AbstractService { // 略 } これらは、PhpStormで簡単に修正でき、修正結果を他ならぬPHPStanで保証できるので、修正することにしました。 class_alias(...) も削除できました。 このエラーを修正した結果、10個程度に減りました。 残りのエラーについて、簡単なものは修正しましたが、難しいものは、除外することにしました。 excludePaths: analyse: # ベースラインで無視できず、修正困難なため解析対象外にする - Too/Dificalt/Fix/Error/Module.php CIでも実行されるよう、設定する CIでPHPStanを実行すると、テストコードを同時にコミットしていなくても、変更したプロダクションコードの問題を指摘してくれて大変便利です。 また、継続的に実行されないと、コード変更でエラーが増えてしまうので、CIで実行し、エラーは修正してもらうようお願いしています。 CircleCIでのjob設定は以下のようになります。 phpstan : docker : - image : cimg/php:8.0 resource_class : xlarge steps : - checkout - run : name : composer install command : | composer config --global github-oauth.github.com ${GITHUB_ACCESS_TOKEN} composer install --prefer-dist --no-interaction --no-scripts - run : name : create reports directory command : mkdir ~/reports/ - run : name : Run phpstan analyse command : Vendor/bin/phpstan analyse --no-interaction --error-format=junit --memory-limit=1536M > ~/reports/phpstan-junit.xml - store_test_results : path : ~/reports/ - store_artifacts : path : ~/reports/ エラー情報を JUnit フォーマットで出力し、 store_test_results でテスト結果として認識されるよう設定すると、PHPUnitテスト同様に、エラーが一覧できます。 PHPStan解析エラーがCircleCI結果に表示される PHPStanを実行するインスタンスのスペックを指定する resource_class: xlarge としています。 CircleCIは、 resource_class で、CPUコア数やメモリを増減できます。 PHPStanは、解析に使う論理CPUコア数が増えると解析時間が短くなるので、 resource_class を増やした時の効果が高いです。また使える論理CPUコア数が増えれば、1プロセスあたりに使うメモリ量は減ります。 PHPStan を実行してみて、メモリ不足で失敗せず、解析時間が長くならないような resource_class として、このリポジトリでは xlarge にしました。 他のリポジトリでは、 small で十分なリポジトリもあります。 これまでの変更をメインのブランチにマージする CIでPHPStan解析ができるようになりましたので、メインのブランチにマージします。 Slackに相談用チャンネルを用意し、PHPStanで相談できるようにする メインのブランチにマージ後すぐは、指摘されるエラーへの対応で困ることが発生するかもしれない、と思い、Slackに相談窓口としてチャンネル #help-dev-phpstan を作りました。 「新しくPHPStanでエラーになった場合の対応」について相談を想定してましたが、ほぼありませんでした。かわりに スローする例外の名前のtypoが見つかった、という情報共有 phpstan-baseline.php をコマンドで更新するには CI のログには、エラー内容が表示されなくて困っている TESTSタブに表示されるので、そちらを見てもらうよう案内した(CircleCIで、JUnit形式のレポートを指定するとわかりやすく表示してくれる機能) といった相談がありました。 得られた効果・まとめ PHPバージョンアップがしやすくなった 前述のような後方互換性のない振る舞い変更を、一部検知してくれるようになり「非staticメソッドのstatic呼び出し」も指摘されるので、まとめて修正することができました。 テストで実行されないコードの問題が見つけられるようになった 既存コードに存在した問題がいくつか見つかりました。 例えば、異常系処理でスローされる例外クラスが見つからない(存在しなかったり、クラス名にtypoがあったり)という指摘が数件ありました(実運用環境でスローされたことがなかったためか見過ごされていた) 導入前の懸念についてPHPStan導入後に思ったこと 以下のような懸念を抱いてました。 namespace のないPHPコードでは解析がうまくできないのではないか 多数のエラーを指摘され、導入までに長い時間がかかってしまうのではないか 実際作業してみると、namespaceのないコードも解析できますし、ベースラインを使うことで既存のエラーを除外しつつ新規の潜在的問題を指摘する能力を素早く導入できました。 参考資料 Find Bugs Without Writing Tests | PHPStan Getting Started | PHPStan PHPStanクイックガイド2023 (PHPerKaigi 2023 パンフレット p78〜) レガシーコードにPHPStanを導入するためのTIPS - RAKUS Developers Blog | ラクス エンジニアブログ PHPStanをどうやってインストールするか リリースして11年経過したPHPアプリケーションにPHPStanを導入した - Chatwork Creator's Note
アバター
10点満点でつけてもらったアンケートの回答です。 ごあいさつ はじめましての人ははじめまして、こんにちは!フロントエンドエンジニアのがっちゃん( @gatchan0807 )です テックブログに出てくるのはお久しぶりです 今回の記事では、4月末に社内で実施したイベント 「あの頃のオフライン勉強会の感覚を取り戻そう! オフラインリハビリ勉強会」 という取り組みをご紹介します! どんなイベント? BASE には #iikanji-conference-toudan というワーキンググループがあり、外部カンファレンスやイベントでの登壇の支援をしていく活動をしています (詳しくは2022年のアドベントカレンダーの記事で振り返りを行っているのでそちらをご覧ください!) devblog.thebase.in 今回の「あの頃のオフライン勉強会の感覚を取り戻そう! オフラインリハビリ勉強会」(以下、オフラインリハビリ勉強会)は、その #iikanji-conference-toudan 主催で実施したイベントで、 オフラインでゆるく社内に集まり、LTと寿司やお酒を囲んで技術の話をする感覚を取り戻そう! という趣旨で開催しました 結果的に オフライン会場に20人近く集まっていただき 、あらかじめお声がけしていた3本のLTと1本の自己紹介LT、さらには飛び込みで1本のLT( MPC について熱く語っていただきました)までお話いただくことができました! 🙌 🙌 🙌 なんで実施したの? 「もっと社内から登壇やイベントに参加してくれる人を増やすにはどのようなことをすればいいんだろうか…?」と私たちの中で分析している中で、以下のような仮説が立ちました コロナ禍もある程度落ち着いてきて、オフライン勉強会も増えてきてるが、 「前は色んなコミュニティのイベント行ってたけど、コロナ禍になってからあんま行けてなくて久々に行きたいな…でも久々すぎて喋れるかな…」 みたいな不安を持っている方が一定数いるんじゃないか…? この仮説から、 「いきなり社外のイベントに参加する前に、一旦社内でオフライン環境に慣れるためのリハビリできる環境作ってみない?」 という話になり、この会を開催することにした形です また、よくメンバーで話しているなかで出る課題感として「登壇して話す・話したいと思えるネタがない問題」というものがあります これについてメンバーで雑談をしている中で、過去の経験からふと「イベントで聞いた話・懇親会で人と話したことが話すネタになるきっかけになることもあったよね」ということに気づき、そういった技術雑談が発生する「カンファレンスの廊下」を何らかの形で社内に擬似的に作ることができれば登壇にチャレンジするサポートもできるのではないか?と思い、実施にいたりました 工夫・気をつけたところ 上記のような開催の目的を実現するために、いくつか工夫した部分があったので紹介します ほとんどが当たり前のようなことではあるのですが、細かいところにこだわりを持って会のクオリティを上げるための活動をしていました 参加のハードルをできるかぎり下げる 「リハビリ」を謳っている以上、少しでも参加することにハードルを感じてしまっては実施する意味がなくなってしまうので、ここをどのようにハードルを下げられるかを考えました 参加表明なしでも出社していたらフラっと来てもらってOK!というのを何度もアナウンス時に伝えておく 参加表明もSlack Reacjiの 🙋 や、古の「丿」コメントをするだけでOK!という形を取りました 開催日を金曜日にしない(この日はちょうどGWの前日というのもあって、余計に避けていました) たくさんの人に参加してもらうには、他の予定とバッティングしがちな金曜日を外すのが意外と大事だったりします 翌日まだ仕事があるというところで、自制しやすく実りある話がしやすい会になると良いな〜という目論見も実はひとつまみ入っていました LTの資料はつくらないことを推奨! 資料作りの時間がないから…というところで二の足を踏んで欲しくなかったので、資料なし登壇を推奨してアナウンスしていました あらかじめ声がけをしていたメンバー3名にも資料を作らないで!(一度話した内容の再演で資料があるのとかは大歓迎!)というお願いまでしちゃっていました 資料なしのLTって慣れていないとすごく難しいので、これに対応してくださった皆さんに感謝です… 自分も資料なしでLTをやってたんですが、ダラダラ話してしまって結果的に主催者が倍ぐらい時間オーバーするという事態になってしまいました(後続の登壇ハードルをできる限り下げたと思うことにしてギリギリ心を保ちました) あらかじめタイムテーブルで飛び込みLT歓迎タイムを用意する 乾杯 → 準備してもらったメンバーのLTパート → 飛び込み歓迎タイム 兼 歓談タイムの流れで実施し、最初のLTパートで話しきれなかった内容やLTから刺激をうけて話したいことができた方が話せるように枠を用意しました 結果的に、前述の通り1人がMPCに付いてアツく語ってくださりました🔥 何故かたまたま持っていたMPCを実際に叩いてみている時の写真 イベントの目的をあらかじめオープンにする 一般的な会議でも言われることですが「会のアジェンダと目的をまず明確にしよう」ということに注意してイベントの告知などをしていました イベントの目的をできる限り社内のメンバーに理解してもらうために、社内ドキュメントプラットフォームに実施意図や経緯をかんたんにまとめた記事を書いて、アナウンス時に公開していました この記事の「どんなイベント?」と「なんで実施したの?」の部分はその社内向け記事に書いていたものを少しだけいじって、内容はほぼそのまま公開しています また、イベントの目的に共感してもらうことも重要と考えて、イベントの名付けと開催に至るまでのストーリーをきちんと伝えられるような書き口の工夫などもしていました 感想などなど オフラインイベントの設営が久しぶりすぎて開始15分ほど「プロジェクターが繋がらない!なんで!」とバタバタ⁠してしまったり、LTの時間配分ミスってダラダラ話してしまったりと個人的なものも含めていくつも反省点はありますが、結果的に実施後のアンケートで高い満足度を得ることが出来てとても嬉しかったです! 嬉しすぎて記事の1行目にドカンと置いてしまってたこちら 他にも、この先の登壇やオフラインイベントの参加につながるようなことが出来ていたのかを確認する質問項目も置いており、結果としては以下のような形になりました おわりに 以上、4月末に実施した 「あの頃のオフライン勉強会の感覚を取り戻そう! オフラインリハビリ勉強会」 という取り組みについてのご紹介でした 今回紹介した内容をもとに、皆さんの所属する企業や団体でもオフラインイベントに体を慣らすための社内イベントなどを実施してみてもらえると嬉しいです! そしてその取り組みをまた記事にしてアウトプットとかしてもらえるとなお嬉しいです! これからも #iikanji-conference-toudan では引き続き様々な社内イベントを実施して、BASE社内からもっとたくさんの登壇者・登壇実績を積んだ方が出てくるようにサポートしていければなと思っています 最後までお読みいただきありがとうございました!
アバター
はじめに みなさん、こんにちは。BASEでエンジニアをしております田村( taiyou )です。 2023年4月6日に、ChatGPTを活用してショップオーナー向けに文章の作成をサポートする機能「 BASE AI アシスタント 」をリリースしました。そのため、この記事では、BASE AI アシスタントのシステム構成について紹介します。 今回は、システム設計についてのテックブログなのでChatGPTのプロンプトについての言及はしません。(要望があれば次のテックブログに執筆いたします!) また、前回のテックブログで「 IdPとしてSAML認証機能を自前実装した 」を執筆したのでこちらも是非見てみてください! BASE AI アシスタントとは BASE AI アシスタントとは、 ショップオーナーがよりクリエイティブな作業に時間に費やせるように機械学習・深層学習などのAIを用いて、ショップオーナーをアシストするサービス のことです。 2023年6月現在では商品登録/商品編集画面にある「商品説明文の例を書き出す」機能を指します。今後、他の機能も公開予定です。 BASE AI アシスタントとは システムの構成 アーキテクチャ 2023年6月現在では、BASE AI アシスタントのアーキテクチャは以下のようになっています。Vue.jsとCakePHPから構成される「商品登録/商品編集」システムからDSチームが管理するDS APIへリクエストし、ChatGPTに送信しています。 DSチームとは、Data Strategyチームのことで機械学習やデータ基盤などの専門知識を持つチームです。 システムの構成 「商品登録/商品編集」システムから直接ChatGPT APIへリクエストを送信しなかった理由は以下の通りです。 BASE AI アシスタントは機械学習・統計分析などの専門知識が必要になるため、領域的にDS APIを管理しているDSチームに責務を持たせたほうが組織的に適切である。 採用している言語がチーム間で異なり(PHP vs Python etc...)、また分割することでより適切な技術選定が可能になり、将来的な開発速度が向上する。 将来、BASE独自の文章生成モデルが誕生しても「商品登録/商品編集」システム側の対応が不要である。 上記の理由から「商品登録/商品編集」システムから直接ChatGPTにリクエストせずに、「商品登録/商品編集」システムとDS APIで領域を分割しました。 処理の流れ アプリケーションの処理の流れは以下の図の通りです。 Frontend : 生成リクエスト フォームがsubmitされたら、Backend(API)に商品情報を送信する Backend : ショップ情報を取得 商品説明文を生成するために必要なショップの情報(カテゴリ/...)をデータベースから取得する Backend : 商品説明文の生成リクエスト DS API : ChatGPTに送信 DS API : 生成された文字列を返却 処理の流れ 2023年4月リリース当初では、フォームを押下してから実際に商品説明文がショップオーナーに表示されるまでに20~30秒ほどかかりました。 ビッグシルエットTシャツの例 リリース後 上記の処理で正常に動作していたもののある日からうまく文章が生成されない事象が度々発生するようになりました。 原因は、 ChatGPTからのレスポンス時間が増加して、DS APIとBackend(API)の通信がタイムアウトした ためです。DS APIは AWS のAPI GatewayとLambdaで構成されたシステムなのですが、API Gatewayはタイムアウトの上限を30秒までしか設定できません。 原因は不明ですが、ChatGPT側のレスポンスタイムが30秒を超過したことでAPI Gatewayの接続がタイムアウトになり、結果としてBackend(API)でエラーになったのです。 改修 ChatGPTによる文章生成時間が30秒を超過した場合でも文章が表示されるようにで次のように処理を改修しました。 Frontend : 生成リクエスト フォームがsubmitされたら、Backend(API)に商品情報を送信する Backend : ショップ情報を取得 商品説明文を生成するために必要なショップの情報(カテゴリ/...)をデータベースから取得する Backend : 商品説明文の生成リクエスト DS API : 生成リクエストIDを発行し、文章を生成し始める Backend : 生成リクエストIDを返却 Frontend -> Backend : 生成リクエストID指定で取得リクエストを送信 文章が生成されていない場合は 生成中 ステータスが返却される 一定時間間隔で取得リクエストを送信しつづけるする 文章が生成されたら、商品説明文を返却する こうすることで仮に文章生成時間が30秒を超過してもエラーが発生しなくなりました。 今後の課題 上記の改修で仮に文章生成時間が30秒を超過してもエラーが発生しなくなり、正常応答率が更に向上しました。しかし、これはあくまで暫定的な対応であるため、ショップオーナーにより早く文章を生成し、表示する改修を行う必要があります。(こちらの対応はChatGPT側へ問い合わせ中です。) おわりに この記事では、ChatGPTを活用した「BASE AI アシスタント」のシステム構成について紹介しました。要望があれば次回以降にBASE AI アシスタントを実現するプロンプトエンジニアリングについての記事を書こうかと思います!
アバター