TECH PLAY

BASE株式会社

BASE株式会社 の技術ブログ

576

本記事は BASEアドベントカレンダー2023 の21日目の記事です。 はじめに こんにちは、BASE BANK Division(以降BASE BANKと略記)にて振込申請周りの開発を担当している umi です。 全く畑違いのところから行き着いた前職のWeb制作会社から転職し、BASE BANKにてWeb開発を行うようになり1年とちょっと経ちました。Web開発エンジニア歴は浅いですが、エンジニアという領域に閉じず、他職種と積極的にコラボレーションしながら、プロダクトの開発を行なっています。 突然コラボレーションと表現しましたが、BASE BANK Devでは「ショップオーナーに対する直接の価値提供」「社内外様々なステークホルダーに対する価値提供」のためにコラボレーションすることを2023年の行動指針にしており、その一例として、振込申請における振込先口座名義の訂正に対する改善を取り上げようというのが今回の記事になります。 振込申請における振込先口座名義の訂正を改善 振込申請 とは、ショップの売上金を引き出すための機能です。 売上金を振込先口座に振り込む仕組み上、ショップオーナー様に口座情報を入力していただく必要がありますが、この際、ショップオーナー様が誤った口座名義を入力してしまうケースがあります。 この場合、ショップオーナー様に口座名義を訂正していただくと売上金の振込が可能となります。しかし、改善前の状況として、口座名義を訂正しても振込ができなかったという問い合わせが1週間で約10件ありました。振り込みできなかった理由は、訂正した口座名義が再び誤っていたためです。 今回の改善における問題・仮説・改善策を挙げると以下のようになります。 問題 「ショップオーナー様が口座名義の訂正時に誤入力してしまう」 口座名義の誤入力が続くと、振込までの時間が伸びてしまい、ショップオーナー様の出金が遅延してしまいます。 仮説 「ショップオーナー様のネクストアクションを促す適切な文言になっていないのではないか」 どのように訂正すれば口座名義が訂正できるのかがうまく伝わっていないのであれば、むしろショップオーナー様が誤入力することを促す状態となってしまいます。 改善策 「適切な文言によって、ショップオーナー様に次のアクションを丁寧に促す」 仮説通りであり、文言修正で改善できるのであれば、相談から実装、リリース含め1日で対応できると判断でき、コスパよく改善できます。 全体の経緯としては、一次問い合わせ対応を行うカスタマーサポートチーム(以降CSと略記)より具体的な改善の方向を含め連携をいただき、影響度と工数の観点からチームで相談し優先度を上げ対応する運びでした。 また、改善の結果として実際にどれくらい効果があったのかはDevとしてもCSとしても知っておきたい情報です。CSにお願いし「 改善前後でどれくらい問い合わせが変化したか 」を計測する依頼をこちらからはさせていただきました。 結果 改善前は問い合わせが1週間あたり約10件ありましたが、改善後は1週間あたり約2件に減少しました。つまり「1/5に減少」です。 ショップの売上金の引き出しというネットショップのコアとなる機能において、相談から実装、リリース含め1日以内で対応を行い問い合わせ件数を1/5に減らすことができたため、とてもコスパのよい改善となりました。 本件はちょっとした改善でありながらも、口座名義訂正時おけるUX向上・ショップの売上金を引き出すまでの時間短縮という点で「ショップオーナーに対する直接の価値提供」、CSに来る問い合わせ件数の減少という点で「社内外様々なステークホルダーに対する価値提供」をするというコラボレーションになりました。 チームを跨いでスピード感を持って改善できるのは、自社プロダクトを持つWeb開発会社ならでは特徴の1つではないかと思います。 おわりに 日々の業務では大変なこともありますが、それを通じてチームの垣根を越え開発する楽しさやショップオーナー様へ価値提供できることの素晴らしさを感じています。 というところで若干急ハンドルですが、上記のような私含め様々なバックグラウンドを持つ方々がいるBASE BANKでは、一緒に頑張っていく人を募集しています。少しでも気になった方は、どのような人が中にいるか知ることができるため、「 社内公募制度を使ってみました!〜Youは何しにBASE BANKへ?〜 」なども合わせて読んでみるとおもしろいかもしれません。ぜひ、カジュアル面談などお越しください! devblog.thebase.in open.talentio.com 明日は usui さんの記事です、お楽しみに!
アバター
この記事は BASE Advent Calendar 2023 の20日目の記事です。 こんにちは!BASE BANK Divisionでエンジニアをしている大津(@cocoeyes02)です。 今回は社内公募制度と、社内公募制度を使ってBASE BANK Divisionにジョインした人たちの声をお届けします! 社内公募制度とは? 社内公募制度とは、社内で求人の募集をしている部署に、社員が自ら部署への異動に応募することができる制度です。この制度は、組織の活性化や、社員の主体的なキャリア形成を目的として誕生しました。 社内公募制度は、求人の募集を終了しない限りは基本的に通年で応募できます。よくある選考フローとしては 書類選考 面接 現所属部署の直上長のリファレンスチェック 最終面接 オファー面談 と、リファレンスチェックを書く人が上長になる点を除けば、社外から求人に応募した人の選考フローと同じフローを通って、異動が決定します。 社内公募制度を使った人たちの声 実際に社内公募制度を使ってBASE BANK Divisionへ異動した人に、社内公募制度を使った背景などを語ってもらいました! 機械学習エンジニア Saito Yusuke さんの場合 BASEの機械学習エンジニアとして入社してから約6年になります。これまでBASEの様々なサービスに対して、機械学習モデルの開発や運用・データ整備・分析のサポートを行なってきました。各チームやサービスの課題を解消するためにプロジェクトに参画する機会を多く頂き、どんなアプローチで解決するべきかを模索し、提案、開発していく過程はとても満足のいく経験でした。 ただ、既に顕在化している課題を対処するだけでなく、課題発見から価値創造までを責任を持てるようになりたいという意欲が生まれていたタイミングで、社内公募に挑戦してみました。 今回はキャリアチェンジというより、新規事業部という新しい環境での挑戦でした。部署を移動してからも期待通りのタスクを担当できて満足しています。また、転職とは違い部署の雰囲気を間近で感じていたので、あまり不安要素なく決断できました。 デザイナー Hayakawa Muneaki さんの場合 自分は2014年12月にBASEに入社したので、BASE歴は結構長い方になります。 デザイナーとしてBASEプロダクトに携わってきた8年間は、常に新しいことが待ち受けていて飽きる暇もない日々だったのですが、2021年の12月、翌年からのことを考えていた時に、在籍丸8年が経ち9年目に突入するというタイミングで、思い切った新しいことにチャレンジしたいという気持ちが強くなっていました。ちょうどその時期に社内公募のBASE BANKデザイナー募集の説明会を聞き、強く興味を惹かれました。 自分としては9年目というこのタイミングで、これまでとはもう一段別の切り口でショップオーナーへの価値提供ができるBASE BANKというフィールドで新しいチャレンジをしてみたいという気持ちに火がつき、応募に至りました。 入ってみてからの感想としては、自分がBASEに入社した2014年12月当時、組織としては30名弱くらいの規模感だったのですが、今のBASE BANKのメンバー数もそれに近く、雰囲気としても当時のBASEに近いものを感じています。 少人数の新規事業部ならではの機動力の高さ、意思決定の早さはとても刺激的な環境で、デザイナーとしてもどんどん新しいチャレンジができるので日々楽しく仕事ができています。 フロントエンドエンジニア Koga Yuki さんの場合 私は2021年1月にBASEに入社し、BASE BANKには2023年11月付で異動しました。BASEではフロントエンド主体の業務を行っており、直近だとメンバーシップAppの開発に携わっていたりしました。 BASEのフロントエンドの開発領域としては、Vue.jsを使ったショップオーナーさん向けの画面を作る部分、ノーコードでショップの画面を作ることができる「デザイン編集画面」の機能拡張などなど、多岐にわたります。こういった開発はフロントエンドエンジニアとしてはチャレンジングで他では経験できないような開発を行うことが出来ており、正直、直近で転職や部署異動などを検討していたわけではありませんでした。 しかし、社内のイベントなどでBASE BANKチームの話を聞いている中で、チームにフロントエンド主軸のメンバーが足りておらず、まだまだやりたいことがやりきれていないこと、BASEのフロントエンド領域との連携に課題があることを知り、「自分の純粋な技術力アップだけではなく、自分のスキルを活かして組織のパフォーマンス向上にも寄与できる動きをやってみたい!」と思ったことから社内公募での異動を志願しました。 現在ではフロントエンドに関するタスクだけではなく、フルサイクルエンジニアを目指してGo / Python製のAPIの実装をしたりAWSの設定を触ったりもできていて、自身のいちエンジニアとしての見識が広まってきており、とても楽しく仕事ができています。 実際に異動してどんなタスクの対応をしたのか?などを書いた記事が BASE Advent Calendar 2023の9日目の記事 にもあるので、そちらもぜひご覧ください! バックエンドエンジニア Otsu Kazuki の場合 2021年2月にBASEに入社したので、入社してからもうすぐ3年が経ちます。これまではバックエンドメインのエンジニアとして、BASEで提供している様々なAppの開発(Google商品連携・広告Appや、Tiktok商品連携・広告Appなど)に携わってきました。 開発していく中で自分が目指したいエンジニア像を考えていると、ソフトウェアサイクルの全てに責任をもちたいことや色々な事業フェーズを経験してみたいという欲求があることに気がつきました。しかし、BASEというプロダクトは好きであるため転職したいわけではないとも思っており、どうしたものかと考えていたところで、社内公募制度の存在を思い出しました。 結果としては、社内公募をしていた部署の中の1つであるBASE BANKへと応募するに至りました。 BASE BANKは現在少人数の新規事業部であり、事業をグロースしていく中でフェーズがどんどん変わっていく環境に身を置けるのは得難い経験だと思ったのが大きな要因です。また、BASE BANKではソフトウェアサイクルの全てに責任を持つフルサイクルエンジニアの思想があり、今よりももっと広範囲にわたってエンジニアリングの経験を積めると思いました。フルサイクルエンジニアの話は以下の記事でも触れられていますので、併せてご覧ください。 devblog.thebase.in 社内公募制度は応募先も当然社内になるので、応募する前に普段どんなコミュニケーションが行われているのか話したり、slackやドキュメントなどを見て具体的にどんな業務をしているかなど情報収集が可能です。そのため、単に転職するよりもずっと、ジョインした後の動きが想像しやすかったです。 現在では長期PJにジョインし、設計や実装はもちろん、QA、運用の設計や実施、ステークホルダーとのコミュニケーションやチームの越境によるコラボレーションなどソフトウェアサイクルの全てにまつわる業務に触れる日常を送っています。また、主導して進めていく機会も日毎に増えており、チャレンジの連続による自身の成長も感じています。 最後に 社内公募制度とは、Be Hopefulにチャレンジができる機会を提供する、そんな制度だと個人的には思っています。誰かのチャレンジを後押しする、そんな文化でありたいですね。 BASE BANK Divisionでは社内公募に限らず、社外からもメンバーを募集しております。一緒にチャレンジしたい!という方は、ぜひ一緒にチャレンジしましょう!お待ちしております! open.talentio.com さて、明日のアドベントカレンダーは Unno Yuki さんです!お楽しみに!
アバター
はじめに この記事は BASE Advent Calendar 2023 の19日目の記事です。 こんにちは!Data Strategyチーム(以下、DSチーム)でデータエンジニア兼データアナリストの @shota.imazeki です。 今回は全社的なデータ活用を推し進めていくために、いくつかの施策を行ってみたので、それらを紹介していきます。その際に上手くいかなかった点やこうすればよかった点なども紹介していきます。「データを整備したけど使ってくれない」といった課題に直面している方々の参考になれば嬉しいです。 結果 まず最初に結果から話していきます。以下のグラフはBASEで使用しているLookerというBIツールのMAU(月次アクティブユーザー)です。昨年の同時期に比べて利用者数が1.5倍以上伸びています! Looker アクティブユーザー数の推移 「ユーザー数が増えただけでは?」という声も聞こえてきそうなので割合(MAU / その月の全Lookerユーザー数)も一緒に載せています。割合としても50%前後から65%前後にまで上がっています。またBASEの従業員数は276名(2023年9月末時点)なので半分以上の方が毎月Lookerを利用していることになります。 もちろんDSチームで行った施策以外に色々な要因があるとは思っていますが、結果として社内でのデータ活用が昨年よりも進んでいることは事実で、これに関しては素直に喜んでいいのかなと思っています。 施策 施策の方向性としては2種類あります。 全社向け チーム向け 広く浅くが前者、狭く深く進めていくのが後者かなと思っており、それぞれDSチームでやったことを紹介していきます。 全社向け 全社向けに行う施策として意識しているのは、データを見る際のハードルを可能な限り下げることです。難しい技術を扱えるようにするよりも気軽にデータを見れるような環境作り、データリテラシーの向上を目的として施策を実施しています。今回は2つ紹介します。 1. 勉強会の実施 社内のデータリテラシー向上を目的としてLookerやSQLの勉強会を実施しました。Looker勉強会は春から夏にかけて3回、SQL勉強会は秋から冬にかけて5回ほど実施しました。どちらも基礎的な内容になっています。参加人数は回によってばらつきがあるのですが20~40名ほどでBASE全体の1割~2割ほどの方が参加してくれました。 工夫した点としては以下になります。 勉強会の資料を色々な方にレビューしてもらう DSチームに限らず社内で詳しい方や初学者の方に内容を相談したり、レビューをお願いして、勉強会の中身をより良くできるように心がけてました 実データを使って、BASEのデータにも詳しくなる BASEにある実際のデータを使い、よく見られている指標を出すなどして、BASEのデータにも詳しくなれる機会にしました 講義中のクイズや勉強会後の練習問題で実際に手を動かしてもらうことで、能動的に学べるようにもしました オンライン実施且つ録画をすることで、誰でも受けられるようにする 勉強会に都合が合わなかった方、開催当時に入社していなかった方々のためにweb会議ツールの機能で録画をしました 後述するチーム向けの施策において、導入時に見ていただくこともあります。新入社員のオンボーディング時に見るようにしているチームもあるそうです 反省点としては、告知が充分にできなかったことかなと思います。全体Slackチャンネルに投稿しても皆さん忙しかったり、他の投稿に埋もれてしまって見落とすことがどうしてもあります。 実際、以下のような声を聞きました。 そもそも勉強会知らない人多そうな気がしてます・・・ 〇〇チーム内でDSチームが勉強会してますよ!!!って布教してたら、知らなくて、感動してくれる人けっこういて・・・ Slackだけだとどうしても見逃してしまうと思うので、各チームのどなたかに宣伝をお願いするのも一つの手かなと思ってます。100%周知は難しいと思うのですが、できる限りニーズのある方々には伝わるようにしていきたいです。 2. 週一の相談窓口の開設 「Looker / SQLカフェ」と題して、週に1回1時間程度、データ関連の相談をなんでも受け付ける相談窓口の開設を8月に行いました。 社内のフリースペース席に張り紙を貼って、そこで相談を聞くというスタイルです。 業務上困っていることやラフな相談、暇だから話を聞きに来たといった方々もいて、相談自体も幅広く受け付けています。実際に来た一例を列挙しておきます。 Lookerでこんな数値が見たいけど、どうすれば出せますか?? SQLのここについて分からないので教えて欲しいです 勉強会のここについて不明点があるので質問したい ダッシュボードを作る時に意識することや注意点を教えて Looker、何も知らないので教えて欲しい! その場で解決できるなら解決しますし、時間がかかるものは適切な申請先や参考になるドキュメントを紹介しています。 反省点としては、あまりないと思ってますが強いて言うなら、悩みがない人も参加できる形にしてみたいなと思っています。カフェという名前が入っているので、コーヒーを淹れて誰でも参加しやすくして、DSメンバーと交流する機会があっても面白いかなと思ってます。 チーム向け こちらは全社向けとは対照的に、特定のチームに参加してデータ活用を進めていくものです。立ち回りとしてはデータアナリストに近いです。 こちらで意識しているのは、最終的にDSチームのメンバーがいなくなってもそのチーム自身でデータを見ていけるような下地を作っていくことです。正直、ここはとても難しくて暗中模索な部分もあります。データアナリストとしてのメイン業務(分析や効果検証など)の部分は本旨と異なるので割愛して、もう一つのメイン業務であるダッシュボード構築時に意識的に取り組んでる部分について紹介していきます。 ニーズに合わせたLookerハンズオンの実施 チームのニーズとしてダッシュボード構築があった時、DSチームが構築するのではなく、チームから担当者を選出していただき、その方に教える形でハンズオンを実施しています。 DSチームが一番Lookerの扱いに慣れているので短期的に見れば、自分たちで作ってしまった方が早いのですが、長期的な目線で考えてこのような形で取り組んでいます。 チームのニーズに合わせてハンズオンを行うので勉強会以上に実践度合いが高くて、大変ではあるのですが効果は高いかなと思っています。 実際の声がこちら。 勉強会も参加したのですが、自分のPJでやりたいことベースの事例で実践を一緒にやっていただいたことで理解度がかなり上がるな~と思いました🙏 〇〇プロジェクトでダッシュボードの作り方を手取り足取り教えてもらったためか、別件でダッシュボード作る時も驚くほど簡単でした! 反省点としては、導入として全社向けで紹介した勉強会動画の視聴をお願いしているのですが、SQLの知識がある方向けのハイレベルな部分の動画がないことです。LookerではLookMLという特殊な言語を使う場面があって、ハイレベルな方にはそこの部分まで担当いただいてます。ただそこの導入については動画が用意できておらず現状DSチームメンバーが直接教える形になっており、少々効率が悪いです。早めに導入資料や動画を作成するか、参考図書を社内に用意したいと思っています。 Slackチャンネルにダッシュボードを定期配信する 要件によって差はありますが、作成したダッシュボードはSlackの各チームチャンネルなどで日次で配信するようにしています。データを見にいくハードルを可能な限り下げるために、社員皆が毎日必ず使うツールからの導線を作っています。 実際、ダッシュボードを定期的に配信することでSlack上でチームメンバーから反応があり、そこから「こういう分析もしてみよう」といった話に発展することもありました。以下に一例を列挙しておきます。 機能リリース直後に利用状況やユーザーの反応をデータから見てみる 各チームのKPIを日次で配信する ある指標が閾値を超えた場合にSlackに通知する Slackに気軽にデータを見に行ける導線を置くのは、データを見る意識も上がって効果的かなと思っています。 反省点というわけではないのですが、意識の話ではあるので効果が出ているのか分かりづらいという点です。この話だけではないのですが、データを専門的に扱う職業として自分たちの施策の効果はある程度は計測できるようにしたいなと思ってます。 最後に 上記で述べてきた施策や組織的な変化もあり、社内のデータ活用はここ一年で大きく進んでいきました。 またこのような取り組みを行う中で、自分の意識も少しずつ変化がありました。BASE入社前までは自分のスキルばかりに意識が向いてましたが、段々と自分だけではなく組織として分析基盤や分析組織がどうあるべきかに目線が移っていっています。自身初めての自社開発企業だからなのか、BASEの文化がそうさせたのかは分かりませんが、自分にとってはとても良い変化だったと思います。 今後もこのような取り組みをどんどん進めていきたいのですが全体的な反省として、その場の思いつきで動いてしまうことが多々ありました。来年はゴールや理想像をちゃんと考えた上で計画的に取り組んでいきたいと思っています。 最後となりますが、弊社ではデータエンジニアやデータアナリストを募集しております!組織的にも技術的にもまだまだ課題はたくさんあって、とてもやりがいのある仕事かなと思っております!ご興味のある方は気軽にご応募ください! https://open.talentio.com/r/1/c/binc/homes/4380 明日は @02 さんの記事です!お楽しみに!!
アバター
この記事は  BASE Advent Calendar 2023  の18日目の記事です。 Pay ID Appグループの北川です。ショッピングアプリ「Pay ID」の開発チームでエンジニアリングマネージャーを担当しています。 iOSアプリ開発で依存管理に使っている Mint のバージョンアップデートを GitHub Actions を使って自動化した話をします。 Mint とは Mint は Swift 製のライブラリのパッケージマネージャです。 https://github.com/yonaskolb/Mint 私たちのiOSアプリのプロジェクトでは、以下のようなビルドツール系のライブラリの依存管理に1年ほど利用しています(アプリ本体の依存管理には Swift Package Manager を使っています)。 SwiftLint Mockolo IBLinter SwiftFormat XcodeGen periphery LicensePlist SwiftGen もともと、プロジェクトリポジトリの中に BuildTools というディレクトリをつくり、そのなかにアプリケーション本体では利用しないツール群を Package.swift で定義しビルドしていました。 しかし、複数のライブラリを利用している場合、それぞれで利用している依存関係のバージョンが衝突することがたびたびおこり、よりシンプルな方法を検討した結果 Mint を採用することにしました。 サードパーティ製のライブラリではありますが、シンプルな定義ファイルでバージョンを固定することができ、かつキャッシュも扱いやすいことがメリットと捉えています。 候補としてビルド不要で利用できる Homebrew も上がりましたが、チーム開発をする上で各メンバのマシンや実行環境で利用するバージョンを固定したいため、私たちは選択しませんでした。 重視したポイント バージョン固定 依存管理難易度 CIでのキャッシュ管理 SPM ⭕️ ❌ ⭕️ Homebrew ❌ ⭕️ ❌ Mint ⭕️ ⭕️ ⭕️ ちなみに Mint 自体をどうやってインストールするかという課題もあるのですが、それは Homebrew を使ってインストールすることで妥協しています。 バージョンの更新を自動化したい さて、Mint では Mintfile というファイルでパッケージのバージョンを固定することができます。 yonaskolb/xcodegen@2.18.0 yonaskolb/genesis@0.4.0 これらのパッケージは定期的にアップデートをしたいとは思いつつ、 Linter などが管理したいパッケージであったのでアップデートしなくともある程度動くため後回しになりがちという課題がありました。 Renovate を使ってアップデートするPRを作れそうだとわかりましたが、弊社では現時点で導入しておらず、 別の方法を検討しました。 Mintfile 自体はシンプルなフォーマットであり、Swift Package Manager も tag を使ってバージョン管理を行う仕組みなので、GitHub Action で文字列操作と GitHub API を組み合わせることでサクッと実現できるのではと考えました。 パッケージの分だけ更新チェックをするために動的に matrix を設定する チームでは過去に CocoaPods や Swift Package Manager で管理しているライブラリの定期的な更新チェックを CI で自前で実現していたのですが、複数のライブラリのアップデートを1つの Pull Request で行うような仕組みになっていました。 このような仕組みだと、A、Bという更新可能なライブラリがあり、Aは更新が容易だがBはすぐには更新できない、というような場合にAも更新できない、という点で使いづらさを感じていました。 そこで、以下のステップのように各パッケージ毎に更新をチェックし必要なら Pull Request を作成できるようにする仕組みを目指しました。 Mintfile に定義されている依存を取り出す 1で取り出したパッケージ毎にジョブを実行する 各ジョブでパッケージの最新バージョンをチェック Using a matrix for your jobs - GitHub Docs GitHub Actions には matrix キーワードを使って、定義されたパターン分の変数を使ってジョブを実行することができます。 jobs : example_matrix : strategy : matrix : version : [ 10 , 12 , 14 ] os : [ ubuntu-latest, windows-latest ] この matrix は動的に設定することができるので、 Mintfile で定義した分のパッケージを matrix として設定するようにします。 まず、 Mintfile を1行毎に分割し配列の JSON 形式にします。 jobs : # Mintfile から依存関係を取得して、それを Matrix にするために JSON に変換して出力する prepare-mint-dependencies : runs-on : ubuntu-latest steps : - uses : actions/checkout@v4 - name : make matrix for each mint dependencies id : set-matrix run : | MINT_DEPENDENCIES=$(cat Mintfile | sed -e 's/#.*$//' | sed -e '/^$/d' | jq -R -s -c 'split("\n") | map(select(. != ""))' ) echo "dependencies=${MINT_DEPENDENCIES}" >> $GITHUB_OUTPUT outputs : dependencies : ${{ steps.set-matrix.outputs.dependencies }} 例として JSON はこのようになっているイメージです。 [ " realm/SwiftLint@0.51.0 "," IBDecodable/IBLinter@0.5.0 "," nicklockwood/SwiftFormat@0.50.3 "," yonaskolb/XcodeGen@2.25.0 "," uber/mockolo@2.0.0 "," mono0926/LicensePlist@3.23.4 "," peripheryapp/periphery@2.12.3 "," SwiftGen/SwiftGen@6.4.0 " ] 次に、出力した outputs を使って matrix を定義します。 # 各依存関係のリポジトリを参照して、最新のバージョンを取得する check-update : runs-on : ubuntu-latest needs : prepare-mint-dependencies strategy : matrix : mint-dependency : ${{fromJson(needs.prepare-mint-dependencies.outputs.dependencies)}} fail-fast : false steps : - name : print dependencies run : | echo ${{ matrix.mint-dependency }} こうすることで1回のワークフローを起点に依存しているパッケージ毎に更新をチェックすることができるようになります。 あとは、各ライブラリのリポジトリを見に行って最新バージョンがあるか確認し必要なら PR を作成すれば良いという流れになります。 ちなみに、最新バージョンを取得する場合は github-script が便利です。 # GitHub Scriptで最新のバージョンを取得する - name : find latest version from GitHub API id : get-latest-version uses : actions/github-script@v7 with : script : | const { data } = await github.repos.getLatestRelease({ owner : '${{ env.REPO_OWNER }}' , repo : '${{ env.REPO_NAME }}' , }) return data.tag_name result-encoding : string おわりに このようにして Mint で管理しているパッケージの更新チェックを GitHub Actions を使って実現することができました。 matrix を使うことである種プログラミング的にワークフローを制御できるので、自動化の幅が広がると感じています。 明日の19日目は ImazekiShota さんの記事です。お楽しみに!
アバター
この記事は  BASE Advent Calendar 2023  の18日目の記事です。 はじめに こんにちは、Pay IDアプリ開発チームでエンジニアをしている小林( @eijenson )です。 ショッピングアプリ「Pay ID」のAndroid版アプリの開発を担当しています。 本アプリでは一部機能でWebViewを使って実装しています。 そこで少し厄介だった仕様とそれをどう実装したかを紹介していこうと思います。 使用言語/ライブラリのバージョン Kotlin 1.7.10 Jetpack Compose BOM 2023.06.01 今回の画面設計 よくあるMVVMの設計です。 UIはJetpack Composeで書かれています。 WebViewに関してはJetpack Composeでは用意されていないので、AndroidViewを使ってWebViewクラスをCompose内に定義しています。 AACのViewModelを使っていて、WebViewClient.shouldOverrideUrlLoadingやdoUpdateVisitedHistory等々のタイミングでViewModelのメソッドを叩いてタイミングに応じた処理を行い、その結果をFlowのイベントとしてUIクラスに伝えて結果を反映させます。 _blankのリンクをタップしたときだけ別ブラウザを開きたい WebViewで開くリンクで、ブラウザで新しいタブを開きたい時に設定する <a href=”https://〇〇” target=”_blank”> といった_blankの設定は無視されます。 WebView でウェブアプリを作成する  |  Android Developers 無視されると、WebView上でこのリンクをタップした際にはWebView内で遷移します。 Pay IDアプリでは購入操作をWebViewで行い、ヘルプページ等へのリンクは購入画面を表示しているWebViewではなく別ブラウザで表示したいということになりました。 そのために実装したコードは以下のようなものです。 fun Screen( // target="_blank"のリンクをタップした時の画面遷移操作をActivity等で実装してListenerに設定する onClickTargetBlankLinkListener: (Uri) -> Unit , ){ //... 省略(ツールバーの設置とかテーマの設定とか) AndroidView( factory = { val webView = WebView(it) // target="_blank"のリンクをタップしたときにonCreateWindowが反応するようにする webView.settings.setSupportMultipleWindows( true ) webView.webChromeClient = createCheckoutCartWebChromeClient(onClickTargetBlankLinkListener) } ) //... 省略 } private fun createWebChromeClient( onClickTargetBlankLinkListener: (Uri) -> Unit , ): WebChromeClient { return object : WebChromeClient() { // target="_blank"のリンクをタップした際の挙動を実装 override fun onCreateWindow( view: WebView?, isDialog: Boolean , isUserGesture: Boolean , resultMsg: Message? ): Boolean { view ?: return false val webView = WebView(view.context) webView.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { val url = request?.url ?: return false // _blankのリンク等新しいタブが作られる際だけ発火する onClickTargetBlankLinkListener(url) view?.destory() // メモリリーク防止/警告が出ますので後述します return true } } val transport = resultMsg?.obj as WebView.WebViewTransport transport.webView = webView resultMsg.sendToTarget() return true } } } setSupportMultipleWindows(true) と設定してますが、これだけだと_blankのリンクは遷移せず何もしなくなってしまいます。 そこで、 WebChromeClient.onCreateWindow にて一時的に使うWebViewを作成します。 そのWebViewの shouldOverrideUrlLoading メソッドから_blankで遷移するURLを受け取り、Listenerに投げます。 そうすることでtarget=”_blank”のリンクがタップされた時だけ動く処理が描けるようになります Pay IDアプリでは、その処理の中で別ブラウザでWebページを開くようにしました。 onCreateWindow メソッド内で getHitTestResult() からURLを取得する方法もあるのですが、新しいタブを開く操作はWebページにあるJavaScriptが行うこともできて、その場合動かない可能性があったので今回の方法を取りました。 一つ解決していない問題として、この方法を使った際に_blankのリンクをタップしたタイミングで以下の警告が表示されます。 W Application attempted to call on a destroyed WebView java.lang. Throwable at org.chromium.android_webview.AwContents.s(chromium - TrichromeWebViewGoogle6432.aab - stable - 604519333 : 10 ) at WV.s8.loadingStateChanged(chromium - TrichromeWebViewGoogle6432.aab - stable - 604519333 : 4 ) at J.N.MlAm1rvf(Native Method) at org.chromium.android_webview.AwContents.Q(chromium - TrichromeWebViewGoogle6432.aab - stable - 604519333 : 56 ) at com.android.webview.chromium.WebViewChromium.b(chromium - TrichromeWebViewGoogle6432.aab - stable - 604519333 : 21 ) at com.android.webview.chromium.N.handleMessage(chromium - TrichromeWebViewGoogle6432.aab - stable - 604519333 : 44 ) at android.os.Handler.dispatchMessage(Handler.java: 106 ) at android.os.Looper.loopOnce(Looper.java: 205 ) at android.os.Looper.loop(Looper.java: 294 ) at android.app.ActivityThread.main(ActivityThread.java: 8194 ) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java: 552 ) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java: 971 ) こちらは一時的に作成したWebViewを view?.destory() で後処理をした場合に発生します。 destroy()をしないとAndroid StudioのProfilerで確認するとメモリが_blankのリンクをタップするたびに増えていって減らないため、例外が出るのは許容しています。 一部ページが全て表示されず見切れてしまう こちらはリリース後に一部のページが見切れて表示されるという不具合が発生しました。 調査の結果、Jetpack Compose上で.fillMaxSizeを使っていたのですが、そのほかにWebViewの設定でちゃんとmatch_parentを設定する必要がありました。 Box( modifier = Modifier.fillMaxSize(), ) { AndroidView( factory = { val webView = WebView(it) webView.layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) // こちらの設定を追加 //... 省略 }, update = { //... 省略 }) } Boxの部分にModifier.fillMaxSize()で画面サイズいっぱいにWebViewを表示しようとしていたのですが、確かにこれでWebviewは画面サイズまで広がります。 しかし、WebView側の設定もしないと、以下のようなcssが設定されている画面では見切れてしまいました <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> < html xmlns= "http://www.w3.org/1999/xhtml" > < head > < style type = "text/css" > body { <!-- この 3 つの記述のあるcssを使っているページだと見切れてしまう --> display : flex ; align-items : center ; height : 100 VH; } </ style > </ head > < body > < div > ここが見切れます < br > ここから見えるようになります </ div > </ body > </ html > MATCH_PARENTを設定してない場合 設定した場合 WebViewで不特定多数のWebページを表示することはあまりないと思うので、特定の画面を表示するならそれらのページは事前にチェックする必要があると痛感した出来事でした。 さいごに 今回はWebViewに関することを書いていきました。 同じような所で困った人の一助になれば嬉しいです。 明日の19日目は ImazekiShota さんの記事です、お楽しみに!
アバター
はじめに 本記事は  BASE Advent Calendar 2023  の17日目の記事です。 こんにちはPay IDでフロントエンドエンジニアをしているnojiです。普段はあと払い(Pay ID)に関するフロントエンド周りの開発をメインに行っています。 payid.jp 11月6日にあと払い(Pay ID)は口座振替機能をリリースしました。これにより、今まではあと払い(Pay ID)を利用した翌月に、コンビニに支払いに行く必要がありましたが、金融機関口座を登録することでコンビニに行かずに手数料無料で自動で引き落とされるような支払い方法を選べるようになりました。 ( https://payid.jp/atobarai より) 口座振替登録時には以下のような画面を経て、金融機関のページに遷移し、金融機関と連携をする必要があります。 自分は口座振替機能におけるアプリでの口座申込画面の開発をWebViewで行ったのですが、本記事ではそのWebViewに新規でReact + Viteを導入した話を紹介します。 なぜWebViewを利用したのか なぜ、今回iOSやAndroidのアプリで実装するのではなく、WebViewを利用したのかというと、 「金融機関」というBASEやPay ID管理外の外部のシステムと連携するにあたり、不確実性が高い 金融機関との連携で異常があった場合など、WebViewであれば緊急で修正のリリースが可能 iOS/Androidエンジニアの対応が不要で、フロントエンドエンジニアが対応したものがiOS/Androidの両方で動く などの理由がありました。 WebViewでやるにあたっての技術選定 実は以前からアプリの一部ページでは、WebViewが使われていました。しかし、CakePHP + jQuery or JavaScriptで書かれていました。 今回、WebViewで実装するにあたり、既存の仕組みでやることもできなくはなかったですが、以下の理由で違う方法を試すことにしました 今回の機能要件に入力中のtextからこちらが用意している項目の一致で絞り込みやサジェストを行う機能があったが、jQuery, JavaScriptだとやや実装が大変 保守やレビューなども含めたメンテナンス性(TypeScriptも使われていないので。。) 今後Pay IDアプリの機能拡充で追加のWebViewを作る可能性があるので違う方法を試すチャンス 断念したことにより、フロントエンドライブラリを導入して実装することにしました。今回はReactを導入することにしたのと、サーバーサイドは既存のCakePHPのサーバーを利用するようにしました。 Reactの理由 開発者本人が書き慣れている BASEのフロントエンドでReact化が進行している preact, solid, svelteなど他の軽量めなライブラリ導入も考えたが、学習コストとなにかあった際にほかメンバー対応できない可能性がある TypeScriptで書きやすい サーバーサイド現状維持の理由 実装する数ページのためにサーバーを立てるのは管理コストなども含めてオーバースペックになる ページに認証が必要で、認証をPHP側で行っているが、サーバーをたてるとそこでも認証を考慮する必要が出てくる 開発着手からリリースまでのスケジュール的な部分であまり大規模な導入を行いたくなかった また、Reactのビルドツールについては Vite を利用することにしました。 webpackよりも高速 HMRが標準で使えるので、開発しやすそう GitHubのstar数でwebpackに追いついていきそう(2023年12/13日現在 webpack =63.8k, vite =61.8k) 今回利用する対応量でビルドツールによる大きな差分が出ることはほぼなかったと思いますが、勢いのあるツールだと思ったのでやや勢いで導入しました。 構成的なところ Viteの導入とSPAの作成については割愛します。 Reactで作ったSPAとバックエンドをどのように統合したかについてを書こうと思います。 Viteではビルド時の設定をvite.config.jsに記述します。 vite.config.js const react = require('@vitejs/plugin-react'); const { defineConfig } = require('vite'); module.exports = defineConfig({ ... build: { outDir: 'path_to_manifest', // manifestファイルの出力ディレクトリ manifest: true, rollupOptions: { input: 'src/index.tsx', }, modulePreload: { polyfill: false, }, }, ... }); frontendをビルドすると以下のようなファイル郡が作成されます。manifest.jsonにはビルド済みのindex-〇〇.jsやindex-△△.cssファイルのpathが格納されています。(〇〇、△△にはランダムな文字列が入ってます。以下は一例です) ├── assets ├── index-〇〇.js └── index-△△.css └── manifest.json (outputに関しては、フロントエンドや設定の構成などによって、内容が変わることがあります) PHPの例は以下の通りでサーバー側でmanifestファイルからcssとjsのファイル名を取得し、テンプレートエンジンに渡すパラメータに入れます。 class SampleController { ... public function v1_hogehoge() { // 何かしらの処理...(認証など) $this- > set_assets(); } private function set_assets() { $filePath = 'path_to_manifest'; // manifestファイルへのpath $manifest = json_decode(file_get_contents($filePath), true); $jsUrl = $manifest['src/index.tsx']['file']; $cssUrl = $manifest['src/index.css']['file']; $this- > set([ 'js_url': $js_url, 'css_url': $css_url ]) } } テンプレートエンジンはtwigを利用しています。 ローカル環境でHMRを利用するために、サーバーから返すファイルtwigファイルは以下のようにしています。 {% if env == 'local' %} < script type = "module" > import RefreshRuntime from 'http://localhost:5173/@react-refresh' RefreshRuntime.injectIntoGlobalHook ( window ) window .$RefreshReg$ = () => {} window .$RefreshSig$ = () => ( type ) => type window .__vite_plugin_react_preamble_installed__ = true </ script > < script type = "module" src = "http://localhost:5173/@vite/client" ></ script > < script type = "module" src = "http://localhost:5173/src/index.tsx" ></ script > {% else %} < link rel = "stylesheet" href = {{ css_url }} /> < script src = {{ js_url }} defer ></ script > {% endif %} < div id = "root" ></ div > ローカルのときはローカルの開発環境のサーバーを参照し、デプロイした際にはビルド済みのassetを読み込むようにしています。 クライアント側では、以下のようにreact-routerでルーティングを行い、CSRしています。 src/index.tsx const router = createBrowserRouter([ { path: '/path_to_page', element: <PageComponent />, }, { path: '*', element: <NotFoundPageComponent />, }, ]); const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); root.render( <React.StrictMode> <RouterProvider router={router} /> </React.StrictMode> ); 導入しての所管 ページ数やファイル数が多くないので、Viteのスピード面でとても大きいメリットを享受出来たとまでは言えないですが、ローカル環境ではHMRが効いていて更新がすぐに反映するので、細かい調整などとても開発がしやすく感じました。 設定が多くなく、Vite自体の導入して実際に開発できるようになるまでがスムーズに感じました。 ファイル数が増えてくるなど、今後WebViewでやることが増えてきたときによりメリットを享受できるかもしれないなと思いました。 まとめ 今回はPay IDアプリのWebViewにReact+Viteの導入についてお話しました。 明日はeijiさんの記事です。お楽しみに! 参考 https://ja.vitejs.dev/guide/backend-integration.html https://payid.jp/atobarai
アバター
はじめに この記事は BASEアドベントカレンダー2023 の16日目の記事です。 こんにちは! Cart DevチームでBackendエンジニアをしている @endu です。 気づけば入社してからそろそろ1年が経ちます。 この1年をふりかえると、最初のオンボーディング期間を経てはひたすらに、既存コードを読んでは仕様を把握してコードを書く1年だったかなと思います。 今回は既存コードを理解するにあたっての、 ドメイン知識を素早くキャッチアップする事 に焦点をあてて、自分が心がけている事をご紹介できればと思います。 ドメイン知識とはなにか? ここで話す「ドメイン知識」についてですが、調べてみると以下のように定義されています。 ドメイン知識(英: Domain knowledge)または領域知識は、はっきり限定された、ある専門分野に特化した分野の知識であり、一般知識またはドメイン独立の知識と対比される。 ref: ドメイン知識 - Wikipedia この定義を元に「ドメイン知識を習得している状態」がどういう状態なのか?を考えてみます。 自分が所属しているCartDevチームですと、ECにおける基本的な決済の流れだったり、決済後の商品の在庫の裏側の処理について専門的知識がわかっている状態が「ドメイン知識を習得している」と定義できるかもしれません。ドメイン知識を習得していると何が良いのか?というと、機能を要件定義する際の精度だったり、他メンバーとのコミュニケーションの効率化や、実装速度の向上に関わります。 ドメイン知識を素早くキャッチアップしたいと思った背景 ドメイン知識をキャッチアップする事自体は特別の事ではありません。 ソフトウェアエンジニアでしたら誰しもが経験をする事であり必ず行う作業です。 自分がドメイン知識を素早くキャッチアップしたいと思った背景については、所属しているCart Devチームに理由があります。 Cart DevチームはBASEの決済領域に関わる問題に対して責任を持ち、運用を行うチームになっています。 具体的には購入者様が商品を購入する際の決済だったり、オーナー様が利用するカート機能周りの開発、保守運用を行ってます。 CartDevチームの紹介に関しては以前、テックブログで記事を書いたのでよければご覧ください。 チームの取り組みを社内に紹介する「月刊CartDev」を始めた話 - BASEプロダクトチームブログ この「決済を行う」というのは幅広い領域をもっており、あるチームが特定の機能開発で決済領域に変更が加えるとなった場合にはそのチームのサポートに入り一緒に開発を行うスタイルとなっています。 なので、他のチームが開発をした機能を見る機会が多く必然とドメイン知識をキャッチアップ機会が増えました。 しかし、開発にはリリース期限があるのでキャッチアップだけに長く時間をかけすぎても意味がないです。 その為、素早くキャッチアップする上で試した事を紹介します。 処理の流れをドキュメントにまとめる 一番最初のステップとしては、変更を加える機能の概要を調査してドキュメントにまとめる所から作業を行います。自分がよく行う作業としては過去のドキュメントを見るのもそうですが、既存のコードを見て、処理のおおまかな流れを自分がわかる内容で1つのドキュメントにまとめます。 実際の取り組んだ例をあげると、BASEで提供している「 抽選販売App 」の当選処理を改修する機会がありました。 最初はコードを眺める前に、過去のドキュメントを一通り目を通した上で、1つ1つ処理を追っていきます。 この時、リクエストからレスポンスを返すまでの処理を一通り書き出す所まで把握できれば良いです。 ここにどれだけ時間をかけるかについては悩みますが全部を調査するとキリがないので、ある程度区切りがついたら、細かい単位でPRを切って実際にコードに手を加えながらデータの流れをおっていきます。 デバッグツールを使ってデータの流れを追う 前述のドキュメントにまとめる作業だと、データ構造などの細かい仕様まで把握ができないので、デバッグツールを使ってデータの流れを追う作業を行います。 BASEのBackendは主にPHPで開発が行われており、エディターとしては PHPStorm を使ってコーディングしています。PHPStormには標準でデバッグツールとしてXdebugが備わっており、ブレイクポイントを貼りながらデバッグしていきます。 https://xdebug.org/ デバッグ方法としてはどのようなデータを保持するか?に注目し、ブレイクポイントを貼って1つ1つステップオーバーしながら見ています。特にDBに対して操作を行っているのであれば、実行前に用意されるデータと、実行後に書き出されるSQL、そしてどのカラムに変更が加えているかを見て、処理の解像度をあげます。 データベースに関しては Sequel Aceを使って、期待されたデータが保持されているかを見ます。 https://sequel-ace.com/ Sequel AceはGUIでデータ構造を変更できるので、特定のデータが入った場合の処理をデバッグしたい時によく使います。 以上のように、データの流れを追う際にはXdebugやSequel Aceなどのデバッグツールと、早めに仲良くなっておくと良いでしょう。 積極的にペアプロ、モブプロを活用する ここまでの「処理の流れをドキュメントにまとめる」と、「デバッグツールを使ってデータの流れを追う」まで出来た段階ですぐに実装に入れるのですが、自分はそこで実装に入る前にペアプログラミング、モブプログラミングを通じて作業内容を見てもらいます。 なぜ、そんな事を行うのか?というと複雑かつ規模が大きい機能だと細かい仕様まで気づけなく、手戻り発生する為、早い段階で有識者の方と作業するようにしています。 またペアプログラミング、モブプログラミングを活用する事で、どのような背景で実装されてきたかだったり、設計思想についてもメンバー同士の理解が深めれるので、より素早くドメイン知識がキャッチアップしやすくなります。 まとめ 「ドメイン知識を素早くキャッチアップする時に心がけている事」に関して、3つ方法を紹介しました。 ここに書かれている内容自体は当たり前の事を書いているだけなのですがこの1年で新しいPJにアサインされて、既存コードを調べる機会が多くあったので特に意識してやっている事をまとめてみました! これらの方法が参考になれば嬉しいです! 明日は @noji さんの記事です! お楽しみに!
アバター
本記事は  BASE アドベントカレンダー 2023  の15日目の記事です。 はじめに こんにちは。BASEのデータ分析チーム(Data Strategy Team)で不正対策を行ったり、機械学習を触ったりしている竹内です。 ChatGPT(GPT-3.5 Turbo)が2022年の11月に公開されてから、だいたい1年以上が経ったことになります。 そしてこの1年近くでChatGPTに匹敵する多数のLLMの公開や国産LLM作成の動き、拡散モデルを主軸とした画像生成AIの台頭など様々なムーブメントがあり、それを経て「生成AI」という単語は2023年の流行語大賞に選ばれるほど人口に膾炙する結果となりました。 生成AI、特にChatGPTをはじめとする対話用にチューニングされた大規模言語モデル(以下チャットLLMと表記します。)の実応用という面に関していうと、人の代わりに文章を作成させたり、知りたい情報を提示させたり、アイデアを提案させたりといった人間のアシスタントのように振る舞える性質を活かした使われ方が多い印象で、BASEにおいてもチャットLLMのそういった側面を活用したサービスの開発、リリースが進んでいます。 一方で少し見方を変えると、チャットLLMは任意のテキストを入力として受け付け、指定した手順に従ってタスクを実行し、出力のフォーマットもある程度コントロールできる、世界知識をもった自然言語処理ツールであるという側面も存在します。(精度はタスクの難易度やモデルの性能に依存します。) ChatGPTによるクラス分類 チャットLLMのこうした側面に着目した実応用例やツール化に関しては、もしかするともうすでにいろいろな場所で検証され、実際に運用されているのかもしれませんが、様々な事情から前者の「アシスタントAI」的な例と比較してあまり表立って取り上げられていないような印象があります。 そういった背景もあり、直近で商品テキストのカテゴリ分類という典型的なタスクを現実的なコストで実行するために、チャットLLMを利用したちょっとしたツールを作る機会があったため、それについてまとめていきたいと思います。 チャットLLMによるテキスト分類 BASEでは検索、レコメンドシステムの改善や不正決済対策、不適切なコンテンツへの対策などに活用するため一部機械学習ベースのテキスト分類モデルの作成を行っています。具体的には以前も紹介した 商品カテゴリの分類モデル などが挙げられます。 このようなテキスト分類モデルを作成するには、従来であれば人の手によってラベル付け(アノテーション)された十分なサイズの学習/テストデータセットを用意する必要がありました。しかしながら十分なサイズとラベルの品質が担保されたデータセットを作成するのは容易ではなく、クラウドソーシングなどを利用した場合でも相当な時間的、金銭的コストがかかることになります。商品カテゴリの分類という、内容自体は比較的取り組みやすいタスクについてもデータセットの作成がネックとなっていました。 一方でChatGPTのようなチャットLLMは、適切な指示文(プロンプト)を与えるだけで様々なタスクを解くことができるという非常に高い汎用性、柔軟性をもっているため、テキスト分類においてもファインチューニング無しで十分な力を発揮することができ、ドメインによっては人間と比較しても遜色ない性能を発揮することも可能です。[1] しかしながらチャットLLMをそのままテキスト分類モデルとして利用し続ける場合、ファインチューニングしたモデルで推論を行う手法と比較して 推論速度 コスト APIの利用制限(ChatGPTやBARDのようなAPIを利用する場合) 出力のフォーマットの安定性 などの面で課題が発生します。 特に推論速度の面で言えば簡単なテキスト分類においても1件あたり数秒〜十数秒は要するケースが多く、モデルの最適化もバッチ処理も基本的には不可能であるために、データの量によってはそもそも推論が追いつかないことがあります。 こうしたスケーラビリティ上の課題を解決する方法としては、チャットLLMを用いてテキスト分類を行った結果を教師信号とし、それを用いてBERTなどのより軽量な事前学習モデルをファインチューニングするという方法が考えられます。 要するに人間の代わりにチャットLLMにアノテーションさせるということになりますが、複雑で巨大なモデルの有する知識を、よりデプロイメントに適したモデルへと移転させる技術である知識蒸留(knowledge distillation)[2]を行っているという見方もできるかもしれません。 (ただしChatGPTの場合、出力をOpenAIとの競合となるようなモデルの学習目的で利用することが規約で禁止されている[3]ため注意が必要になります。) 分類タスクを実行するためのプロンプトのテンプレート化 データセットへのアノテーションを目的としてテキスト分類を実行するという工程については、ある程度ツール化した方が便利であると考えました。 テキスト分類を実行させるための指示や結果のフォーマットの指定などに必要なプロンプトをいちいち書かずに、scikit-learnやPyTorchなどと同じような振る舞いをするPythonのライブラリとして使用できればドメインごとの取り回しが良いです。 例えばニュースのトピック分類やツイートのネガポジ分類など異なるドメインのテキスト分類タスクを解くためにチャットLLMを利用する際、いちいち「クラス分類してください」的なプロンプトや「この例のようにjson形式で出力してください」的なフォーマット部分や、APIを叩く部分を書くのはなかなかに煩わしいものとなります。タスクを解く上で必要最低限となる情報や要件のみを直接引数などで設定できることが望ましいです。 クラス分類タスクを実行させるためのプロンプトに関して言えば、ドメインに関わらず共通している部分と、ドメインごとに指定する必要のある部分に分けることができ、前者をある程度テンプレート化した上で後者をそのテンプレートに当てはめることが可能です。 ドメインに関わらず共通化できる部分としては テキスト分類を実行させるための指示部分 理由や確度、マルチラベルとするかなどの有無の指定 各種設定に応じた出力のフォーマットとその例示部分 などが挙げられ、 テキスト分類においてドメインごとに指定する必要のある部分としては どのような文脈のテキストを分類するか。(例: Webサービスの利用者のコメント) どのようなラベルに分類するか。(例: 利用時の印象) それぞれのラベルはどんなものか。(例: 「ネガティブorポジティブ」) などが挙げられます。 以上を踏まえて、結果的に以下のように動作するチャットLLMによるテキスト分類ツールを作成しました。 from llmtask.tasks import ClassificationTask # クラス分類されるテキストの説明 input_description = "EC review comments" # クラスの説明 label_description = "Impressions from the comments" # ラベル名: ラベルの説明や具体例 labels = { "Positive" : "Receive a good impression(endorsement, praise, support, recommendation)" , "Negative" : "Receive a bad impression(disagree, criticize, attack, slander)" , "Neutral" : "Neither positive nor negative" } task = ClassificationTask( input_description=input_description, label_description=label_description, labels=labels, require_reason= True , llm= "openai" , language= "en" , ) output = task( input_text= "The order was delivered right away!" , model= "gpt-3.5-turbo-1106" , ) labels = output.label label_index=output.label_index print (label_index, labels) # >> 0 Positive reason = output.reason print (reason) # >> The comment 'The order was delivered right away!' expresses satisfaction and a positive impression. 内部ではテンプレートに従ってだいたい以下のようなイメージのプロンプトが作成され、パースされた出力結果が戻り値として返るようになっています。 """ Perform text labeling according to the following instructions. Let text X be EC review comments. Let label Y be Impressions from the comments. Select one of the labels Y that corresponds to text X. Briefly describe the reason for the labeling. The output format is json format as follows: {"reason": "Reason for labeling (string type)", "label": "Label to be assigned (string type) "} for example {"reason": "Because it is xx.", "label": "label"}. label Y=['Positive', 'Negative', 'Neutral'] Details of each label: {'Positive': 'Receive a good impression(endorsement, praise, support, recommendation)', 'Negative': 'Receive a bad impression(disagree, criticize, attack, slander)', 'Neutral': 'Neither positive nor negative'} text X='The order was delivered right away!' """ 別の言語の分類においても同様な形式でタスクを実行することができ、exampleとしてのデータの追加やマルチラベル出力の指定なども可能となっています。 input_description = "ECサイトのレビューコメント" label_description = "コメントから読み取れる印象" labels = { "ポジティブ" : "良い印象(賞賛、支持、推奨など)" , "ネガティブ" : "悪い印象(批判、攻撃、中傷など)" , "ニュートラル" : "どちらでもない中立的な印象" , } task = ClassificationTask( input_description=input_description, label_description=label_description, labels=labels, multi_label= False , require_reason= True , require_confidence= True , llm= "openai" , language= "ja" , ) task.set_examples( example_inputs=[ "とても良い品質でした。" , "包装が破れていました。" , "六本木に本店があります。" ], example_labels=[ "ポジティブ" , "ネガティブ" , "ニュートラル" ]) output = task( input_text= "注文された商品がすぐに届きました!" , model= "gpt-3.5-turbo-1106" , ) labels = output.label label_index=output.label_index print (label_index, labels) # >> 0 ポジティブ reason = output.reason print (reason) # >> 「すぐに届きました!」という内容から、賞賛の意味でポジティブな印象に該当します。 プロンプトのテンプレートは以下のような形式で言語ごとに実装しています。(あまり洗練されていません。) class _ClassificationPrompt : role: str task_description: str input_definition_holder: str ... def __init__ (self, input_description: str , label_description: str , labels: dict [ str , str ], multi_label: bool = False , require_reason: bool = False , require_confidence: bool = False ) -> None : self.instruction = self._build_instruction() ... def _build_instruction (self) -> str : instruction = "" instruction += self.task_description instruction += self.input_definition_holder.format(input_description=self.input_description) instruction += self.output_definition_holder.format(label_description=self.label_description) ... return instruction class _EnglishClassificationPrompt (_ClassificationPrompt): role = "Machine learning model for labeling text" task_description = f "Perform text labeling according to the following instructions." input_definition_holder = "Let text X be {input_description}." ... class _JapaneseClassificationPrompt (_ClassificationPrompt): role = "テキストのラベル付けを行う機械学習モデル" task_description = "次の指示に従いテキストのラベル付けを実行せよ。" input_definition_holder = "テキストXを{input_description}とする。" ... 実際に上記のツールを使用してテキストデータのラベル付けを行い、非常に低コストで十分な量および質(人手でのアノテーション結果と比較して遜色ない程度)のデータセットを作成することが可能となりました。 例えば商品カテゴリの分類に関しては最終的に百を超えるラベル数を設定する形になりましたが、ファインチューニングを行ったモデルのメトリクスに関しても十分な数値を出すことができています。 課題や展望など 出力のフォーマット 柔軟性の高さと引き換えに、出力が100%期待した形式になるとは限らないという点はチャットLLMのよく知られた欠点の1つであり、今回のようなテキスト分類においても例外ではありません。 上記のツールではラベル名を直接回答する形でプロンプトを作成していますが、与えられた選択肢に適切なものがなかった場合などは、選択肢にないラベル名を回答することも多々ありました。 ラベル名を回答させるのではなく、{1: ”ラベル1”, 2: “ラベル2”, 3: “ラベル3”}といった形式の選択肢に対して該当するラベルの番号を返すようにプロンプトを実装することも可能で、当初はこの形式を採用していました。この場合出力のフォーマットは安定するものの、精度が下がる(reasonを見る限り正しく理解はできているが、インデックスを選ぶところで間違えている)ケースが見られたため、ラベル名を直接答える方式を採用しました。 一方で選択肢にないものが多数回答されている場合には、ラベルセットを見直す参考にもなるためその点では役に立つ側面もありました。 プロンプトの長さ 現状の仕組みでは1データの推論を行う度にそのデータのテキストだけでなく、共通の指示やラベルの説明といったタスクの説明を行うシステムプロンプトも逐一投げる必要があります。 そのため特にラベル数が百を超えるような日本語のタスクでは全体のトークンサイズが非常に大きくなってしまうため、ラベルの説明部分に細かい具体例を記述せず、簡素なものにせざるを得ないということもありました。 一度ベースとなる指示を与えた後で複数データのテキストをまとめて入力することも可能ではありますが、トークン数が長くなることによって精度が下がる可能性やそもそもトークン数のサイズ制限に引っかかる可能性があるという点を留意すると、一筋縄ではいかないかもしれません。 あるいはChatGPTのファインチューニング機能をうまく活用できればこの点を解決できるかもしれず、その辺りは要検証といったところです。 閾値の設定 テキスト分類モデルと同じような振る舞いをしていますが、出力が確率ではなくラベルそのものであるため、閾値の調節ができない点は欠点として挙げられます。 一応プロンプトの中に確度(confidence)として0〜100の数値を出力させるようなオプションにも一応対応させてはみましたが、あまり信頼できない印象がありました。(全体で一貫した基準が保たれていない印象でした。) おわりに 冒頭でも触れた通りチャットLLMに関しては、情報検索の代替的な使い方や、要約、翻訳、相談役、アイデア創出、語学学習といった「話し相手」「アシスタント」的なものが代表的なユースケースとして取り上げられることが多いですが、見方を変えれば、定義した問題を解釈し、自動的に解きながら指定したフォーマットで出力してくれるツール的な使い方も可能です。 人間の話し相手になってくれたり、人間が解けない難しい問題を解いてくれたりするAIという側面の他に、従来人間が行なっていた「単純で答えもある程度決まっているが世界知識がないと解けない」自然言語の絡むタスクを自動的に処理してくれるAIという側面に着目すると、業務の大幅な効率化やコスト削減が図れるかもしれません。 また、実際プロンプト次第ではテキスト分類のアノテーションだけでなくテキストのクラスタリング[4]やデータの拡張[5]などについても応用例が考えられており、従来の機械学習技術とうまく組み合わせることでも新しい価値や、インパクトのある使い道が発見できるかもしれません。 最近ではチャットLLMは画像とテキストの入出力対応によるマルチモーダル化という次のステップに進みつつあり、LLMを利用する人間側も頭を柔らかくして、いろいろな利用可能性を模索、検証していくことが求められます。 最後となりますが、弊社では機械学習エンジニアを募集しております! ご興味のある方はお気軽にご応募ください! https://open.talentio.com/r/1/c/binc/homes/4380 明日は@endu さんの記事です。お楽しみに! References [1] Fabrizio Gilardi, Meysam Alizadeh, Maël Kubli, ”ChatGPT Outperforms Crowd-Workers for Text-Annotation Tasks”, Mar 2023. [2] G. Hinton, O. Vinyals, and J. Dean, “Distilling the knowledge in a neural network”, Mar. 2015. [3] https://openai.com/policies/terms-of-use [4] Yuwei Zhang, Zihan Wang, Jingbo Shang, ”ClusterLLM: Large Language Models as a Guide for Text Clustering”, May 2023 [5] Zhihong Shao, Yeyun Gong, Yelong Shen, Minlie Huang, Nan Duan, Weizhu Chen, ”Synthetic Prompting: Generating Chain-of-Thought Demonstrations for Large Language Models”, Feb 2023
アバター
はじめに 本記事は BASE アドベントカレンダー 2023 の14日目の記事です。 こんにちは!NEW Dept/Pay ID Dev/Web Backendエンジニアをしている@zanです。 主にPay IDの機能開発を担当しています。 SMS OTPで用いられるメッセージの形式を題材に、 どのような経緯で形式が決まったかを調べてみました。(ちょっとした考古学?みたいなものです。) SMS OTPとは SMS OTPと関連技術について振り返ってみましょう。 ...と思いましたが、 過去 @gatchan0807 が書いた 今度は「WebOTP」についてFrontend Weekly LT(社内勉強会)でお話しました に詳しく書かれているので、気になる方はご一読いただけると幸いです。 (※2021年の記事なので一部情報が古くなっている可能性があります。) 簡単に言ってしまうと SMS OTP = 指定の電話番号にSMSを使ってOTPを送信する 関連技術 = WebOTP API, autocomplete="one-time-code" ...etc です…! SMSの形式 さて、本題のSMS OTPで用いられるメッセージの形式です。 Origin-bound one-time codes delivered via SMS#authoring によると In the following origin-bound one-time code message, the top-level host is "example.com", the code is "747723", no embedded host is specified, and the explanatory text is "747723 is your ExampleCo authentication code.\\n\\n". "747723 is your ExampleCo authentication code. @example.com #747723" と定義されていますね。 実際にSMSに @example.com #747723 形式で送信されてきたメッセージを見たことがある方も多いと思います。 また、 Enabling AutoFill for domain-bound SMS codes でも同様に Your Example code is 123456. @example.com #123456 と定義されていますね。 ここらへんは形式が統一されているようです。 クロスオリジンのiframeはどうなる? では、クロスオリジンのiframeのなどの場合のメッセージの形式はどうなるのでしょうか? あまりユースケースがないのかもしれませんが、 WebOTP API やSafariでは、クロスオリジンのiframeでのSMS OTPの入力をサポートしています。 前述のメッセージの形式と異なるので、それぞれ見ていきましょう。 Origin-bound one-time codes delivered via SMS#authoring では、 In the following origin-bound one-time code message, the top-level host is "example.com", the code is "747723", the embedded host is "ecommerce.example", and the explanatory text is "747723 is your ExampleCo authentication code.\\n\\n". "747723 is your ExampleCo authentication code. @example.com #747723 @ecommerce.example" と定義されています。 一方、 Enabling AutoFill for domain-bound SMS codes では、 Your Example code is 123456. @example.com #123456 %iframe-auth.example.org と定義されています。 あれ、と思ったかたもいるかもしれません...! よくよく見てみると、微妙に形式が違います。 @example.com #747723 @ecommerce.example @example.com #123456 %iframe-auth.example.org 気づきましたね...! embedded hostが @ か % かの違いです。(typoではありません...!) なぜこの形式になったのか時系列で整理してみた。 なぜこのような違いがあるか疑問に思ったので、時系列で整理してみました。 Apr 30, 2020 Third-party authentication / nested iframes #4 を見てみると 元々はクロスオリジンのiframeをサポートする思想ではなかったようです。 (むしろ、意図的に無視していたようです。) August 4, 2020 Enhance SMS-delivered code security with domain-bound codes の提供開始。 このときはクロスオリジンのiframeに関する記述はありませんね。 123456 is your Example code. @example.com #123456 との記載があるように @example.com #123456 形式のみのようです。 Oct 16, 2020 https://github.com/WICG/sms-one-time-codes/issues/4#issuecomment-709557866 この時点で既にクロスオリジンのiframeをサポートするためにembedded hostに % 形式を用いていたことがわかります。 議論が再燃して、embedded hostに % 形式を採用するかなど、議論がなされていました。 他にも @top #code %iframe or @top #code @iframe or @top @iframe #code などなど...。 Mar 24, 2021 https://github.com/WICG/sms-one-time-codes/pull/5 最終的に @example.com #747723 @ecommerce.example 形式でマージされたようです。 Dec 13, 2023(※執筆時点) Enabling AutoFill for domain-bound SMS codes によると @example.com #123456 %iframe-auth.example.org 形式がいまだに使われています。 ほうほう...。 紆余曲折経て、最終的に @example.com #747723 @ecommerce.example に着地したことがわかり面白いですね。 そして先駆者はembedded hostに % 形式を採用しつづけていると...。 つまり、紆余曲折あって @example.com #747723 @ecommerce.example @example.com #123456 %iframe-auth.example.org に分かれたわけですね。 さて実動作はどうなのか。 気になるところ実動作はどうなのか、というところです。 実動作を見てみるか、と思って調べてみるとブラウザ/OSの違いでそもそもお困りの方が多いようです。 ですので、調査結果はまた別の記事にしたいと思います。(iOS + Chromeェ...。) (また、いろいろ整理できたので、個人的にも議論に参戦してみたいな、とも思いました。) Domain bound SMS Otp autofill not working in WKWebView (but does in Safari) Format is inconsistent with iOS docs #18 まとめ SMS OTPで用いられるメッセージの形式の考古学をしてみました。 こう見てみると、どのような目的があって、今の形式になったのかがわかって面白いですね。 フォーマットや形式はbikeshedな議論になりやすい文脈かと思いますが、最終的な着地点が気持ちいいですね。 何気なく使っている技術も深掘りしてみると面白いものですね。 明日は @TakeuchiSho さんの記事です!お楽しみに。 参照 今度は「WebOTP」についてFrontend Weekly LT(社内勉強会)でお話しました Origin-bound one-time codes delivered via SMS#authoring Enabling AutoFill for domain-bound SMS codes Domain bound SMS Otp autofill not working in WKWebView (but does in Safari) Format is inconsistent with iOS docs #18
アバター
こんにちは。BASE株式会社でプロダクトマネージャー(以下PdM)をやっています、船坂です。 この記事は BASE Advent Calendar 2023 の13日目の記事です。 アドベントカレンダーもあっという間に折り返し地点、12月は日が経つのがあっという間ですね。 さて、この記事では、自分が今年関わったプロジェクト(以下PJ)の中から、特に特徴のあった2つのPJ、「BASE AI アシスタント」アップデートと、インボイス制度対応についてご紹介できればと思います。 前者はBASEのAI関連機能の総称である「BASE AI アシスタント」の機能追加という整理ですが、実質的にはAIを用いて新たな可能性を探る、新機能の開発プロジェクトです。どちらかというと輝かしいプロダクト開発的な側面が目立ちます。 後者は、ECプラットフォームである「BASE」上で起こるありとあらゆる取引をインボイス制度に対応させるという、対応必須な法対応かつ影響範囲の広い地味で大変なプロジェクトです。どちらかというと縁の下の力持ち的な作業となります。 これらのPJ、正反対かと思いきや、PdMとして両PJに参加していると似ている側面も持っていました。 それらをご紹介しつつ、PdMとしてやっていたことを書きながらふりかえっていこうと思います。 AI機能開発とインボイス対応、2つの共通点 これらの2つのPJ、大きくは以下の2点が似ていました。 要件を定める際に、制約が発生する リリース時期が非常に重要な意味を持つ それぞれについて詳しく説明します。 要件を定める際に、制約が発生する 各PJにおいて、自分はPdMとして作るものを決める立場で参加するわけですが、どちらのPJにおいても、要件を定める際の外的要因の影響力がとても大きかったと感じました。 AIの場合:できるかできないかはAI次第でもある AI機能においては、目下の施策の要件を考えるためには「現時点でAIにできるのか」「それは自分たちでもできるのか」が非常に重要になってきました。PJ初期では、企画を作ったあとにAIを触ってみると、想定していたよりもAIを利用してアウトプットのクオリティを上げることができず、現状では諦めるといった決断をしたアイデアも発生しています。(画像生成系など) これまでの機能開発においてはエンジニアが「できそうだ」と想定できれば、それはほぼほぼ実現できたわけですが、AIをくみこんだ機能開発では、ソリューション実現の根幹にAIがいるため、AIも含めてクオリティを担保できなければ、リリースまで持っていくことができません。 企画を検討する中で、早い段階でAIに指示を出すためのプロンプトを作成し、どこまでAIができそうか、アテをつけながら企画することが非常に重要でした。 インボイスの場合:インボイス制度が絶対的な存在である 言うまでもなく、インボイス制度対応の要件に影響を与えるのは、インボイス制度です。BASEにおけるインボイス制度対応の内容が決まるまで、大きく下記のような流れで進行していきました。 インボイス制度概要を理解する BASEにおける全ての課税取引を洗い出す BASEに関連する部分のインボイス制度の内容を更に詳しく理解する。法的に定められた「しなければならない」「することが望ましい」を把握する 「しなければならない」「することが望ましい」ことの実装方法を調査(※)し、ユーザー体験を考慮しながら各取引のインボイス対応方針を定める。 ※ここでの調査とは例えば「メールでインボイスを送ることは認められるか」など、法的に認められる実装方法を調査すること 感想 見てきたように、どちらのPJも、PdMはそのドメインの知識をかなり深くまで知ることが要求されました。というより、筋の良い施策を考えるためには、制約を生み出す要因について詳しく知らないと、定義の難しい領域のプロダクト要件を的確に設定することができないのだと感じます。 外的制約がたくさん発生する複雑な領域こそ、PdMを設置する意味が出て来るんだなと改めて感じました。 リリース時期が非常に重要な意味を持つ AIの場合:リリースタイミングでのAIの話題性 生成AIの劇的な進化が注目を集めて以来、様々な企業がAI機能をリリースしています。現在は、毎日のように新たなAI関連ニュースを目にします。 BASEとしては、今年の4月に出した「BASE AI アシスタント」では、まずはとにかく早くリリースしてAIの流れにしっかり対応しにいきました。逆に、この間の12月6日に新たにリリースした新機能3つは、本当に価値のある機能を提供することを目指して、スピード感は持ちつつも、確かに価値を感じてもらえるであろう機能を検討してリリースしました。 社内では、早期に一つずつリリースする方向性なども含めて様々な議論を行いましたが、結果的には、「BASEらしく、かんたんに使えてしっかり価値を感じられる」機能をいくつかまとめてリリースすることを優先した形です。 AIの話題性が刻一刻と変わる状況を見ながら、どのような単位でいつリリースするか、検討した結果が今回のリリースとなっていました。 インボイスの場合:どのような状態でXデーを迎えるか インボイス制度対応については、2023年10月1日のインボイス制度適用開始日は動かないので、それ以前でどのようなタイミングからなんの機能を提供するか、実装順やリリース日の決定、事前告知告知はどのように行うかを非常に重視しました。 BASEでは、インボイス発行記録を保持するDBの一ヶ月分のデータをまるっと経理のデータと突合して、金額のズレがないか(=実装漏れが無いか)のテストをする必要がありました。これは実装後、1ヶ月間の集計期間をおいたうえで初めて実施できるテストだったため非常に期限がシビアでしたので、各種取引において、DB書き込みまでの作業のみ先行して全て実施してから、突合テスト対象外の部分の実装を進めるなど、開発作業順を工夫しました。 また、今回はBASE株式会社自身だけではなく、BASEをご利用のショップもインボイス制度に対応できるようにしたので、制度開始ギリギリでの機能リリースではなく、インボイス制度対応の準備ができる期間を想定したうえでの機能リリースとなりました。 インボイス制度対応のために必要な「適格請求書発行事業者の登録番号」だけは早めに入力して貰わないと制度開始日から自動でインボイスを発行することができないため、登録番号を入力してもらうフォームだけ先行リリースして設定期間を設けるなど、どの機能がいつまでにあれば全てのショップが10月1日に対応できているのか、時期を調整しながら制度開始当日を迎えました。 感想 これらのプロジェクトはGo-To-Marketの視点が非常に重要で、「要件を決めたら、あとは頑張って作って、でき次第出す」というようなシンプルなPJではなかったのが特徴でした。PdMとしてはどのタイミングでその機能を提供することで価値が最大化されるのかを真剣に考える必要があり、良い経験になりました。 ブランディングチームやお問い合わせに対応していただいてるチームなどと連携しながら、どういう状態を目指してリリースするかをすり合わせるのが重要だと改めて感じました。 まとめ 今回はAIとインボイス対応という、一見すると正反対の2つのPJの共通点として、 要件を定める際に、制約が発生する リリース時期が非常に重要な意味を持つ というよく似た側面をご紹介しました。 自分はPdMとして色々なプロジェクトに参加していると、ついついその施策ごとに異なる人格を作ってしまいがちというか、意外と学習したことを転用できると気づかなかったりするんですよね。 こうやってあとから共通点を抽出してみると様々なPJに転用できるような汎用的なナレッジが見つかって、いいふりかえりになるなと感じました。 PdMは役割の定義が難しい職種の一つだと思いますが、色々なことを経験できるのは、魅力の一つでもあります。 様々なことに興味を持って、学習をしながらできることを増やしていくことに楽しさを感じる方は、ぜひプロダクトマネージャーというキャリアを検討してみて下さい! そして、興味があればBASEも検討してみてもらえるともっと嬉しいです! 明日は @zan さんの記事です!お楽しみに。
アバター
この記事は BASE Advent Calendar 2023 と 身の回りの困りごとを楽しく解決! by Works Human Intelligence Advent Calendar 2023 の12日目の記事です。 はじめに こんにちは、BASE BANK Division で資金調達サービス「 YELL BANK 」の開発を担当している Doarakko です。 BASE BANK Division については、最近社内異動制度を使用して入られた方がブログを書いてくださったので気になる方は読んでみてください。 参考: フルサイクルエンジニアリングの第一歩を進める - BASE BANKでの新たな挑戦 昨今リモートワークが増えていますが、弊社でも出社とリモートを組み合わせたハイブリッドワークとなっています。 出社頻度が少なくなったことで「オフィスに全然人がいない…」「なんだか職場が暗いな」とぼんやり思われている方が多いのではないでしょうか。 そこで今回はその課題を解決するために「職場を明るくする」ための画期的な装置を作りました。 作成したもの BASE でリリースが行われるとオフィスにあるライトがカラフルに光り出すというものです。 youtu.be 何だかリリースするのが楽しくなってきましたね。 ちなみにこちらのライトスタンドは弊社の福利厚生「 BASE加盟店の購入補助制度 」を使用して購入したものです。 こちらの制度では毎月1万円まで BASE 加盟店で買い物することができます(2023年12月現在)。 構成 Slack Platform 上で動かしているアプリが、リリース完了のメッセージをトリガーに SwichBot API にリクエストを送信することでライトを光らせています。 SwichBot にはスマート電球以外にも多くの IoT 製品があり、その多くが Web API からも操作することが可能です。 参照: OpenWonderLabs/SwitchBotAPI Slack Platform とは Slack の 次世代プラットフォーム機能 とは、今までより簡単に Slack アプリを開発、実行できる基盤機能です。 Slack の有料プランでのみ使用可能となっています(2023年12月現在)。 ポイント Slack アプリの実行基盤(AWS Lambda、Heroku、etc)を用意する必要がない デプロイは CLI で行い、アプリの作成もマニフェストファイルから自動で行われる ブラウザ上でボタンぽちぽちする必要がない Slack のアプリ作成フレームワーク「 Bolt 」 は動かせない 専用の SDK (ランタイムは Deno)を使用する必要がある Bolt よりも型サポートが強力 Bolt とは構造が完全に異なっており、癖がある(個人的に)ため初期の学習コストは少し高め データストアも提供している DynamoDB のシンタックスで使用可能 ワークフローの実行回数のみが課金対象のためいくらでもデータを保存することができる(2023年12月現在) 参照:Slack プラットフォームの機能と料金プランガイド 実行基盤を用意する必要がないというのは魅力的すぎます。 ただ現在の仕様だとアプリが使用されるチャネルをハードコードする必要がある(Bolt の場合はチャネルに入れるだけで OK ですが)ため、使用されるチャネルが頻繁に増えるようなアプリには向いていません。 workflow: `#/workflows/${Workflow.definition.callback_id}`, event: { event_type: TriggerEventTypes.MessagePosted, channel_ids: ["CXXXX"], filter: { version: 1.0, root: { operator: "AND", inputs: [ { // triggers only bot messages statement: "{{data.user_id}} == null", }, { operator: "OR", inputs: [ { statement: "{{data.text}} CONTAINS 'デプロイが完了しました'", }, { statement: "{{data.text}} CONTAINS 'リリースが完了しました'", }, ], }, ], }, }, }, Slack アプリを開発する方法はいくつかありますが、ちょっとしたツールを作る際に上記を許容できるのであれば Slack Platform 一択と言っていいのではないでしょうか。 コードは Doarakko/release-party に公開しているので、ぜひみなさんも職場を明るくしてみてください。 トラブル発生:社内ネットワーク環境が快適 自宅でのデモとブログを書き終えて意気揚々とライトスタンドをオフィスに設置しに行ったところ、トラブルが発生しました(冒頭のデモ動画は上述とは別の方法で光らせています)。 SwichBot を操作するためには本体を 2.4GHz 帯の Wifi に接続する必要があるのですが、なんと社内で接続可能な SSID の一覧に 2.4GHz 帯のものが存在しません。 慌てて情シスの方に確認したところ、バンドステアリング機能を使用して自動で帯域が切り替わるようにしているとのことでした。 バンドステアリング機能とは、Wifi に接続された端末を 2.4GHz と 5.0GHz のうち混雑していない帯域に自動的に接続する機能です。 こうして快適にオフィスで仕事ができていたんだなと知れたのは良かったのですが、それどころではありません。 ここまできて引き下がるわけにはいかないので別の方法で明るくすることにしました。 解決策:Raspberry Pi + Philips Hue Philips Hue は SwichBot と同じスマート電球ですが接続方法が異なります。 Wifi との接続はブリッジを介して行い、ブリッジとスマート電球本体との通信は ZigBee という無線通信規格で行われています。 Philips Hue の Web API へのリクエストはローカルのネットワークからしか行えません。 同じネットワークに接続した Raspberry Pi 上で Slack ボット(Bolt for JavaScript)を動かし、リリース完了のメッセージをトリガーに Philips Hue API にリクエストを送信する形でライトを光らせます。 当初は Web API が公開されている別のスマート電球を購入して API の向き先を変えるだけかなと思っていたのですが、私が調べた範囲では 5.0GHz 帯の Wifi に直接接続可能なスマート電球を見つけることはできませんでした。 どのスマート電球も 2.4GHz 帯の Wifi にしか接続できないのはおかしいなと思い色々と調べましたが納得のいく理由には辿り着けず、Q&A サービスの「Mond」に質問を投稿したところなるほどなという回答をいただけたので気になる方は読んでみてください。 参考:最近スマート電球についていろいろと調べていたのですが… おわりに インターネットから少し現実世界に飛び出したことで、IoT 開発ならではの楽しさを味わうことができました。 ただ家で遊ぶ用のラズパイがなくなってしまったので次回の購入補助を使って買いたいと思います。 明日の BASE Advent Calendar 2023 は @takumi_funasaka さん、 身の回りの困りごとを楽しく解決! by Works Human Intelligence Advent Calendar 2023 は @wanko_in_lunch さんが担当です。 お楽しみに!
アバター
はじめに こんにちは。シニアエンジニアのプログラミングをするパンダ( @Panda_Program )です。本記事は BASE アドベントカレンダー 2023 の11日目の記事です。 BASE のバックエンド開発では巨大なモノリスからモジュラーモノリスへの移行が進んでいます。この記事では、モジュラーモノリスの中で自分のチームが担当しているモジュールに導入した PHPStan のカスタムルールの導入とその効果について紹介します。 PHPStan は BASE のモジュラーモノリスなバックエンドシステムに既に導入されていました。モジュラーモノリスの中で PHPStan のカスタムルールは2種類あります。各モジュールが守るべき共通のルールと、それぞれのモジュール内で特有のルールです。 PHP のコード品質を担保する PHPStan は多くの開発現場で採用されていますが、具体的なカスタムルールの事例はあまり公開されていないように思います。そこで、自分たちが実際に現場で使用しているカスタムルールの一部を共有します。PHPStan のカスタムルールに関しては、どのようなルールを設けているか、また具体的なコードにどう落とし込んでいるかの2つの側面があります。特に後者の面で一定の価値があるのではないかと考えて記事にしました。 自分が所属しているチームでは、 CRM モジュールという BASE の CRM 領域の機能を担う部分を開発しています。本記事では CRM モジュールで適用するカスタムルールに限定して紹介します。 課題: レビューで同様の指摘をしていること ツールは何らかの課題を解決するための手段です。カスタムルールを作成するに至った理由も、コードレビュー時にどうも似たような指摘を複数回、いろんな人にしているかもしれないという課題があったからです。 ストレートに考えると同様の指摘を複数回することの解決策は、コーディング規約を作成したりモブプロ・ペアプロで担保することです。そこで開発チームの前提を共有すると、現在2つのスクラムチームがCRM モジュールの担当になっています。メンバー構成について、Aチームはバックエンドエンジニアが6名、Bチームはフロントエンドエンジニア2名+バックエンドエンジニア3名であり、自分もその中の一人です。 LeSS (Large-Scale Scrum)というフレームワークに基づき、2チーム体制で開発をし始めて1年以上が経ちます。しかし、当たり前のことですが、チームは均質ではありません。時間が経って互いのチームが LeSS に慣れたとしても、一人一人のスキルにバラツキがあったり、他チームからサポートとして来てくれている方が2名いたりと、コミュニケーションコストが減ることはありません。 以前はコーディング規約や細かい書き方のドキュメントは整備されていませんでした。このため、チーム歴が長い人がペアプロをして CRM モジュールではこう書くんだよと口頭で伝えるか、コードレビューで誰かがコメントすることで知識の平準化が図られていました。しかし、ペア・モブプロにも限界はあります。設計レベルの議論はチームを横断して行われるものの、「今はこう書くようになった」という細かい実装の知識はチーム内に閉じてしまいます。 自分は両チームのコードレビューをしているのですが、レビューコメントでその中で細かいコーディングレベルの話を共有することを心がけていました。同一モジュール内でも UseCase ごとに書き方がバラけてしまうを防ぐためです。 AチームのXさんのコードレビューで、プロパティが readonly ではない DTO を見つけては、コメントで「最近Bチームでは DTO は必ず readonly class にすることになったよ」と伝えたり、BチームのYさんのコードに switch 文を見つけては「過去に swtich 文は使わず match 式にしようという指摘があって、あの時はチーム内で特に議論はなかったけどやっぱり match 式使う?どうする?」と決定を促す質問をするうちに、両チームの過去のプルリクエストに遡り、当該コメントのURLを貼るのも流石に大変だなと思うようになってきました。 そこで、非同期で誰でも参照できるコーディング規約を作ることと、静的解析で実装レベルの表記揺れを防ぐことを目指しました。そのために、まずは意を決して(というほど大変でもなかったですが)直近3ヶ月の約180件のPRを見返しました。結果的に、期間は1週間ほどで開発のスキマ時間で過去のPRを辿っていきました。これは良い指摘だと思えるレビューコメントをPRの中からピックアップし、その内容を以下の3つに分類しました。 静的解析で検出できるもの 必ず守る方針 どちらでもいいもの 解決方針: コーディング規約を作る一方で、静的解析で表記揺れを検出する それぞれの解決方針は以下の通りです。 分類 対策 静的解析で検出できるもの PHPStanのカスタムルールを作成して CI で検査する 必ず守る方針 ドキュメント化されていなかった暗黙のルールだったのでコーディング規約という形で文書化 どちらでもいいもの 本当にどちらでも良いので特にルール化はしていない、ということを明示するために文書化 「必ず守る方針」も「どちらでもいいもの」については、マークダウンで文書化してモジュラーモノリスがあるレポジトリにコミットしました。そうすると、S3 にデプロイした VitePress 上で誰でも(もちろん社内の人限定ではありますが)閲覧することができます。この仕組みもちょっと面白いので、その話はどこかで書くかもしれません。 「静的解析で検出できるもの」については PHPStan で対応することにしました。なお、カスタムルール作成を検討するにあたり、ルールにするほどか迷うものは両チームにアンケートを取っていました。全て過去の PR でコメントがあったものです。 slack の投稿 1つ目の自身をインスタンス化する書き方については、本当にどっちでもいいので特に文書にも記載していません。2つ目の「テストコードで if 文を書けないようにする」ものは、カスタムルールを作成しました(下に実コードを載せています)。3つ目の PHP 組み込みの enum の生成テストについては、あって困ることはないが特に書かなくても問題ないということを「どちらでもいいもの」の文書に掲載しています。 (3つ目は、例えば String の Backed Enum SomeEnum::Foo がある時に、 SomeEnum::Foo->name === 'Foo' , SomeEnum::Foo->value === 'foo' であることをテストするかしないかというものです) カスタムルールの紹介 CRM モジュール内で実際に使用しているPHPStanのカスタムルールをいくつか紹介します。 DTO は Readonly Class にするというルール 名前の最後が Dto であるクラスは、必ず readonly class にするというルールです。readonly class は PHP 8.2 からの機能で、コンストラクタ以外でプロパティに代入ができないので DTO に最適です。 <?php declare ( strict_types = 1 ) ; namespace BaseInc\Modules\Crm\Tools\PhpStan\Rules; use PhpParser\Node; use PhpParser\Node\Stmt\Class_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; /** * DTO は Readonly Class にするというルール */ final class ReadonlyDtoRule implements Rule { public function getNodeType () : string { return Class_ :: class ; } public function processNode ( Node $ node , Scope $ scope ) : array { if ( ! ( $ node instanceof Class_ )) { return [] ; } $ isInApplicationDirectory = str_starts_with ( $ node -> namespacedName -> toString () , 'BaseInc\Modules\Crm\Application' ) ; $ isRequestClass = str_ends_with ( $ node -> namespacedName -> toString () , 'Dto' ) ; if ( !$ isInApplicationDirectory || !$ isRequestClass ) { return [] ; } return $ node -> isReadonly () ? [] : [ 'DTO が Readonly Class ではありません' ] ; } } DTO には setter が不要なので、PHP 8.2 だと以下のようにシンプルに書くことができます。 readonly class MembershipMemberSummaryDto { public function __construct ( public MembershipId $membershipId, public UserId $userId, public MembershipName $membershipName, public MembershipMemberActionType $actionType, public int $count, ) { } } switch 文を禁止するというルール switch 文を使ってはいけないというルールです。CRMモジュール内では swtich はそもそも1箇所でしか使われていなかったので簡単に書き換えができること、switch でできることは match 式で簡単にできることから採用されました。 <?php declare ( strict_types = 1 ) ; namespace BaseInc\Modules\Crm\Tools\PhpStan\Rules; use PhpParser\Node; use PhpParser\NodeFinder; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; final class NoSwitchRule implements Rule { public function getNodeType () : string { return Node\Stmt\ClassMethod :: class ; } /** * @param Node\Stmt\ClassMethod $node */ public function processNode ( Node $ node , Scope $ scope ) : array { if ( !$ scope -> isInClass ()) { return [] ; } $ nodeFinder = new NodeFinder () ; $ switchNodes = $ nodeFinder -> find ( $ node -> stmts, fn ( Node $ node ) => $ node instanceof Node\Stmt\Switch_ ) ; if ( count ( $ switchNodes ) === 0 ) { return [] ; } return [ 'switch 文は使用せず、match 式に置き換えてください' ] ; } } アンケートの投票中に「このケースとあのケースは match 式で実現できないのでは?」という質問がありました。そこで「それは全部 match 式に置き換え可能ですよ」と返事をしたところ、全員一致で賛成になりました。ルール化の検討がチーム内で match 式の理解を深めるきっかけになってよかったです。 if 文のネストを禁止する 過去のPRを遡ると、「ここは早期リターンで書けるのでは?」というコメントがちらほらあったのでif 文のネストを禁止するルールを追加しました。 <?php declare ( strict_types = 1 ) ; namespace BaseInc\Modules\Crm\Tools\PhpStan\Rules; use PhpParser\Node; use PhpParser\Node\Stmt\If_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; final class NoNestedIfRule implements Rule { public function getNodeType () : string { return If_ :: class ; } public function processNode ( Node $ node , Scope $ scope ) : array { if ( !$ node instanceof If_ ) { return [] ; } $ parent = $ node -> getAttribute ( 'parent' ) ; while ( $ parent !== null ) { if ( $ parent instanceof If_ ) { return [ 'if 文のネストは禁止されています' ] ; } $ parent = $ parent -> getAttribute ( 'parent' ) ; } return [] ; } } またテスト界隈では「テストで if 文を使うのは良くない、それならそもそもテストケースを分けるべきだ」という意見があり、実際に過去の PR で同様の指摘があったため、テストメソッドの中で if 文があれば検出するルールも書いています。 <?php declare ( strict_types = 1 ) ; namespace BaseInc\Modules\Crm\Tools\PhpStan\Rules; use PhpParser\Node; use PhpParser\Node\Stmt\If_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; final class NoIfInTestRule implements Rule { public function getNodeType () : string { return If_ :: class ; } public function processNode ( Node $ node , Scope $ scope ) : array { if ( !$ node instanceof If_ ) { return [] ; } $ method = $ scope -> getFunction () ; if ( $ method === null ) { return [] ; } $ isTestMethod = str_starts_with ( $ method -> getName () , 'test' ) || str_contains ( $ method -> getDocComment () ?? '' , '@test' ) ; return $ isTestMethod ? [ 'テストメソッド内で if 文は使用できません' ] : [] ; } } Repository の参照系のメソッドの命名の OrFail を末尾に固定する とても細かいことなのですが、Repository の参照系のメソッドの命名が「findById OrFail 」や「find OrFail ById」のように、 OrFail の位置が固定されていませんでした。命名が Repository ごとに異なることは、レビューする側として気になったため、こちらもアンケートを取りました。投票の結果、末尾以外で書けないようにするルールを作りました。 <?php declare ( strict_types = 1 ) ; namespace BaseInc\Modules\Crm\Tools\PhpStan\Rules; use PhpParser\Node; use PhpParser\Node\Stmt\ClassMethod; use PhpParser\Node\Stmt\Interface_; use PHPStan\Analyser\Scope; use PHPStan\Rules\Rule; final class FindOrFailRule implements Rule { public function getNodeType () : string { return Interface_ :: class ; } public function processNode ( Node $ node , Scope $ scope ) : array { if ( !$ node instanceof Interface_ ) { return [] ; } $ target = 'OrFail' ; foreach ( $ node -> stmts as $ stmt ) { if ( $ stmt instanceof ClassMethod ) { $ methodName = $ stmt -> name -> toString () ; if ( str_contains ( $ methodName , $ target ) && ! str_ends_with ( $ methodName , $ target )) { return [ 'メソッド名は findOrFailByFooBar ではなく、findByFooBarOrFail のように、OrFail を末尾に記述してください' ] ; } } } return [] ; } } このルールを適用した結果、なんと同一 Repository の中でも findOrFailByXxx と findByYyyOrFail のように混在しているインターフェースを一つ見つけました。それを見た瞬間に「細かいルールだけど、これはこれでやる意味があったな」と思いました。 繰り返される些細な指摘が なくなった もともと上記のような細かい指摘の数が多いわけではなかったので、たくさんのPRを遡ったりチームでアンケートを取ったりカスタムルールを記述してルール化するコストより、スポットで指摘するコストの方が低いと判断してルール化してはいませんでした。しかし、最近同じような指摘が増えてきてるかもと思い、今回整理することにしました。 これらのカスタムルールの導入から1ヶ月が経ち、レビューでの些細な指摘事項はなくなりました。表記揺れや細かい書き方を PHPStan の解析で検出できるようにしてから、コードレビュー時に本質的なところに意識を向けるだけで良くなり、「ここは細かいけど見逃せない!」と思う回数が減りました。細かい指摘というものはレビューされる側も小さいといえどもストレスを感じるものだと思います。また、コメントをする自分自身も気が楽になったので導入してよかったと思いました。他の人のレビューコメントを読んでいても、些細な指摘事項から生まれる些細な議論は無くなったと思います。 ただ、過去の PR をたくさん遡ることの副作用にも注意が必要です。悲しいことに、PHPStan のルールを作成する前後の期間、無意識のうちに自分のレビューコメントの指摘が細かく、厳しくなっていました。 slack 何か最近自分のレビューコメントが過剰かもなと自分でなんとか気づけたこと、またフォローしてくれる同僚がいてよかったです。 明日は、TanaamiYukiさんの記事です。お楽しみに!
アバター
この記事は BASE Advent Calendar 2023 の10日目の記事です。 ちわ BASEから代わりまして、PAY株式会社(BASE株式会社グループ会社)のクリス @x86_64 です。数か月前、絵を買うよう執拗に勧めてくる人に言いくるめられ、額縁入りのライザのアトリエ複製原画だのデジタルアートブック特典付きのアーマード・コア6だのを入手しました。 この記事は私がセキュリティエンジニアとして日頃感じることや昔話、将来のイメージについてろくろを回しまくり、なんか宣誓を立てるものです。少しはPCI DSS v4.0の話もしますがあとは自分の話しかしません。 PAY株式会社とは BASE株式会社グループ会社で、同社をはじめとして国内のスタートアップなど多くの加盟店にクレジットカード決済サービス「PAY.JP」を提供する会社です。クレジットカード情報を加盟店のみなさまに完全に代わって安全に取り扱います。 IT業界の中でも特に厳格と知られるセキュリティ基準「Payment Card Industry Data Security Standard (PCI DSS)」に準拠していることが要求されており、もちろん弊社はこの基準の最新バージョンであるv4.0に準拠しています。 昔話をしよう ここからは完全に自分の話です。似た思いの人を探すため自分から情報を発信します。アクティブソナーです。 私は学生時代から現在の役を務めており、入社してから数えるとまもなく8年目になりますが、ここに至るまでには少年時代からの経験が積み重なっています。 時は遡り2005年、市井はとある大手サイトでSQLインジェクションに起因する情報漏洩が起こったことで震撼していましたが、この事件こそが私がWebセキュリティの門をたたくきっかけとなりました。つまりは私のセキュリティへの関心を喚起した原動力は、この不善なる者たちが示した威力でした。 セキュリティにはもともと善悪の二面性がありますが、私がセキュリティを学んだ情熱の根源はいたずら心でした。システムにちょっかいを出すと情報や特権をポロッと出すという現象には征服欲をそそられました。この背景があるので動機が不純であったのは認めざるを得ないのですが、幸いなことにその当時からハッキングを学び試せるサイトが合法的に存在していたので、知識と楽しみを追求する道が閉ざされることはありませんでした。 そのときから抱いてきた情熱を胸に、私は弊社のPCI DSS準拠を主導するだけでなく、社内でペネトレーションテストもしています。身もふたもない話をすると、この仕事もしているやむにやまれぬ事情は、弊社のシステムが複雑すぎるうえに更新頻度が高いせいで専門機関によるテストが永遠に終わらない状況にあるということではありますが、おかげで却って社内で先進的なレッドチーム活動を行う機会が常にあるので、挑戦が終わることはありません。 ペネトレーションテストはPCI DSSにおいては少なくとも年に一度欠かさず実施する義務があるため、数奇なことに私がただただいたずら心の満足のために求めてきた知識がほぼそのまま役に立っています。ハッキング趣味は人に言うと怪訝な顔をされることもあり、親には誤解されてやめさせられそうにもなったものですが、今どきはセキュリティを確保する活動の中でも最も精密で厳正な手順としてペネトレーションテストが当たり前のものになりました。クレジットカード会社やクレジットカード決済を行う会社があればあるほど善なるハッカーもそれだけ多くいる前提になっている仕組みなので、この巡り合わせも奇妙なものです。 今を模索し、未来を描く 私にはこの仕事をカッコいいと思える時とだるいと思う時が交互に来ています。充実しているのですが、義務です。義務の履行はお世辞にも楽しいとは言えないものですが、使命感が燃えます。これは世界を救える仕事です。 セキュリティは技術的に非常に深遠で複雑怪奇ですが、今の世界では企業にとってセキュリティの確保は必達の義務であり、また他方では市民生活のうえでも基本的なリテラシーとなってきています。誰しもがいつなんどきでも標的にされうるため、自らを保護する必要があります。 私は決して恐怖やパラノイアの煽動をしたいのではなく、情報化社会ではひとりひとりが守護者として行動することができるということを伝えたいのです。もはや誰も彼もがセキュリティの確保を義務化されたも同然であるこの時代は、セキュリティに造詣の深い人材がありとあらゆる所に遍在すべき局面になっています。専門分野も生活も違う万人がセキュリティを共通の文化としてともに擁することができれば、守護者のひとりとして幸甚に存じます。 千里の道も一歩から BASEを筆頭とする弊社グループはIT産業の上場企業であり、扱う情報資産の価値やそれに伴う責任の重さは計り知れません。このような重要なポジションにある企業として、弊社はグループが一体となって数々の取り組みを行っています。 ひとつの例としては、全メンバーに対し徹底的な情報セキュリティ研修を実施しています。このような取り組み自体は広く普及していますが、弊社グループではその研修ならびにテストをセキュリティ専門のチームがすべて内製している点が一線を画すると考えています。 ディテールにもこだわり、全員にそれなりに高度なセキュリティ意識が浸透するように取り組んでいます。 例えば、多要素認証の分野では適切な2つの要素のペアを選択する問題と、厳密には多要素ではない多段階認証 *1 を指摘する問題を出すなど複数の着眼点を用意し、受講者がセキュリティについて自ら正しい判断を下せるレベルに成熟した体系的知識を確実に持つように工夫しています。 2020年からのコロナ禍を起点に世界中の働き方が変わり、急速にWFH(いわゆるリモートワーク)が普及しました。弊社も例に漏れず大急ぎでWFHが導入され未来がどうなっていくのかまるで予測できない状況が続きましたが、現在はハイブリッドワークスタイルが定着したようです。この働き方を作れたのは世界の激変が発端の数奇な出来事でしたが、速やかなWFH移行に技術から体制まで全面的に携われたのも、セキュリティエンジニア冥利に尽きます。今後もこういった画期的な取り組みをたくさん行う機会があるでしょう。 おわりに(採用活動オチです) この仕事、どうですか?セキュリティエンジニアになりたいですか? 募集しています! この募集要項の技術要件も4割くらい私が書きました。 AWSのイベントで登壇したことがありますが探さないでください。そうしなくてもたぶん変なところで巡り会うので。 次回はシニアエンジニア @Panda_Program の記事です。お楽しみに! *1 : 具体的には ID/パスワード と 秘密の質問の答え のペア。これらは共に知識要素である
アバター
この記事は BASE Advent Calendar 2023 の9日目の記事です ごあいさつ はじめましての人ははじめまして、こんにちは!BASE BANK Divisionのフロントエンドエンジニアのがっちゃん(  @gatchan0807  )です。テックブログに出てくるのは半年ぶりぐらいですね。お久しぶりです ちょっと大それた感じのタイトルを付けてしまいましたが、今回の記事では、先日 BASE BANK Divisionに社内公募という制度で異動して感じた BASE 組織との違いと、オンボーディングタスクでAWS ECSと格闘した記録をご紹介していこうと思います! また、20日の記事では私も含む、実際に社内公募制度を使って異動したメンバーの体験談や感想などをまとめたものが公開される予定ですので、そちらもぜひご覧ください! BASE組織とBASE BANK組織の違い まずはBASE BANKの組織構造や使用技術を簡単に紹介し、その上で、いくつか自分が関わってきたBASE組織と比べて感じた違いについてお話します ざっくりまとめると、BASE BANKの組織の特徴として主に以下のようなものが挙げられます👇 フルサイクルエンジニアを目指すという志向がより強い 関わる技術領域がより広く、Go / Pythonを使ったAPIの開発を行いながら、BASE側のPHP製アプリケーションとの連携やフロントエンドの対応も行っている チーム全体の人数がまだ少ないので、より全体感を持ってプロダクトの成長方法を考えながら、さらに打ち手を増やしていくための取り組みを行っている また技術的には、今後組織とプロダクトが大きくなってもスケールできるよう、以下のような仕組みで作られています 責務で分かれた複数のAPIを使った分散システム レイヤードアーキテクチャベースで整理されたバックエンドコード FE / BE間の連携を容易にするためにOpenAPIを使ったスキーマベースでのやり取り これらの仕組みは、1人で複数領域を担当する場合にはBASE側のリポジトリも含めて複数のリポジトリを開いたエディタ反復横とびすることが必要になることもあります(このあたりは絶賛練習中…) ここに関しては慣れが必要で大変なこともありますが、それでも依存関係やコードの責務が細かく分かれており、より見通しがしやすいという余りあるメリットがあります より詳しい内容についてはSpeaker DeckにあるBASE BANKチーム紹介資料とBASEチームのエンジニア向け会社紹介資料を見比べていただくと、もっとイメージしていただきやすくなるかと思いますのでそちらもぜひご覧ください! https://speakerdeck.com/base/basebank https://speakerdeck.com/base/for-engineers 異動後初タスクでAWS周りでハマったポイント ここからは、実際にBASE BANKチームに異動して1つ目のオンボーディングタスクで学んだこと、ハマったことについて書いていこうかと思います🕳🏃‍♂️💨 BANKチームに異動して新しい環境でコードを書き始めたところ、いくつかの問題に直面しました これらの問題をどのように解決したか、ケーススタディとして共有していきます オンボーディング時に開発用AWS環境を使用している場所があることに気づいた 自分の異動時には、昨日の @glassmonkey さんの記事( LocalStack/MinIO を導入して開発者体験が捗った話 - BASEプロダクトチームブログ )で紹介された minio / localstack 環境がすでに整っていました しかしながら、一部のGo製アプリケーションはまだそれを利用していない部分がありました これ自体は即座に問題が発生するものではありませんが、今後開発環境を新メンバーが構築するたびにAWSクレデンシャルを登録する必要があるため手間がかかってしまう状態になっていたため、オンボーディングタスクとしてDockerとアプリケーションの構成を修正してminio / localstackに対応することになりました ローカル環境での動作確認は問題なかったが、動作検証用環境へのデプロイで問題が発生した… BASE / BASE BANKには 「ローカル環境 / 動作検証用環境 / ステージング環境 / 本番環境」という4つのアプリケーション実行環境があり、今回はローカル環境での動作確認後、リリース前に動作検証用環境で問題なく動くことを確認しました とはいえ、動作検証用環境 自体はステージング環境ほど頻繁には使われておらず、基本的に各エンジニアのPCから直接 ecspresso のコマンドを実行する形でのデプロイが行われており、CIを介したデプロイは行われていませんでした(ステージング環境 / 本番環境へのデプロイはCIを介して行われます) しかし、 エンジニア全体に支給されるPCが去年から少しずつARMアーキテクチャ(M1系 Macbook)に切り替わった ことにより、ビルドをおこなう開発機とアプリケーション実行をするECSインスタンスのCPUアーキテクチャが異なってしまい、コンテナが起動しないという問題が発生するようになってしまいました(たまたま、このタイミングでこれに気づき、ハマってしまった形) さらに、その問題の確認中に、ECS側で何度もリビジョンを作成しようとしてしまっている(絶対に起動できないコンテナを起こそうとし続けてしまう)ことに気づかず、そのリビジョン内で同時に使用しているサービス監視ツールのコンテナをDocker Hubから取得する部分でレートリミットがかかるという問題も併発させてしまいました…😵‍💫 ECSのリビジョン設定を変更して問題を解決 対応としては、BANKチームで使用しているECSのタスク定義の設定ファイル( ecs-task-def.json )でデプロイ先のCPUアーキテクチャ設定を変更したり、本番用構成では使用しているログ用コンテナやサービス監視用コンテナを一時的に外したりして、まずは アプリケーションがECS上で単独で動作することを目的としてフォーカス しました その結果、アプリケーションの実装に問題はないことがわかったため、最終確認のためにステージング環境を使用し、最終的には問題なくBANKチームへの異動初のタスクを完了させることができました🎉 フルサイクルエンジニアへの第一歩が進みました このような経験を異動直後からすることができたので、これは自分もフルサイクルエンジニアへの第一歩を踏み出せたんだな…と噛み締めながら作業していました これまでのBASEチームでは主にフロントエンドの業務が中心であり、インフラ領域については仕様や実現したいことの相談メインで、実際の作業はSREチームにお願いするという形で進めることが多かったため、自分で手を動かせるようになったことは、とても学びがあり、非常に楽しかったです とはいえ、まだまだ勉強中ですので重大な問題などが発生しないようにしながら頑張っていきたいなと思います BASE BANKでは一緒にフルサイクルエンジニアリング目指して頑張っていく人も募集しています! 自分自身はBASE組織で2年半の間に身につけた知識を活用しながら、BASE BANKをさらに発展させていけるように頑張っていきたいと思います💪 とはいえ、BASE BANKを発展させていくためにはまだまだ人が足りていません… 先述の通り、BASE BANKチームにはスケーラブルな技術構成や組織構造の土壌があり、様々なプロダクトの改善を進めつつ、それらをより良くするための活動を行っているため、多くの学びがあってとっても面白いチームです ぜひ、カジュアル面談などにお越しください!お話しましょう! open.talentio.com https://twitter.com/gatchan0807 明日は10日目、 @x86_64 さんの記事です!お楽しみに!
アバター
はじめに この記事は BASEアドベントカレンダー 8日目の記事です。 LocalStack/minioを導入して開発者体験が捗った話 こんにちは、BASE 株式会社 BASE BANK Division でソフトウェアエンジニアをしています。 @glassmonkey こと永野です。 最近ではAWS上にVPCから環境を作っては、壊したりしています。 今回の記事では、AWSのサービスを使ったアプリケーション開発における、開発者自身のPCにおける開発、いわゆるローカル環境での開発者体験をあげた話をします。 YELL BANKについて 私が担当しているサービスであるYELL BANKは、BASEでショップを運営しているオーナー様に「気軽な資金調達」を提供するプロダクトになります。 yellbank-lp.thebase.com 現在のYELL BANKのアーキテクチャは以下のように複数のシステムコンポーネントが組み合わさることで実現しています。インフラにはAWSを利用しています。 BASE株式会社 BASE BANKチーム紹介資料 - Speaker Deck 特にYELL BANKは様々な計算を常日頃を内部的に行っており、それらの算出された結果が「どう推移した?」「異常は無いか?」を意識することがサービス運営上必要です。 そこで内部的な計算結果を定期的に記録することが求められており、そのためにAmazon SQSやS3を使って簡単なログ収集基盤を作っています。 実際の施策の一例にはなりますが、過去にショップカルテなるものを作ったので良かったらご覧ください。 devblog.thebase.in 最近のYELL BANKにおける開発環境の課題 YELL BANKでは前述のシステム実現のために、システムコンポーネントはAmazon SQSやS3に依存したものになります。 実際にAWS上に展開されたアプリケーションだと大きな問題はないですが、開発者の手元のPCのローカル環境だとどうでしょうか? 真っ先に考えられる方法としては、開発環境のAWS環境を用意しておき、そこで開発用のIAMを発行してアクセスキー・シークレットキーを扱うという方法が考えられるのではないでしょうか? 当初は開発メンバーの規模も大きくはなかったので問題にはなってはいませんでしたが、最近は体制も代わり開発メンバーが増えたこともあり、開発用のAWS環境を共有する点がボトルネックになることが増えてきました。 基本的にIaCしているものの、開発環境用のIAMはIaCの対象外にしていたこともあり、作業が煩雑でオンボーディングでのボトルネックにもなっていました。 また、セキュリティ的にもいたずらにIAMを増やす点は好ましくないはずです。 ローカル環境用のエミュレータを用意する チーム体制的にもローカル環境での開発がつらい状況になってきたので、ローカル環境でエミュータを立てる方向で対応しました。 今回ではLocalstackとminioを導入して解決を図りました。 LocalStackについて LocalStackとはローカルPC上でAWS上の各サービスを再現するエミュレータサービスです。 Lambda, SQS, S3, Dynamoといったメジャーどころは一通りカバーしています。 最近めでたくv3が出ましたが、多機能で高性能です。 www.localstack.cloud 使い方は簡単で以下をcompose.ymlなどに追加するだけです。 環境変数などの各種設定に関しての詳細は 公式ドキュメント をご確認ください。 localstack : image : localstack/localstack:3.0 healthcheck : test : [ "CMD-SHELL" , "curl http://localhost:4566/_localstack/health" ] interval : 2s start_period : 20s retries : 30 timeout : 30s ports : - "4566:4566" volumes : - localstack:/var/lib/localstack environment : - USE_SINGLE_REGION=1 - PERSISTENCE=1 - TZ=Asia/Tokyo これにより、各種コンテナからはlocalstack:4566(host上ではlocalhost:4566)に各種向き先を変えることでローカル環境上で各種AWS操作が可能になります。 例 aws s3 ls --endpoint localhost:4566 ただし、無料版と有料版でカバーしている機能に差があり、 そのなかでS3の永続化は無料版だと対応していないことがわかりました。 その他の各プランごとの機能のカバー範囲については公式の AWS Service Feature Coverage をご覧ください。 MinIO について YELL BANKのシステムは、S3上でデータを加工して利用者に提示するといった機能が多く、s3の永続化は死活問題といえました。 そこでLocalStackとは別にs3 api互換のminioを採用しました。 主な決め手はローカルスタックと同じようにendpointを変えるだけで気軽に立ち上げることができる点でした。 min.io MINIO_ROOT_USER は AWS_ACCESS_KEY_ID に、 MINIO_ROOT_PASSWORD は AWS_SECRET_ACCESS_KEYに対応します。 minio : image : minio/minio:latest healthcheck : # init スクリプトが完了する前に app が起動しないよう ヘルスチェックする test : [ "CMD-SHELL" , "curl http://localhost:9000/minio/health/liveh" ] interval : 2s start_period : 20s retries : 30 timeout : 30s command : [ 'server' , '/data' , '--console-address' , ':9001' ] ports : # 管理画面にアクセスできる - "9001:9001" - "9000:9000" environment : - MINIO_ROOT_USER=dummy-accesskey - MINIO_ROOT_PASSWORD=dummy-secretkey volumes : - ./services/infra/data/:/data ローカルで利用するときは以下のようなコマンドでs3 コマンドを各種利用することができます。 aws s3 ls --endpoirnt localhost:9000 また9001番ポートでも管理画面が用意されており、 IDには MINIO_ROOT_USER で設定した値、パスワードには MINIO_ROOT_PASSWORD を利用することで気軽に確認が可能です。 簡単にブラウズすることができ、アップロードされたファイルの内容も以下のように確認することができます。 変更後の影響 各種アプリケーション上で、AWSの処理を呼び出しを行っている点は変更対象になります。 基本的にはAPIコールのエンドポイントを、明示的に環境変数で設定することで実現しました。 たとえば Goのアプリケーションのaws sdk-v2の場合は以下のような形です。 client := s3.NewFromConfig(cfg, func (o *svc.Options) { o.BaseEndpoint = aws.String(os.Getenv("ENDPOINT")) } ) この変更に関してはオンボーディングタスクを兼ねて、新しくチームに異動してきた @gatchan0807 にやっていただきました。 詳細は明日の記事の「フルサイクルエンジニアリングの第一歩を進める - BASE BANKでの新たな挑戦」で紹介されるはずです。乞うご期待!! この対応で、当初の課題だったローカル環境の開発環境の悪さやオンボーディング時の複雑さの解消は無事に達成することができました。 最後に クラウド技術が便利になってきた昨今ですが、ローカルの開発者体験まで含めて技術選定することはなかなか難しいように思います。 今回のケースではエミュレータで頑張る方針取りましたが、IaCを頑張って個人ごとに環境をつくるという方法も取るといった他のアプローチも考えられはします。 皆さんはどういった方法を取っていらっしゃるでしょうか? 日々の開発者体験向上や、クラウドネイティブなアプリケーション作りにもっと邁進していきたいところですが、全然人が足りていない状況です。 よかったら @glassmonkey までDMやカジュアル面談含めてお待ちしています!! open.talentio.com
アバター
こんにちは! BASE 株式会社 BASE BANK Divisionでエンジニアのトップをやらせてもらっている @applepine1125 です。 12/3に Hatena-Blog-Workflows-Boilerplate に関する記事も書いたのでぜひ読んでみてください! さて、ここから本題です “夜な夜な.go”というイベントを始めました! 実は今年9月から、弁護士ドットコムさんと一緒に 夜な夜な.go というイベントを始めていました! ゆるく公式のドキュメントなどを一緒に読み、ドキュメントリーディングのハードルを下げたりみんなでワイワイ話すことでより理解を深めて行くためのイベントです。 現状は皆で読みたいドキュメントを募集して、皆で回し読む輪読会形式をとっています。 12/5時点で第4回まで開催しており、ちょうど Go 1.21で導入された新機能 slogの中で、メッセージやレベルなどの構造化されたログ情報を処理するHandlerの実装方法のドキュメントを読み終えたところです。 github.com なんで始めたの? Go Conference 2023 にて、BASEと弁護士ドットコムさんはスポンサーブースを出していました。 ブースに遊びに行った際に色々とお話をさせていただき、BASE、弁護士ドットコムさん双方ともPHPを使っていることや、新しい事業ではGoを使っていることなどで意気投合をし、ぜひ一緒になにかイベントをやりましょう!という話になりました。 テーマを決めるに当たり、自社アピールのためのLT会なども検討したのですが、どちらの会社もシンプルにGoを勉強する場を求めていました。 クローズドで合同の勉強会をやるのであれば、場をオープンにして色々な方と継続してGoに付いて知見を深めることができる場を作りたいと考え、輪読会形式で社外イベントとして開催しています。 夜な夜な.goで心がけていること 参加ハードルを下げる 輪読会と聞くと、ドキュメントを読みながら内容もきちんと即座に理解できないといけないのではないか? や、初対面の人達の中で色々読まないといけないの緊張する・・・など、かなりハードルが高く感じる方もいると思います。(正直自分もそう思ってます) そこで、夜な夜な.goでは参加ハードルを下げるために以下の2点を心がけています。 参加枠、盛り上げ枠に分ける スピーディに読み進めることよりもきちんと中身を理解することを心がける。寄り道や素人質問大歓迎 ドキュメントを回し読む参加枠だけでなく、見学しつつチャットで参加することもできる盛り上げ枠を用意することで、どんな感じか様子を見てみようかな?と参加しやすいようにしています。 また、ドキュメントによっては他のドキュメントへの参照やissueへのリンクなども多数あるため、ガンガン寄り道をして広く理解し、実際にThe Go Playgroundで動かしてみたりすることを心がけています。 途中参加しやすくする 一度ドキュメントを読み始めると、ドキュメントの量にもよりますがじっくり読んでいくため2, 3回分かかることもあります。 “それまでの内容を理解してないからその状態で途中参加してもなあ・・・” と参加を見送られてしまうのはもったいないので、回の最初にそれまでのおさらいをしたり、connpass上にてその会でどのドキュメントのどこから読むかを記載しておき、事前に予習ができるようにしておくことで、途中からの参加でもある程度共通の知識を持って会に臨んでもらえるように心がけています。 継続開催する こういったイベントは、何よりも継続して開催し続けることが何より重要だと考えています。 そのためにも、上に書いたように参加ハードルを下げたり途中参加しやすくすることで、一定人数参加者に集まってもらえるようにしています。 ファシリテートもBASE、弁護士ドットコムさんで交互に行ってファシリテートの経験を広く積めるようにしつつ特定のメンバーに負担がかからないようにしています。 夜な夜な.goのこれから 引き続き輪読会形式は続ける予定ですが、この形式もまだまだ実験的で色々な形式を試せればなと思っています。 来年も引き続き開催していくので、ぜひ盛り上げ枠から参加お待ちしています!(いきなり参加枠も大大歓迎です!!!!) 最後に BASE BANKでは、BASEのショップオーナーさんの資金繰りの課題解決を行うプロダクトをGoやPHPを使ってアウトプットしていきたい方や、夜な夜な.goのようなイベント運営にも興味のある方々を大募集中です! 求人から、自分のXのDMから、どういった形でご連絡いただいてもOKです。ぜひざっくばらんにお話しましょう~~~ open.talentio.com https://twitter.com/applepine1125 明日12/8は @glassmonkey さんのminio導入話です!ぜひお楽しみに!
アバター
はじめに Creative Time 1という、ショップオーナーがクリエイティブな活動にもっと充実した時間を作れるように、あれやこれやをしているチームでマネージャをしています、bonです。 このbonって名前は、子供の時は受け入れられませんでしたが、大人になって都落ちした元ボンボンなので、もうネタにして自ら名乗っています。 本記事は BASE アドベントカレンダー 2023 の6日目の記事です。前日のBASEアドベントカレンダー「 Notion導入について(前編) 」の続きとなります。前編が導入にあたっての技術的な困難をどう乗り越えたか、に対して、私の記事ではなぜ挑戦したのか、どこを目指しているのか、を記事にしています。 Notionと私 私自身、前職でNotionを使い始め、今では公私共に3年以上愛用しており、Notionのコミュニティやイベントにも参加しております。そのコミュニティ活動の中で、ベータテスターにもなったり、Notion Championsコミュニティにも参加していることから、Notion導入においてもリードしていった経緯があります。 Notionチャンピオンズコミュニティ Notion導入によって解決したかったこと 約1年前に私がBASEに入社し、その際に受けたオンボーディングで私が根本的に解決しなきゃな課題に気づくキッカケでした。 BASEでは、リモートワークも組み合わせたハイブリッドワークを導入しているため、得た知見をドキュメントや何かしらのテキストに落とし込む文化は根付いていました。しかし、これがGoogle DocsやらExcelやらGitHub wikiやらAsanaやらFigma/FigJam、Slackといった局所最適されたツールに散らばっていました。そのため、オンボーディングでも調べたらすごくわかるドキュメントに辿り着くことは出来ても、その調べかたを都度、誰かに聞いて回る状況でした。 これを解決したかった、です。 しかし当時から私はほぼ脳死で「ーーーーNotionッッッッ!!!」と唸るほど、Notionに脳を焼かれていたので冷静になるべく課題を整理してみました。 解決に向けての課題設定 私が困っていたことは、どこに欲しい知見があるか、だけでした。 どこかを探せば、ほぼ間違いなく欲しい知見がある状態でした。 しかし、どこを探せば欲しい知見に辿り着けるかの知見が、新参者の私には分からない状態でした。 探し当てた知見については内容が濃いものが多いものの、更新がされていないドキュメント類も多いなと感じました。もちろん、ここには更新をしていく類のドキュメントではないものも含まれています。 この状況を、SECIモデルで言うと、以下のような状況だと認識しました; 欲しい知見がどこかにある=共同化&表出化は出来ている 欲しい知見に辿りつきにくい=連結化が出来ていない 未更新ドキュメントが少くない=内面化がうまくいっていない テキストに残す文化はあるので内面化は、そのフェーズまで情報を届ければ循環していくと想定し、この中で一番解決すべきところは連結化にある、と絞りました。 出典:野中郁次郎(2002)「企業の知識ベース理論の構想」『組織科学』Vol.36 No.1 4-13 p.10 よって課題解決の初手には、連結化を改善していく事として定めました。 Notionとは 今更ですが、強調します。単なるノート・メモ帳の類ではありません。 Notionは、様々なツールを繋ぎ合わせるハブであり、ポータルであり、これらの役割を満たすことでSingle Source of Truthになるツールです。 BASEでは元々、Kibelaというドキュメントツールを使っていましたが、導入するにあたってNotionも、ドキュメントツールとして機能することから、ドキュメントツールをBASEではNotionに移行しました。これによって予算も捻出しました。(※1) そのため、結合化の課題の解決策としては、適切なアプローチだなと判断し、同期の凄腕なんでも屋さん(※2)に全体リードをしてもらいながら、社内でプロジェクトチームを立ち上げました。 ※1 同様の整理で、Asanaからもタスク管理ツールをNotionに移行完了しております。 ※2 職域問わず活躍している方なので言い表しにくく、bonのできる最大限ポジティブな表現を使用しています。 情報整理の狙い Notionを導入し、今ある知見が繋がる事で知見が1箇所に蓄積されていくことになりますが、これが最終地点ではありません。手に入った知見をもとにショップオーナーへ如何に早く、多く、大きな価値を届けるかを目指しています。 情報そのものを整理する ここまでで、知見と情報とを無断で使い分けてきました。これらは以下のように使い分けをしております。この使い分けは、田坂広志氏著書『 成長し続けるための77の言葉 』に記載されている定義を継承しています。 情報とは、一般的に手に入る知見であり、一言でいえばググると出てくる類のものと認識しています。一方で知見とは情報をベースに動いてみた結果のうち、言語化ができる部分のことを指しています。しかし当然ですが、知見を見聞きするだけでは得られない部分、言語化しきれない部分もあります。こここそが知見をベースに動いて得られる深い知見であり、経験と人間とからでしか体得できない智慧だと認識しており、これによってショップオーナーへの価値提供が早く、多く、大きくなると思っています。 イノベーション for owners’ success 智慧に繋がる経験を積み重ねていくためには元となる知見が、個人なら頭の中に、チームならどこか1箇所に集約されている必要があります。そのための手法は数多くあれど、少なくとも知見が散らばっている状態は、探し回る労力が途方もありません。何がどこにあるかがわかっている人でしか辿り着けない知見からは価値が生まれません。これを打破していければ、智慧を得るための行動を助長していくことになり、作りたい未来を作る土台になると信じ、今回私はNotion導入を積極的に進めていきました。無事に導入できたことによって、私たちが作り出した知見と知見とが繋がって智慧を蓄え、価値の交換が最適化された未来をともに実現していく基盤が出来ました。 We are hiring !! 突然、価値の交換が最適化された未来と言う言葉が出てきて『?』となった方、この下のリンク先を是非ご確認ください。アドベントカレンダーならではのお決まりですが、 We are hiring !! 採用情報 | BASE, Inc. - BASE, Inc. 最後に アドベントカレンダー 7日目はmatsuyukiさんとmatzzさんです。お楽しみに!
アバター
本記事は BASE アドベントカレンダー 2023 の6日目の記事です。 はじめに こんにちは。 Shop to Shop チームでマネージャーをしている髙嶋です。 本記事は昨日からの続編になりますので、前編については以下の記事を参照ください。 devblog.thebase.in さて、本日は開発チーム内で取り組んだ10個の取り組みのうち、後半5個についてご紹介させていただきます。 再掲すると、以下の No.6 以降を取り上げようと思います。 No. 取り組んだ内容 1 チームとしての出社日運用の廃止 2 雑談の活性化 3 No Slack Day(日単位での原則 Slack 禁止) 4 No Slack Time(時間単位での原則 Slack 禁止) 5 オンライン会議ツールを Google Meet からハドルミーティングに変更 6 タスク管理ツールを Notion から GitHub Projects に変更 7 スクラムイベント時に使用するツールを FigJam から GitHub に変更 8 スクラムイベント実施日を毎週水曜日から金曜日に変更 9 デイリースクラムを4日間から2日間に削減 10 GitHub Discussions の活用 本日も、まずはこの記事で伝えたい要点だけ先に列挙しておきます。 GitHub サイコー スクラムイベントも、前提を疑って守破離で言う破にチャレンジしていくことも大事 では続きに参りましょう。 ⑥タスク管理ツールを Notion から GitHub Projects に変更 こちらもまずはその背景から話を進める必要があります。 全社的に、それまで使っていたタスク管理ツールとしての Asana、加えてそれ以外のツールも含めて Notion に一本化しようという動きがありました。 その際、私たちのチームでも Asana でやっていたことを Notion で表現しようと試みました。 Notion は非常に便利で強力なツールであり、タスク管理という文脈においても一定再現することはできました。 しかしながらその変更のカジュアルさや機能性の高さが却って仇になる場面もあり、もう一度自分たちのやりたいことをフラットに考え直しました。 結局のところ、開発チームとしての成果物は GitHub 上に生み出されることが多く、であればその近くにタスクの管理場所も作るのがよいのではと考え、GitHub Projects を使ってみることにしました。 結論、これも今回の取り組みの中で評価が高かったものの一つになりました。 チームメンバーが抱えていた課題への打ち手としてハマった、そしてイテレーションでの開発を進めるうえで必要な機能が GitHub Projects に必要十分なレベルで備わっていたことがその理由になるかと思っています。 メンバーからの声としては以下のようなものがありました。 仕様の質問などがその機能開発のための Issue 上で行え、「あの話どこでしてたっけ」と迷子になることが減った エンジニアの成果物がコードであることが多いという前提もあり、Issue / PR / Discusssion と、GitHub 上で連携して一元管理できるメリットが大きい GitHub API を利用したチーム活動に関するデータ集計がしやすくなった 機能上、親タスクに子タスクをぶら下げるような階層は作れるが、同列に見えてしまったり親タスクの進捗がぱっと把握しづらかったりする 結果:4点満点中3.9点で継続 GitHub Projects でできることへの理解が進み、後続の取り組みへと繋がっていきます。 ⑦スクラムイベント時に使用するツールを FigJam から GitHub に変更 ここで言うスクラムイベントが何を指しているかでいうと、以下の4つになります。 スプリントレビュー レトロスペクティブ リファインメント スプリントプランニング それらを進行するための会場として、FigJam を使用する形でしばらく運用してきていました。 FigJam は各々の頭の中にあるものを発散するという意味では非常に優れたツールであり、各スクラムイベント自体はそれなりに活気のある状態ではあったと思います。 一方で、結局タスク化に際しては GitHub などツールをまたいだり、あるいは発散するのはよくてもそれを一つに収束させていくといったことが求められたり、冷静に見つめ直すとそのデメリットも浮かび上がってきました。 そうした状況を踏まえて、実は GitHub Projects である程度表現できるのではないかと考えて一度トライすることにしてみました。 結論、これも想像以上に上手くできた、という結果になりました。 FigJam と比較すると表現の幅に制約が生まれたのは確かですが、その制約があるからこそ最適な形を改めて考えることができたように思います。 メンバーからの声としては以下のようなものがありました。 レトロスペクティブで出たトライをそのままタスク化しやすいなど、話して発散だけして満足とならないのが良い 扱うツールが減ったことで運用しやすくなった スクラムイベント終了後も話途中や気になった議題などについては引き続き GitHub 上(GitHub Discussions 等)でやりとりできるようになり、そういう意味での時間的制約がなくなったと感じた 不便さはないが、スタンプなどで反応できないのは若干寂しい 結果:4点満点中3.4点で継続 慣れ親しんでいたスクラムイベントのやり方に大きくメスを入れたことで、他にも前提を疑ってトライできないかといった思考が強くなっていきます。 ⑧スクラムイベント実施日を毎週水曜日から金曜日に変更 これはある意味やったことはその通りでしかないのですが、一般的に連休にされやすい月曜日や金曜日にスクラムイベントを置くのはアンチパターンのように言われることも多いと思います。 ただそれまでのチーム状況を改めて振り返って水曜日と金曜日を比較したときに、特段の違いを感じられませんでした。 これは BASE における休暇の取りやすさということであったりも影響しているのではないかなと思います。 そうなると、週の終わりにスクラムイベントを置くということの分かりやすさ、リズムの作りやすさのメリットの方が大きくなるのではないかと考えました。 結論、これは良くも悪くも特に影響がありませんでした。 水曜日に実施していたことによるメリットは、実はそこまでなかったことが証明されたとも言えます。 メンバーからの声としては以下のようなものがありました。 スプリントが月曜日から開始するため気持ちが良く、そのまま木曜日まで連続するため振り返りもしやすい プロジェクト外の業務あるいは業務外のことも含めて、週のサイクルと揃って分かりやすくスケジュールもたてやすくなった 当初懸念していた「土日を挟むことで忘れてしまう」といったことも特に起きなかった 特に水曜日と比較したときの変化は感じなかった(ニュートラル) 結果:4点満点中3.5点で継続 がっつりスクラムイベントを実施する日だけではなく、デイリースクラムも見直せないかなと考えることになっていきます。 ⑨デイリースクラムを4日間から2日間に削減 スプリント期間としては1週間、つまり稼働日ベースでは5日間でまわしています。 そのうちの1日は上述したスクラムイベント実施日のコンテンツにマージされているような格好であるため、明確にデイリースクラムのみやっているのが元々4日間であったというのが前提となります。 ここの課題感としては、デイリースクラムを待って問題を話すという事象を極力なくしたかったのと、よりバックログを正しい状態に保つこと、作業時間を柔軟に確保することも併せて推し進めたかったということが挙げられます。 チームないしプロジェクトが発生してから一定期間は毎日やる意味は確かにありましたが、そこから時間がたって同じように必要かで言うと、もはやそういう状況ではないだろうと考えたのが出発点になります。 結論、2日間に削減したからといって業務が滞るわけではない、ただチームメンバーの顔を見る機会が少し減ったのは若干寂しいといった結果になりました。 メンバーからの声としては以下のようなものがありました。 以前は開催すること自体が目的のように思っていたが「なぜ開催するのか」を考えやすくなり、改めてその意義を見直すことができた 問題が発生したら、デイリースクラムを待たずにコミュニケーションをとるようになったと感じた 特に業務上の支障があるわけではないが、顔を見る機会が多少減ったことで他メンバーがどんな感じのテンションでいるのか気になる(根詰まってるのか、全然大丈夫なのかなど) 結果:4点満点中3.1点で継続 スクラム開発のフレームワークは認識しつつも、そこから外れていくこともポジティブな理由であれば、今のチーム状況を考えれば決して悪手ではないということが確認できました。 ⑩GitHub Discussions の活用 レトロスペクティブといったスクラムイベントに限らず、課題になる前のトピックについては FigJam や Slack 上でコミュニケーションをとるのではなく GitHub Discussions に集約してはどうかと考えました。 これは各々がより考えたうえで言語化するとともに、Slack で散見されるような「あれどこで会話したっけ?」といった事象の削減を促進したかったというのが背景にあります。 つまり特定のトピックに対するやり取りをフロー情報として扱って迷子にさせない、加えて GitHub の活用が推進されてきたという状況も相まって、積極的に GitHub で提供されている機能を使っていこうといったマインドになっていたということもあります。 結論、こちらも概ねポジティブで、とりわけそれまで GitHub 上でそこまで業務を進めるということが少なかった PdM やデザイナーのメンバーからも好評だったのが意外ではありました。 Slack がフロー情報で流れやすいというつらみを、上手く補完できたのが良かったのかなと思っています。 メンバーからの声としては以下のようなものがありました。 問題提起したけどそのままうやむやになるということがなくなり、会話が流れず非同期で確認できるので良い 一つの話題を深く議論したい際に、まさにピッタリなツール、使い方だと思った あとから話題を思い出す時に、どこで会話したかを探すのに苦労するということがなくなった GitHub Discussions から明確なトライに繋げる動きがまだまだできていない 結果:4点満点中3.6点で継続 自身の考えを適切に言語化してアウトプットする、それに対するフィードバックを重ねていくという点において有用なツールになりうることを確認できました。 まとめ なんとなくそうだろうと思っていたことも、実際に試してみると良い意味でちょっとギャップがあるものだなと改めて感じることができました。 とにかく失敗を恐れずにどんどんチャレンジしてみる、そしてそこから得た知見をネクストアクションとして繋げていくという空気を今後も醸成していきたいと考えています。 ただ本当にしたいのは、一つのチームに閉じて自分たちのやり方を磨いていくということではなく、チームをまたいでこういった知見を共有しあい、組織全体として開発手法や運用、あるいはその他様々なことをアップデートしていくことなのではないかなとも思っています。 明日は matzz さん、matsuyuki さんの記事です。お楽しみに! devblog.thebase.in
アバター
はじめに 本記事はBASE アドベントカレンダー 2023の5日目の記事です。 こんにちは!BASE株式会社でエンジニアをしている田中です。 Creative Time1 Groupに所属し、主にBASEのBackOffice領域の機能開発を担当しています。 BASEでは、社内でのドキュメント管理の課題解決に向けて、2023年2月頃からツールをKibelaからNotionに移行しようという検証が始まりました。 この記事では、どのようにしてKibelaの記事をNotionに移行したのかや、移行の際に困ったポイントを紹介していこうと思います。 Notion導入にあたっての詳しい経緯や課題などについては、12/6公開予定のbonさんによる「Notion導入について(後編)」にて紹介予定です。 Kibelaのエクスポート機能とNotionのインポート機能 Kibelaには記事の エクスポート機能 があり、記事および画像などのアップロード済みファイルをZip形式でダウンロードできます。 ダウンロードしたZip内には、attachmentsという記事の添付ファイルが含まれるフォルダ、notesというMarkdown (.md)形式の記事が含まれるフォルダが存在します。 エクスポートされたMarkdownには、以下のようにYAMLのヘッダー部分と記事本文の情報が入っています。 --- id: QmxvZy8x path: "/notes/1" author: "@ayako" contributors: - "@ayako" coediting: true folders: - 初期グループ / fuga groups: - 初期グループ published _ at: '2023-03-09 18:10:44 +0900' updated _ at: '2023-03-10 19:31:08 +0900' archived _ at: comments: - id: Q29tbWVudC8x path: "/notes/1#comment _ 1" author: "@ayako" published _ at: '2023-03-10 19:31:08 +0900' updated _ at: '2023-03-10 19:31:08 +0900' content: コメント --- # テスト記事 # タイトル ## 見出し1 hoge < img title = 'スクリーンショット' alt = 'スクリーンショット' src = '../attachments/2.png' width = "376" data -meta= '{"width":376,"height":364}' > Notionには現状Kibelaから直接インポートする機能はないため、Kibelaからエクスポートしたマークダウンファイルを、NotionのText & Markdownのインポート機能で取り込みます。 標準インポートでは取り込めなかった情報 ここまでの手順で記事本文自体のインポートは可能なのですが、標準インポート機能では取り込めなかった情報があります。 YAMLヘッダー部分に記載の情報 以下のような記事の詳細情報は標準インポートでは取り込めません。 著者・編集者 記事の所属グループ 記事の属するフォルダ 記事作成日 最終更新日 記事についていたコメントの内容 どうにか解決する方法はないか調べる中で、既にKibelaのNotion移行を行い、同じ事象で困ってツールを作成していた方を見つけました。 Kibela から Notion に約2万+件の記事を移行するために移行ツールを作った話 こちらの kibela-to-notion を利用し、YAMLヘッダー部分に記載の情報については問題なく取り込めました。 画像やPDFなどのファイル群 attachments/ 内にエクスポートされた記事内に存在したファイル群は、エクスポートした段階で <img src='../attachments/image.png'> のように相対パスになっており、そのままNotionにマークダウンをインポートしても画像を参照できず読み込みはできません。 記事執筆時点(2023/12/1現在)でも、Notion APIで画像のアップロード機能がないため、自分でどこかにアップロードする必要があります。 The Notion API does not yet support uploading files to Notion. Start building with the Notion API 先述のkibela-to-notionの記事でも画像問題は触れられており、S3やGoogleDriveを利用する方法もセキュリティ上の問題や、inline表示ができない点で懸念があり、何か他の方法はないかと模索しました。 検証を繰り返し、Notion上に画像をアップロードしてURLを発行し、APIでURLを取得して置き換える方法を取りました。 ファイルのアップロード Notion上にKibelaからファイルをアップロードするDBを作成し、Files&mediaのプロパティを追加し、空のページを作ります。そこにKibelaからエクスポートしたZIP内の attachements/ 内のファイルをアップロードしていきます。アップロードするファイル数はかなり多かったため、1000ファイルごと程に分割しアップロードしました。 アップロードしたファイルの取得 作成した空のページごとに、 ページプロパティを取得するNotion API を使用してファイルを取得し、ファイル名をkey、Notion上にアップロードしたファイルのURLをvalueにしてRedisに格納します。 相対パスのファイルURLの書き換え あとはRedisに格納したファイル名を元に、マークダウンファイル内の相対パスになっている箇所を書き換えます。ここの手順に関しては、 kibela-to-notion 内にそのまま利用できるロジックが存在していたため、利用しました。 ファイルURLを置き換えたマークダウンファイルをNotionにインポートすると、無事にファイルが読み込めており、ファイルが参照できない問題は解決しました。 その他苦労したこと Notionへの大量のファイルアップロードやインポート 今回どうしても必要だった作業として、相対パスの参照問題を解決するためのNotionへのファイルのアップロードと、マークダウンファイルのNotionへのインポートです。 数が数なだけにまとめてアップロードしたいのですが、同じファイル数や同じファイルの大きさだとしても、すぐ完了する場合もあれば途中で原因も分からず失敗してしまうことが何度もありました。そのためにはできるだけファイルを分割して小さい単位で作業を行うしかなかったため、社内の有志の方にご助力いただき、作業を分担し解決しました。 おわりに このNotion移行計画が2月ごろ始まり、計画・検証・作業含め完了したのが10月のため8ヶ月ほどかかりました。メインの業務とは別で行なっていたため時間もかかりましたが、社内の有志の方のご協力もあり無事に移行を終えることができ感謝しています。 アドベントカレンダー 6日目はtakashimaさんとbonさんです!お楽しみに!
アバター
本記事は BASE アドベントカレンダー 2023 の5日目の記事です。 はじめに こんにちは。 Shop to Shop チームでマネージャーをしている髙嶋です。 役割としてはエンジニアリングマネージャー(以下 EM)と言われるものを想像していただくとイメージしやすいかもしれません。 そんな私から、開発チーム内で取り組んだ10個の実験もとい取り組みについてご紹介させていただきます。 開発プロジェクトを遂行するチームの開発現場をスコープにした話になりますが、一つでも参考になるものがあれば幸いです。 ちなみにチーム構成としては PdM 1名、デザイナー1名、エンジニア5名、EM 1名(私)の総勢8名となります。 最後まで読むのが億劫になる可能性もあるので、この記事で伝えたいことだけ先に列挙しておきます。 出社(オフライン)とリモートワークの使い分けが難しいためにチームとしての活動はリモートワークに振り切ってみたが、それが自分たちの働き方を見つめ直すキッカケになった 雑談サイコー Slack は便利、ゆえに付き合い方や捉え方も様々で難しい では早速参りましょう。 取り組み一覧と評価方法 まず、何に取り組んだのかその一覧をご紹介します。 詳細が伺い知れないものもあるかと思いますが、後述するのでここでは割愛させてください。 本記事では No.5 まで、明日の記事でそれ以降を取り上げさせていただきます。 No. 取り組んだ内容 1 チームとしての出社日運用の廃止 2 雑談の活性化 3 No Slack Day(日単位での原則 Slack 禁止) 4 No Slack Time(時間単位での原則 Slack 禁止) 5 オンライン会議ツールを Google Meet からハドルミーティングに変更 6 タスク管理ツールを Notion から GitHub Projects に変更 7 スクラムイベント時に使用するツールを FigJam から GitHub に変更 8 スクラムイベント実施日を毎週水曜日から金曜日に変更 9 デイリースクラムを4日間から2日間に削減 10 GitHub Discussions の活用 そしてどう各取り組みを評価、つまり効果測定をしようかと考えたときに、定量的な形で表現できると一目瞭然なので望ましくはありました。 例えばベロシティや Pull Requests 関連の数値、あるいは稼働時間といったものを計測すること自体はそれほど難しくありません。 しかしそれらは様々な要因が絡み合って影響されうるものであるため各取り組みと結び付けて結論づけるのは難しく、なんとも言えない結果になるであろうことは容易に想像できました。 そのため今回はメンバーからの定性的な評価に重きを置くことにしました。 最終的には各取り組みに対する評価を以下の基準でつけ、なぜそう思うのかというコメントと併せて回答するアンケートを実施しました。 点数 意味 4 満足(続けたいし、現在の運用にも概ね満足している) 3 やや満足(続けたいが、一部運用は見直せないかと考えている) 2 やや不満(積極的にやめたいというわけではないが、少なくともあまり効果は感じていない) 1 不満(期待していた効果がないのでやめたい) さて、前置きが長くなってしまいましたが、ここから各取り組みについてその詳細をご紹介させていただきます。 ①チームとしての出社日運用の廃止 何をやったかの前に、まずは弊社としての前提、そしてチームとしての前提を先にお話しなければなりません。 弊社の働き方としてはハイブリッドであり、フルリモートではありません。 そのため会社としても出社を対面でのコミュニケーション機会として有効に活用していくことなどが推奨されています。 ただ、その出社頻度や活用方法については各チームに任されており、私たちのチームにおいては毎週のスクラムイベント実施日を出社日として定めました。 単にスクラムイベントを実施するというだけではなく、それ以外のオフラインイベントを同日に開催したり、あるいはチームメンバーとランチに行ったりするなど一定活用はできていたと思います。 結論、それをやめました。 誤解のないように補足しておくと、あくまでもチームとして定期的かつ同期的に出社することをしないだけで出社そのものを禁止するわけではありません。 各々の判断で出社したい日は自由に出社する形をとっていますし、あるいは互いに声がけしてスポットで出社して一緒に何らかの活動を行うといったケースもあります。 出社日運用を廃止した背景として、同一プロジェクトを遂行するチームメンバーであっても組織図上は別チーム所属の方がいる、また雇用形態等の事情により遠方で従事されている方もおり、出社することの足並みを完全には揃えられないといったことがありました。 そうなると結局はスクラムイベントもオンラインとオフライン混合の MTG であることに違いなく、その体験の違いもあって十分な効果を生み出せているとは言えない状況でした。 それならいっそのことリモートワークに振り切ってしまい、環境差異を極力なくそうという判断をしました。 メンバーからの声としては以下のようなものがありました。 スクラムイベントの日の出社をやめたからといって、特段困るようなことはなかった 前提として、メンバーの関係性を一定築けていたからできたというのはあったかもしれない 一緒にランチに行くなどして親交を深めていた側面もあり、そういう機会は減ってしまったのでたまにはオフラインで会う機会も作りたい 結果:4点満点中3.4点で継続 これが以降の取り組みの布石となっていきます。 ②雑談の活性化 チームとしての出社日運用を廃止したことで、業務外のコミュニケーション量が減ってしまい、そこから翻って業務にも悪影響を及ぼすのではないかという懸念がありました。 リモートワーク下において、雑談の大切さといったことも一般的に語られることが多いように感じます。 そこで仕事云々の前にまずはお互いをもっと理解することで心理的安全性を確保しようと、チームで雑談ができる random チャンネルを Slack 上に用意し、積極的にコミュニケーションをとってみることにトライしました。 元々チャンネル自体は存在していましたがそこまで活用されていなかったのと、「雑談しよう!」と突然言ってもできるものではないかなと思い、最初は私が中心となって話題を投稿することから始めてみました。 併せてカジュアルに発散したいもののアウトプット場所を Notion 上に用意し、それを当該チャンネルと連携することでさらなる活性化を図ることにしました。 結論、これが一番受けが良かったかもしれません。 正直やる前はどういう結果になるのか、どういった効果を生み出せるのかについて半信半疑でしたが、実際にやってみてその良さを感じ取ることができました。 当初は私が発信して始まることの多かった会話も、徐々にメンバーからトピックを提供する場面も多くなっていきました。 メンバーからの声としては以下のようなものがありました。 チームメンバーとの距離が近くなり、仕事中のちょっとした息抜きにできてよかった 以前よりチームメンバーのパーソナリティを知ることができ、雰囲気もよくなったように感じる 仕事に関係のない話をする場ができて良かったし、非同期で強制されるものでないことも併せて良かった 結果:4点満点中3.9点で継続 次は業務上のコミュニケーションの話へと繋がっていきます。 ③No Slack Day(日単位での原則 Slack 禁止) ここもまずは取り組み自体について話す前に、BASE 組織全体での前提について話しておく必要があります。 現在、毎週木曜日の午後は No MTG Day となっており、原則会議は非推奨となっています。 私たちのチームとしてもその効果は一定感じつつも、作業の分断という意味では決して会議だけではなく、Slack もそうではないかという仮説をたてました。 会議をしない分、それが Slack 上に移って効果が半減してやいないかといった仮説です。 そこで木曜日の No MTG Day に合わせる形で、極力チーム内での Slack コミュニケーションを抑えることに取り組んでみました。 結論、この取り組みはやや評価が分かれる形になりました。 Slack が業務のど真ん中にあるツールであること、人によってツールとの付き合い方にも微妙な違いがあること、あるいは働き方のリズムも違うことなどから一律のルールを設けることの難しさがありました。 メンバーからの声としては以下のようなものがありました。 木曜日が MTG や Slack を気にせず集中できる時間になったのがよかった 開発作業を一人で集中して行いやすくなり、そうできるよう前日のうちにそのための準備をするような動き方ができた Slack でとっていたコミュニケーションが、今度は GitHub の Issue 上などに移っただけのような印象を受けることがあった(目的が Slack を使わないことになってしまった) その日のうちにカジュアルに話しておきたいことはどうしても発生するのでモヤモヤが残った そもそも Slack の通知が作業の妨げになっているとは思っておらず、非同期コミュニケーションであることでその受け手が自分のタイミングでレスできる点にメリットがあるので、作業効率とはあまり相関がないように感じた 結果:4点満点中2.6点で廃止 本施策はやめることにしましたが、Slack との付き合い方についてはもう少しだけ粘ります。 ④No Slack Time(時間単位での原則 Slack 禁止) 日単位で禁止するのはちょっとつらみも大きいということであれば、時間単位ではどうかと考えました。 具体的には14時以降終日という形から始め、その後14〜17時に変更して検証しました。 結論、またまた評価が分かれることになりました。 結局本質的な課題感は変わらず存在し、一定メリットはあるものの、少なからずデメリットもあるといった状態です。 メンバーからの声としては以下のようなものがありました。 No Slack Dayより No Slack Time の方が1日の中で作業計画が立てやすかった 14時まで話していいが故にそのまま続きの会話をしたくなることがあり、No Slack Day よりも切り替えが難しく感じた タイムゾーンが違うわけではないがリモートワークだと個々人の行動の柔軟性は増すので、オフラインで同じ場所にいるといった形じゃないとチームとしてはワークしないかもしれない 結果:4点満点中2.4点で廃止 いったんこれをもって Slack の制限という形での取り組みには区切りをつけることにしましたが、最適解を探っていく試み自体は続けていく予定です。 ⑤オンライン会議ツールを Google Meet からハドルミーティングに変更 まずこちらも社内全体での傾向にはなりますが、オンライン会議ツールとしては Google Meet がメインで、私たちのチームも基本的にはそうでした。 しかしながら上述したように業務上コラボレーションする場としては Slack が最も多く、コンテキストに応じたチャンネルのハドルミーティングを使用することで会議参加をシームレスに実現できるとともに、参加者の分割や移動といったこともライトに行えるのではないかと考えました。 また、ハドルミーティングのスレッドなどに会話したことを議事録としてタイムリーに残せる(議事録を別ツールに探しに行くこともなくなる)ということも理由の一つです。 結論、この取り組みは概ね好評でした。 もちろんそれぞれのツールの良さはありつつも、会議に対する要求がハドルミーティングでも担保できると改めて理解できたことが大きかったのではと思います。 メンバーからの声としては以下のようなものがありました。 Slack でのテキストコミュニケーションからシームレスに口頭での会話に移行できる 会話の中で出たメモやリンクなどをスレッド内に残すことができ、後から検索することも容易である 会議の性質によってはカジュアルにリアクションを取りたいシーンがあり、Google Meet の方がその表現はしやすかった ブラウザの特定タブの画面共有や、開始時にデフォルトでビデオ ON にするといったことがハドルミーティングではできず、ちょっとした不便を感じた 結果:4点満点中3.5点で継続 まとめ 取り組みのご紹介という意味ではまだ折り返し地点ではありますが、一つひとつは小さくともトライを積み重ねることで、何か新しい取り組みを始めることに対するチームとしてのハードルが下がったように感じます。 メンバー構成的にも一定期間同じような顔ぶれであった、またその業務ないし開発スタイルについてもある程度固まってきていたことで、効率性はありつつも一種の停滞感のようなものも同時に感じていました。 別にめちゃめちゃ悪いということがあるわけでもなく、かと言ってすごくいいというわけでもなく、なんとも評価しづらいといった感覚でしょうか。 繰り返しにはなりますが、今回の取り組みを通じてその状況をいくばくか打破でき、今後の活路を見出す機会になったと捉えています。 明日は本記事の続編をお届けします。お楽しみに!
アバター