yasutomogのブログ

Software Engineerの雑記

躍動しているエンジニアリング組織への対峙(チームリーダーからEM、VPoEへの変遷)

背景

トライビート という会社で VPoE をやっています。2017年に入社して、今年で9年目になります。最初はチームリーダー的なポジションで入り、いまは Tech 事業部全体を見ています。会社全体でも100名未満とまだ小さい企業なので、役職や肩書きに固執せず、明るく楽しく、自由に動き回っています!

トライビートはクライアントワークを主軸とし、「顧客課題の探索から、提案、実行、運用までを伴走しながら解決する」ことを大切にしています。現在のTech事業部は、BizDev チームと4つの Engineer チームで構成して運営していますが、ここまで決して順風満帆だったわけではなく、組織やチームの浮き沈みも色々ありました。まだ改善ポイントは多いものの、いまは総じて良い状態になってきたなと感じています。

これまでの丸8年以上の間、より良いエンジニアリングチームをつくるにあたり、書籍や Web、Podcast などから多くの気づきを得て、ときには勇気をもらってきました。一方で、世の中に出ているナレッジは深く共感できるものが多い反面、「既存組織にどう適用するか」という点では難しさを感じることも少なくありませんでした。

システム開発において、何もないところから「0ベースで構築する」よりも、すでに稼働しているものに対して、「影響度や依存度などの制約を考慮しつつアップデートする」方が難しいと感じる感覚に近いと思います。組織やチームもそれと同じで、「生き物」だからこそ、正論や正攻法がそのまま通じるケースの方が少ないと感じています。

そんな中で、自分自身のリアルな経験を整理することで、誰かの一助になれば嬉しいですし、今後自分が迷ったときに読み返すための記録としても、ここにまとめておきたいと思います。

実施してきた内容

1on1の導入

いまでこそどこの会社でも当たり前にやっていますが、自分が始めたのは、まだ世間的に1on1が広まる前でした。日本語の書籍がようやく出始めた頃だった気がします。 最初から「制度として導入しよう!」という形ではなく、チームリーダーをしていたときに「もっとメンバーのことを知りたいな」と思って自然に始めていて、あとから「これ1on1って言うんだ」と知ったくらいです。

トライビート内で組織に定着してきたのは、ここ2〜3年です。いまは各チームで月1〜2回、1時間のところもあれば30分のところもあり、頻度や時間はチームに任せています。 また、「1on1をやること」を目的にしないようにしています。他のイベントや仕組みの中で十分にキャッチアップできているなら、それはそれでOKと考えていて、1on1はあくまで「手段のひとつ」という位置づけです。

1on1のテクニックやフレームワークは世の中に色々ありますが、自分が一番大事にしているのは話してくれたことをそのまま放置しないことです。そこで打ち明けてもらったことを、ちゃんとアクションに落とし込み、小さなことでも一歩ずつ動かすように意識しています。これを積み重ねることで、信頼関係も向上し対話の質も良い方向に変わっていくと考えています。

シャッフルランチの実施

シャッフルランチは、月1回・3〜4名のグループでランチを食べに行ってもらうイベントです。

トライビートにはTech事業部以外にも、プロモーションを主軸とする部署があります。プロモーションメンバーとTechメンバーがもっと自然に混ざり合うことで、会社としてさらにユニークになっていくと思っています。とはいえ、実際のプロジェクトは部署ごとに異なることが多く、普段の業務だけでは「人となり」を知る機会がほとんどありませんでした。ここを意識的に作らない限り、「本当の意味での融合は難しいよな」と感じていました。

そこで、ランチというリラックスした場で、お互いにゆるく知り合ってもらう目的でスタートしました。グループ作成は、あえてシステム化はせず、自分の方で毎月手動で決めていました。「このタイミングでこの人とこの人が話したら面白いかも」と想像しながら組み合わせていくのは、結構楽しかったです。当時は、社員数が今より少なかったので、そこまで負担にもなりませんでした。

グループごとにリーダーを1名決め、日程調整やお店の選択などをお願いする運用にしていました。リーダーを明確にすることで、「誰がオーナーシップを持つのか?」で迷わなくなり、全体の進行がスムーズになるように工夫していました。また、ランチに行ったあとは、簡単なものでいいので社内イントラサイトに投稿してもらうようにしていました。その場にいた人だけでなく、他のメンバーも自然と雰囲気を知れる 「ゆるい共有」を増やすことで、よりコミュニケーションが活性化されることを期待していました。

このイベントは、1〜2巡したタイミングで打ち切り、食事を交えたコミュニケーション施策は、別の形で引き続き運用しています。

ビアバッシュの実施

ビアバッシュは、月1回、業後の時間帯にピザやビールを囲みながらLT(Lightning Talk)を聞くイベントです。業後に開催ということもあり、参加への強制力は減らし任意参加としていました。

日頃の業務がクライアントワーク中心だと、意識的に「共有の場」を作らない限り、どうしてもナレッジがプロジェクト内に閉じがちです。機能要件はプロジェクトごとに固有のものが多いですが、非機能要件は横展開できる学びが多く、技術的アプローチや判断プロセスなどは他プロジェクトでも十分に役立ちます。そこで、プロジェクト横断のナレッジ共有を促進するためにビアバッシュを始めました。

ビアバッシュでは、技術的な内容だけに偏らず、提案資料の共有やプロジェクト紹介などもOKにし、プロモーションもTechも関係なく、みんなで広く情報交換できる場をつくることで共通認識の構築を心がけました。運営側としては、フードやドリンクの準備から、登壇者の調整、必要であれば資料の事前レビューまで、一通りサポートするようにしていました。

また、自己開示が得意な人ばかりではないので、「誰にスポットライトを当てるか?」はマネジメントとしてとても重要だと思っています。このイベントを運営する中で、結果的に「いま誰に光を当てるべきか」を常に考えるようになり、そのネタを意識的に探す習慣が身についたと感じています。こちらのイベントは本社オフィスが移転するタイミングで、スペースの兼ね合いから一度打ち切り、同様のことを目的としたイベントを別の形で運用しています。

勤怠連絡の通知

コロナ禍以前から、トライビートではリモートワークを許容していました。当時はいまほどリモートワークが一般的ではなかったですが、「先んじていた」というより、どちらかというと成り行きで自然とそうなっていた感じです。また、リモートワークをするうえで必要な仕組みや規範をきちんと設計できていたかというと、正直NOでした。その結果、ちょっとした勘違いや情報の行き違いから、無駄なハレーションが発生することもしばしばありました。

そこで、当時みんなが慣習的に入れていた Google カレンダーの予定を取得し、その日の勤務状況をSlackで自動アナウンスする仕組みを作りました。構築時のポイントは、新しいルールや制約を強いるのではなく、既存の習慣にシステムの方を寄せたことです。これにより、追加の手間をかけずにスムーズに運用でき、ストレスの少ない形でワークスタイルの可視化ができるようになりました。

Slackのチャンネル設計

マネジメントをするうえで、「場」の設計と「流れ」の設計はすごく重要だと考えています。「場」は、どこに視点を合わせるべきかを示し、「流れ」は、どう進んでほしいかを示すものです。Slack のチャンネルは、この「場」の設計とほぼ同義だと思っています。

以前はプロジェクト固有のチャンネルが大半でしたが、いまはプロジェクトを横断して知見を共有できるチャンネルや、雑談を通して「人となり」を知れるチャンネルなど、さまざまな場を用意しています。

もちろん、すべてのチャンネルが常に活性化しているわけではありません。でも、「思い立ったときに自己開示しやすい場」があること自体が大事だと思っていて、そのための下地づくりを意識しています。

社内Podcast配信

個人的に、昔から Podcast が大好きでよく聞いていました。当時、Tech業界向けのコンテンツはいくつかありましたが、いまほど普及していなかった気がします。

ちょうどその頃、1on1にも自分が慣れてきていて、クローズドな場でメンバーの「まだ知られていなそうな良いところ」に触れることが増えていました。ただ、それを自分の中だけに留めておくことに、どこかモヤっとした気持ちがありました。そんな背景もあって、「社内限定で聞ける形なら障壁も低くすぐ始めれそう」と思い、勢いで社内Podcastを始めることにしました。

ビアバッシュと同じように、ここでも「誰にスポットライトを当てるか?」を意識し、毎回社内から一人ゲストを招いて二人語り形式で進めていました。内容は、ゲストの生い立ちやこれまでの職歴、プロジェクトの話、最近気になっている技術など、さまざまで特に決めていないです。その人に合わせて大まかな show note だけ作り、あとはその場のノリで話していく感じです。なるべくゲストの「価値観」に触れられるような内容になるよう心がけていました。

コンテンツを作ったら社内全員にアナウンスはするものの、聞くことは強制しないようにしていました。直接「聞いてください」と押し付けるのではなく、自然と興味を持ってもらい、自発的にコンテンツにたどり着いてくれるような文化を作りたいと考えていました。個人的にすごく嬉しかったのは、あまり聞いていないだろうと思っていた管理部の方から「次の配信まだですか?楽しみにしてるんです」と声をかけてもらったことです。

しばらく配信は止めていましたが、2025年度になって、EMメンバーから「昔やっていたあのPodcast、またやってみたいです」と声が上がり、いまはシーズン2が始まっています。また、社内でクローズドな音声コンテンツを聞くための、iOS/Androidアプリも作っていて、よりアクセスしやすい環境を整えているところです。

チーム編成の再設計

Tech事業部のメンバーが増えてきたタイミングで、チーム構成を見直すことになり、この責務を任せてもらいました。

以前のチームは、ジュニアからシニアまでバランスよく分かれるように、ざっくり2〜3チームで構成されていました。ただ、この構成だと、技術的な専門性が深まりづらく、チームとしての「特徴」も曖昧になりがちでした。そこで、チームにもっと分かりやすい役割を持たせる方向にシフトしました。

編成を考えるときに意識していたのは、「現状の機能を大きく損なわないこと」と、「よりスケールしやすい形にすること」です。これはソフトウェアのリファクタリングに近い発想だと思っています。結果として、「BizDev、Frontend Engineer、Backend Engineer、Cloud Engineer」の4つの職能に分けてチームを構成しました。配属については会社が決めるのではなく、本人の興味関心が強い領域を、自分の意思で選択してもらう方針にしました。現在は更に人が増え「App Engineer」も加わり、計5チーム構成になっています。

チーム編成は簡単に変えていいものではないと思っていますが、一方で、メンバーの成長(内部環境)や、顧客からの期待値(外部環境)に応じて、柔軟に変えていく必要もあります。今のチーム構成にしてから数年が経ち、これまでの間は環境の変化にも適応できていましたが、最近は次のフェーズが見え始めています。そこで、いまEM陣で再度チーム編成の再設計に取り組み始めているところです。

Tech Purpose と Tech Valuesの策定

組織編成を行ったあと、改めて「自分たちはどの方向を見て進んでいくべきか?」「どんな行動を価値とみなすのか?」という目線合わせが必要だと感じ、Tech事業部としての存在意義(Purpose)と行動指針(Values)を言語化しました。

このタイミングでは、これまでの取り組みを通じてメンバーの価値観の解像度が上がってきていたので、0ベースでトップダウンに作るのではなく、ボトムアップで考えることにしました。普段の会話の中で頻繁に出てくる言葉や、大事にしている振る舞いがいくつもあり、「暗黙的な共通価値観はすでに芽生えているな」と感じていました。

その「暗黙知」を丁寧にすくい上げ、言語化し、さらに抽象度を適度に高めながら整理していきました。いまの組織に十分浸透しているかと言われると、まだまだ改善ポイントはあります。ただ、絵に描いた餅にしないために、賞与評価の判断軸にも取り入れるなど、日々の活動に馴染むような工夫を続けています。

ジョブディスクリプションの策定

全社的にジョブディスクリプションを整備する流れがあり、Tech事業部については CTO とマネージャー陣で内容を検討していきました。

大きくは 5つの軸(顧客との対峙、チーム・社内との対峙、プロジェクトとの対峙、自分自身との対峙、技術との対峙) に分けて期待値を定義し、さらに BizDev と Engineer では記載内容を分ける形にしました。

抽象度はかなり悩んだポイントです。抽象的すぎると言葉遊びになってしまうし、具体的に寄せすぎると運用しづらいです。ちょうどいい落とし所の判断はなかなか難しく、結構な時間を掛けた記憶があります。策定後は大きな更新をしていませんが、環境の変化に適応できているかを毎年見直すようにしています。チーム編成と同じく、変化に追従できなくなったタイミングがアップデートの合図だと思っています。

tri catch(Tech事業部の組織学習イベント)の実施

ビアバッシュを運用している中で、「業務時間内で実施してくれると参加しやすい(本当は参加したいが、時間外だと参加しにくい)」というフィードバックをメンバーからもらい、運用の見直しを行いました。

そこで、月末最終金曜の業務時間内に実施する形へ変更し、これまで任意参加だったものを、事業部全体の同期を取るためのイベントとして原則参加にしました。必須参加にしたことで、LTだけでなく、チームビルディングを目的としたワークショップなど、内容のバリエーションも広げられるようになりました。

また、業後イベントではなくなったので飲食の提供は廃止しています。ビアバッシュという名前も合わなくなったので、各メンバーから新しいイベント名を募集しました。結果、「tri catch」(プログラミングの try-catch と社名を掛け合わせた造語)に決まりました。間接的にも決断に関わってもらうことで、主体性は生まれやすくなると感じています。

自分の中で、「新しいことを始めるときは、自分がオーナーシップを持ち、企画だけでなくオペレーションや実行まで責任を持ってやる」というポリシーを大事にしています。これは、「場」の設計だけでなく「流れ」を作るところまでやって初めて機能する、と考えているからです。ビアバッシュは自分一人で回していましたが、「場」と「流れ」は十分にできていたので、このタイミングで運用チームを作り、組織として回る流れに移行しました。いまは完全に手離れして自分がやっていたときよりもスムーズに運用が回るようになっています。

GitHubの利用

プロジェクトのソース管理は、お客様の都合に合わせて進めることが多く、当初は自社で管理するケースは多くありませんでした。ただ、プロジェクト横断で技術的な情報交換が活性化してきたことや、社内オペレーションをシステム化する流れが進む中で、自社内で共有できる場所が必要だと感じ、GitHub を契約しました。

自己研鑽や普段の学びから何かアウトプットしようとしたときに、共有しづらい環境であることは、技術を強みにしている会社にとって大きな機会損失だと思っています。正直、もっと早めに準備しておけばよかったと感じています。ただ、この流れがあったおかげで、GitHub Copilot については日本の中小企業としてはかなり早い段階で導入できたと感じています。

トラブル共有の運用見直し

トライビートでは、サービスやアプリケーションを納品(リリース)して終わりではなく、その後の運用にも携わり、一緒に改善を進めていくプロジェクトが多いです。運用の中では、急な障害や想定外のトラブルが発生することもあります。都度、迅速な対応を心がけていますが、こうした現場の知見は書籍や Web からは得づらく、非常に価値が高いものが多いです。より強いチームを目指すために、メンバーからポストモーテムの文化が生まれました。

また、トラブルが発生した際には、専用の Slack チャンネルで共有する運用も行っています。プロジェクトに直接関わっていないメンバーにも状況を共有することで、急遽サポートをお願いできるようにするためです。ただ、責任感が強いメンバーほど、オープンな場でトラブルを共有することに心理的なハードルを感じる傾向がありますし、トラブル対応中は目の前の対処に意識が向くため、共有そのものが後回しになりがちです。

そこで、Slack 共有の心理的障壁をできるだけ下げるために、まずチャンネル名を見直しました。元々は「障害報告」という名称でしたが、「トラブル共有」に変更し、責めるニュアンスを排除して知見共有の意味合いを強めました。さらに、Slack のワークフローを活用し、フォーム入力を行うだけで Notion にポストモーテム記事が自動生成され、その内容が Slack に共有される仕組みを作っています。トラブル対応時は余裕がないことが多いため、自由記述よりも構造化された入力の方がまとめやすく、心理的負荷も下がると考えています。

ポストモーテムの記事が作成されると、EM が内容を確認し、必要に応じて Tech 事業部全体で共有会を開く運用になっています。組織全体で学びに変えていくための「流れ」を整えることを心がけています。

賞与評価のフォーマットの見直し

賞与の判断に関わるようになったタイミングで、評価に使うフォーマットの見直しを進めました。それまでは会社共通のフォーマットを使っていましたが、評価は「決めること」だけでなく、その後のフィードバックまで含めて意味があると感じていました。そこで、自分が必要なインプットをきちんと集められる形に作り替えました。

給与はジョブディスクリプションに紐づいていますが、賞与はその年度の貢献度合いに直結する設計にしています。だからこそ、判断軸が曖昧にならないようにすることを意識しています。このフォーマットも一度作って終わりではなく、毎年見直しを行いながら、少しずつアップデートし続けて運用しています。

採用フローの見直し

採用フローについても、状況に合わせて随時見直しながら改善を進めています。当初は、自分と CTO が面接を担当していましたが、現在はチームリーダーやメンバーとも話す機会を増やすことで、入社後のギャップをできるだけ減らすようにしました。これは、メンバーやリーダーが、社名を主語で話す機会が増えるため、自然と視座が上がる副次的な効果もあると感じています。

現在はハイブリッドワークのため、2次面接から来社いただき、オフィスの雰囲気や、実際に働いている現場の空気感も感じてもらえるように意識しています。また、採用資料についても、最初は営業向けの会社資料をベースに説明していましたが、現在は Tech 事業部の内情が伝わるよう、解像度を上げた資料を用意しています。

今年までの採用プロセスは面談中心のフローでしたが、現在は「BizDev 用・プログラマ用・インフラ用」の3種類のテストを用意しています。テストを通して、スキルを確認するだけでなく、「実業務ではどんなことを期待しているのか」を事前に伝えられるように工夫しています。

オウンドメディアの構築

こちらは、正直に言うとうまくいかなかったアクションです。外向きの発信の場として、オウンドメディアを構築しました。

技術的な発信を通じて、社内外に良い刺激を与えられたらと思っていましたが、結果として運用をうまく軌道に乗せることができませんでした。「場」を用意するところまではできたものの、「流れ」を作りきれなかったパターンだったと思っています。

オウンドメディアという形にこだわる必要はないので、外向きの発信については、また別の形でナイストライできるタイミングを探したいと思っています。

OKRの実施

こちらも、正直なところ、なかなか運用に乗せきれず中途半端になってしまった取り組みです。OKR の思想自体にはとても共感していて、MBO と比べてもよくできたフレームワークだと感じています。ただ、実際に運用してみると、定性目標の匙加減に難しさを感じています。

実業務より少し「飛び地」を狙った目標を置いてみるものの、トライビートはクライアントワークが主軸です。どうしても日々のプロジェクトへの貢献が重視されるため、OKR への意識は相対的に下がりがちでした。何度かトライはしてきましたが、結果として「これだ」と納得できるところまでは至っていません。

現在は、考え方としては OKR に近いものの、別の手法を試しているところです。外部の事例をそのまま当てはめるのではなく、自分たちの組織風土に合った形は何かを、引き続き模索していこうと思っています。

プロジェクト実施計画と予実管理のシステム化

プロジェクト発足時の運用と、進行中の予実管理についてシステム化を行いました。それまでは、プロジェクト立ち上げ時にExcelベースの計画書は存在していたものの、記載粒度はバラバラで、「作ること」が目的化してしまっていました。最新版の計画が各PMのローカルに散らばっている、という状態でもあり、正直ずさんな管理だったと思います。

メンバー数もプロジェクト数も増えていく中で、慣習的な運用だけでは回しきれなくなり、プロジェクト実施計画のフォーマットを見直しました。Excelからスプレッドシートに移行し、誰でも各プロジェクトの最新版を参照できる状態にしています。また、それまでは予定工数のみを入力する形式でしたが、勤怠システムと連動させて実績工数を自動で反映するようにし、予実を比較して確認できるようにしました。

プロジェクトは、人やチームと同じく「生き物」だと思っています。だからこそ、変化する前提で設計し、変化に気づける状態を作ることが大事です。そのために、リアルタイムでプロジェクト全体を俯瞰できるような仕組みを意識して工夫しています。その後も、計画が変わる前提でバックアップを取る仕組みや、複数プロジェクトを横断して見られる仕組みなどを追加し、自然と計画をふりかえる流れになるよう改善を続けています。

あわせて、形骸化していたレビューフローも見直しました。レビューのワークフローや判断基準を言語化することで、属人性を下げ、より仕組みとして回しやすい環境を整えているところです。

Notionの導入

Notionの導入には、表向きの目的と、裏の目的がありました。

表向きの目的は、クライアントワークの性質上、知見がプロジェクト内に閉じてしまいがちで、情報のサイロ化が進みやすい構造課題へのアプローチです。事業部としては、プロジェクトを横断したサポートを評価している一方で、それがやりにくい環境になっていることに課題感がありました。そこで、プロジェクト情報に限らず、事業部の情報を一元管理する場としてNotionを導入しました。Notionに慣れているメンバーばかりではなかったので、有識者で運営チームを作り、まずは全体のページ設計を整えました。そのうえで全員を招待し、困ったことがあればすぐに聞ける流れを作るようにしました。

裏の目的は 心理的安全性の向上でした。当時は売上は伸びているものの、支出も大きく、利益が薄い状況で、数値管理やルールの見直しが避けられないタイミングでした。組織としても変化が求められていて、個々人の自由を最優先するフェーズから、同じ方向を向きながら多様であることを大切にするマインドへシフトする時期だったと思っています。

Notionの導入自体は、個人的に使っていた数名のメンバーから「会社全体でも使いたい」という声を上げてくれたのがきっかけです。ただ正直なところ、「会社に言っても却下されるだろう」という空気感があったのも覚えています。そのネガティブな同調圧力を壊したく、裏で根回しすることは極力せず、真正面から稟議を通すことを決めて進めました。

稟議にどういう内容を書いたのか、いまどのステータスにあるのか、といった進行状況もメンバーに共有しながら進めました。こういう流れで進めればいいんだ、という実例を見せたかったのと、「否定されるかもしれない」「相手の反応が怖い」という理由で行動せず、裏で腐って愚痴ってしまうノリはダサい、という価値観を間接的に伝えたかったのもあります。トライビートでは、お客様と直接対話しながらプロジェクトを進めることが多く、プロアクティブな行動が期待されています。結果が見えている提案だけではなく、どうやって理解してもらうかを考え抜く姿勢が求められます。今回の取り組みは、そこへの気づきも促したかった、という側面もありました。

もちろん、自分自身も提案が却下されると凹みますし、不安で一歩踏み出しづらい気持ちもよく分かります。ただ今回のように、「Notion利用の稟議」という行為を、「組織の心理的安全性を高めるためのチャレンジ」として捉え直すと、不思議と勇気が湧いてきます。別な大義名分を付けて自分自身を鼓舞するのは、おすすめの思考方法です。

tb-wiki(社内wiki)の運用

Notionを導入したタイミングで、プロジェクト横断的な文化をより促進したいと考え、知見を共有・蓄積する場所として tb-wiki(社内wiki)を作りました。

内容については特に制限を設けず、技術的な知見、プロジェクト進行に関する学び、システム設計の話、勉強会やイベントの参加レポート、ポエムまで、何でも書いてもらうようにしています。これまでは Slack 上で流れていってしまっていた有益な情報を、ちゃんとストックできるようにしたかった、というのが一番の狙いです。

運用としては、wikiにページを追加すると自動でSlack投稿される流れにしています。また、「いいね!」ボタンを設置し、ボタンが押されると同じくSlackに通知が飛ぶ仕組みにしました。誰がどの記事に共感しているのかが見えることで、「書いて終わり」にならず、次の記事作成につながることを期待しています。

さらに Notion 上では、毎月の記事投稿数や、メンバー別の記事投稿数などをチャートで可視化しています。ちょっとした「遊び」の要素を入れることで、堅くなりすぎない運用を意識しています。オウンドメディアはうまくいきませんでしたが、こちらは少しずつ知見が溜まってきています。どこかのタイミングで、外向けにも公開できたらいいな、と思っています。

出社とリモートの勤務スタイル

コロナ禍以前、基本的には出社している企業が多かった時代から、トライビートではリモートワークも取り入れていました。といっても、明確なルールを設けていたわけではなく、どちらかというと慣習的に作られた運用でした。

コロナ禍に入り、周囲の企業が一斉にフルリモートへ移行した際も、働き方そのものに大きな違和感はありませんでした。その後、一度コロナが落ち着いたタイミングで、まだ多くの企業がリモートワークを継続している中、トライビートでは原則出社(理由があれば個別にリモートワークを許可)に移行する判断をしました。

さらにその後、周囲の企業に出社回帰の流れが出てきたタイミングで、現在のハイブリッドワークへと切り替えています。結果だけ見ると、タイミングごとに逆張りのような運用になっています。

どの会社でも、勤務スタイルについてはメンバーの関心が非常に高いテーマだと感じています。トライビートでも、原則出社に切り替えた際は、想定通りではありましたが、強い反発やハレーションが生じました。当時は、顧客とより近い距離(商流)で業務を進めることが多くなり、不確実性の高いプロジェクトが増えていた時期でした。また、採用も中途即戦力だけでなく、ジュニア層の獲得を進めていくフェーズに入っていました。こうした外部環境と内部環境の変化を踏まえ、CTOと自分で決断したものです。

反発やハレーションが起きることは織り込み済みだったため、判断の背景をできるだけ言語化し、一方的にアナウンスするのではなく、リーダー陣への説明やフィードバックの機会を複数回設けるなど、丁寧に進めることを意識しました。それでも、このタイミングで退職を選んだ方が一定数いたのも事実で、組織を運営する立場としては、ハードシングスな時間でした。今となっては正しい判断だったと自信を持っていますが、当時は世間の流れとは逆行し、リーダーやメンバーからもネガティブなフィードバックを受けていたため、「自分の設計は間違っていないのか」と何度も考え直した記憶があります。正しかったかどうかよりも、「正しい判断だったものにする」と腹を括り、ビジネスを流れに乗せる覚悟が決まった結果、うまくいったのかもしれません。

この決断には、副次的な効果もあったと感じています。「出社が良い、リモートでもできる」といった議論はいまもネット上でよく見かけますが、トライビートでは他社の価値観は特に気にしていません。あくまで、自分たちの外部環境と内部環境をどう捉えるかを軸に判断するようにしています。その芯の通った姿勢を、行動として示す良い機会だったと考えています。

数値の可視化

今期の売上や発注の実績・見込に加え、顧客別・発注先別の数値、プロジェクトごとの数値、直近四期分の推移、社内メンバーの稼働状況などを、自分の方で複数の社内システムからデータをマッピングし、チャート化して参照できるようにしています。

メンバーが「もう一段、視座を上げて考えたい」と思ったときに、必要なデータへすぐにアクセスできる状態を作っておくことが大事だと思ってます。それによって、情報格差や情報の非対称性をできるだけ減らすことを意識しています。

年度予算と事業計画

毎年の予算については、CTO や営業チームと連携しながら、自分の方で計画するようにしています。当初は、ざっくりとした売上予算を立てる程度でしたが、現在は発注予算、採用予算、SaaSやAI 利用の予算など、年々解像度を上げた検討を進めるようにしています。

決めた予算については、結果だけを一方的にアナウンスするのではなく、マネージャーやリーダーを通じて、メンバーにも「なぜこの数字なのか」という裏付けをできるだけ説明するようにしています。漠然とした数字のままではなく、意味づけを行うことで、より協力的な関係を作ることを期待しています。

Tech Handbook

原則出社からハイブリッドワークへ切り替えるタイミングで、組織としての目線を揃えるために Tech Handbook を作りました。GitLab の Handbook 運用を知り、「うちにも欲しいな」と思ったのがきっかけです。

Tech Handbookでは、テーマごとに「背景と目的」と「行動指針」をセットでまとめています。部分的に細かいルールを定めているところもありますが、ルールで縛ることが目的ではありません。外部環境や内部環境についての共通認識を揃えたうえで、「その結果、どういう振る舞いや行動がトライビートとして “かっこいい” のか」を、自分たちで考えられる状態を作りたくて整備しています。

現在は、以下のようなテーマを扱っています。

  • 出社と在宅
  • 勤務状況
  • プロジェクト進行
  • コミュニケーションの心構え
  • Slack
  • オンライン通話
  • スケジュール調整
  • ソースレビュー
  • 職能チームビルディング
  • プロジェクトやチームの越境
  • トラブル共有
  • ブリリアントジャーク
  • セキュリティ

アサイン予定と見込プロジェクトの可視化

アサインについては、EM 陣で最終的な判断をするようにしています。社員数やプロジェクト数が増えていく中で、感覚だけでは見通しを立てづらくなってきたため、各プロジェクト実施計画からデータを抽出し、今後の予定が一覧で把握できる形にしました。

最終判断はEM陣で行いますが、メンバー個々人の希望や声については、リーダー陣を通じて吸い上げ、判断材料として取り入れるようにしています。全員の希望を同時に叶えることは難しいのですが、トップダウンで一方的に決めるのではなく、できるだけ情報を揃えたうえで決めることを意識しています。

また、メンバーから「見込案件も含めて分かるようにしてほしい」という要望があり、まだアサインが決まっていないプロジェクトについても、誰でも参照できる状態にしています。先の見通しが少しでも見えることで、心の準備ができたり、自分の次の動きを考えやすくなればいいな、という狙いです。メンバーからこういう声が上がるのは非常に嬉しいことで、優先度上げてなるべく早めに対応することも心がけています。

四半期ごとのふりかえり

Tech事業部は、職能ごとに分けた5つのチームで構成されています。プロジェクトが立ち上がる際には、この5つの職能チームから担当者をアサインし、プロジェクト専任のチームを組んで進めています。

日々の業務はどうしてもプロジェクト側の比重が高くなるため、意識的に場を設けないと、職能チームとしての一体感は徐々に薄れていきます。各職能チームの定例運営についてはリーダーに任せていますが、四半期に一度のふりかえりについては、自分から各リーダーに実施をお願いするようにしています。

ふりかえりのあとには、可能であれば会食も行ってもらうようにしています。業務の延長ではなく、少し力を抜いた場を作ることで、「人となり」を知るきっかけを増やしたい、という意図です。

また、ふりかえりにはさまざまなフレームワークがありますが、よく使うものはテンプレートとしてまとめ、実施コストをできるだけ下げるようにしています。続けてもらうためには、準備のハードルを下げることも大事だと考えています。

オンボーディング

小さい組織で、かつリアルで会う頻度が高ければ、そこまで大きなストレスなく組織やチームに馴染めると思います。ただ、トライビートは徐々に人数が増え、ハイブリッドワークを選択していることもあり、これまでノリでやっていた受け入れ方法を、少しずつ見直してきました。

入社初日は出社してもらうようにしています。初出社時のランチタイムは、すでに出来上がっている関係の中に入ることで緊張されるだろうなと想像して、予めチームリーダーが Welcome ランチを設定するようにしています。管理部からは会社全体の一般的なルールを説明する時間を設けていますが、それとは別に、Tech 事業部内のルールや慣習、内情については、自分の方から Welcome MTG を行っています。ただ、Welcome MTG だけでは時間も限られていますし、普段の細かい運用まですべてを伝えきることはできません。また、初日に情報を詰め込みすぎても覚えきれないと思っています。そのため、EM やチームリーダーから、後日あらためて複数回に分けてキャッチアップしてもらう時間を設けるよう、運用を変えてきているところです。

Tech 事業部としてのオンボーディングは、少しずつ体系化され整ってきましたが、プロジェクト単位のオンボーディングについては、まだプロジェクト固有になっている部分が多く、改善の余地があります。特に、プロジェクト全体背景や、技術選定の理由など、ベースとなるコンテキストを伝えることの難しさを感じていて、ADR(Architecture Decision Record)の作成を試みてますが、今後さらに強化していくつもりです。

AI利用

AIの利用についても、積極的に活用していく流れを作っています。感覚値ではありますが、GitHub Copilot の導入は、他社と比べても比較的早い方だったと思います。BizDev チームだけで使っている AI サービスや、Engineer 系チーム向けの AI サービスなど、用途に応じて使い分けもしています。

ただ、単に導入するだけで終わらせないようにしていて、おおよそ3ヶ月くらいのスパンで、検証結果をリーダー陣で共有し、継続するかどうか、あるいは別サービスを検討するかといった判断を行うようにしています。GitHub Copilot については、自分が主導して推し進めましたが、それ以降は各チームやリーダーから「これ試してみたい」という声が自然と上がってくるようになりました。この流れができていること自体、組織として少しずつ強くなってきているなと感じていて、個人的に嬉しく思ってます。

また、最近は n8n で AI を使った社内業務改善も進めてます。トライビートは外国籍の方も多く在籍しています。日本語でコミュニケーション取ることが多いのですが、議事録などは英語の方が読みやすいこともあり、Notion に追加されたものを英語で要約し、Slack で通知するなどの仕組み化をしています。

バーカウンターの立食

トライビートの本社にはバーカウンターがあります。ほぼ毎月のペースで、業後に軽く立食するような場を設けています。ビアバッシュがなくなったことで、定期的にみんなでラフに話す機会が減ってきたなと感じ、ノリで始めてみました。

自分が運営するのであれば、気兼ねなく、とても軽いノリでやりたいという気持ちがあり、その場合に会社の経費を使うことには少し違和感がありました。結果として、CEO や CTO から直接カンパしてもらい、自分も出す形で、フードとドリンクを用意しています。トライビートには、リアルなイベントプロモーションを担当しているメンバーも在籍しているため、フードについてはその方たちに相談し、予算以上のクオリティのものを用意できているのも特徴のひとつです。

参加はもちろん任意で、緩く始めて、各自の業務が終わったタイミングでパラパラと集まり、自然と飲み始めるような雰囲気です。終わりについても、好きなタイミングでお願いしてて、1杯だけ飲んで帰るみたいな人もありです。最近は、協力会社の方やお客様にも参加してもらう機会が増えてきており、交流が自然に広がっていくのを面白いなと感じながら見ています。

当日は、自分自身が率先してバーカウンターの中に入り、ドリンクを提供するなど、「場」だけでなく「流れ」も意識した行動を取るように心がけています。

その他

自分が主導していないものの、組織として進めている取り組みもいくつかあります。直接的なコミットは浅めですが、円滑に進むよう、できる限りサポートする姿勢を意識しています。こうした施策が、さまざまな観点から立ち上がってくる状態そのものが、組織のスケールにつながるとも考えています。

例えば、以下のような取り組みがあります。

  • 監視定例(週1回実施している、プロジェクト横断した情報共有会)の運用
  • ポストモーテムの運用
  • Snykの導入
  • セキュアコードの作成
  • 輪読会
  • re:Inventへの参加

マインドセット

役割は、チームリーダーから始まり、EM、そして VPoE と変わってきましたが、最初の背景に書いた通り、マインドセット自体は一貫して変えていません。リーダーシップとして大事にしているのは、「最初に自ら踊ること」です。そして、もう一つ大事にしているのが、「踊り切ること」です。

トライビート以前の会社で、リーダー的な責務を任されていたときは、「チームの中で、すべてにおいて一番優れていないと信頼してもらえないのではないか?」と思っていた時期もありました。ただ、変化が早く、領域が細分化され、専門性の深度がどんどん高まっている今の外部環境では、それは現実的ではないと感じています。

不確実性が高い現代では、「最初に踊って失敗する姿勢」「自分よりも優れた人に対して自己開示して頼る姿勢、あるいは、優れた人を理解し、追従するためにあえて抗う姿勢」など、自身の弱さもオープンにするくらいの真摯な行動や発言を積み重ねることで、チームは成立していくのだと思っています。これは、自身の弱さを見せるなんてことは小さいことで、もっと本気に目的を達成させたいという強い意思を間接的に伝えれているから成立するのだと思います。逆に、すべてを超越した完璧な人がリーダーに立ったとして、そこから組織がスケールするかと言われると、構造的には難しいのではないか、とも感じています。

また、昔は「多くを語らなくても分かり合えている阿吽の呼吸」がかっこいいと思っていました。長い関係性と共通のコンテキストが揃っている場合には成立しますが、トライビートのように多様な文化を持つ組織では難しいとも感じています。相互のリスペクトを醸成するためには、他者への理解だけでなく、自己開示も欠かせません。そのため、意識的に心の距離を縮めるコミュニケーションを心がけています。そもそも、情報の非対称性が生まれると、相互理解や共通認識の構築は難しくなります。だからこそ、役職や役割に関係なく、プライベートなことを除いては、できるだけオープンに、透明性高く共有することを大事にしています。

最後に

今回書き出してみた内容は、概ね実施した順番になっています。最初は、きれいに整理することで、再現性の高い情報になればいいなと思っていました。 ただ、改めて振り返ると、外部環境と内部環境のコンテキストが合っていない状態で、表面的に同じことをやっても、再現性はどうしても低くなると感じています。

一方で、「どういったことを目的に行動しているのか?」や、最後に書いたマインドセットのような部分については、多少なりとも応用が効くのではないかなとも思っています。

僕はこのソフトウェア業界の自由な思想と、いま以上にもっと改善していこうという姿勢が好きなので、今後も不確実性と多義性が高い環境を楽しみながらやっていこうと思います!

【Next.js & microCMS】SSGのビルドパフォーマンスチューニング

前提

  • Next.jsとmicroCMSを使用し、数百ページのWebサイトを構築している
  • App Routerは使用していない(Next.jsの採用時にApp Routerがまだ正式採用されていなかったため)
  • ISRも使用していない(業務要件の兼ね合いから使っていない)
  • Amplify上で、SSGして静的サイトを構築している

課題

  • ページ数が増えることで、ビルドのパフォーマンスが気になってきた
  • 当初から想定していたが、想定よりも遅いことが気になってきた

microCMSの簡単なデータ構造

  • microCMSでは下図にある通り、各ページからヘッダーとフッターのデータを参照している。また、ヘッダーとフッターからは、グローバルナビゲーションやフッターメニューで表示する、各ページのデータを参照している。

microCMSのデータ構造
microCMSのデータ構造

アプローチ

  • ページ生成時のmicroCMS API通信処理の回数を減らす、もしくは、通信データサイズを減らすことを検討。
  • 基本的には、Webページに合わせてデータ登録しているので、通信回数を減らすことは難しそう。
  • 通信データサイズを減らすための工夫を検討。

各ページの getStaticProps からmicroCMS APIを呼び出しているレスポンス内容を解析すると、想定しているよりもデータサイズが大きいことが分かりました。これは、ヘッダーとフッターで設定しているページ内容を取得するために、microCMS APIのdepthパラメータに 2 を設定しているからでした。

depthは、API単位で設定するもので、各階層に対して個別設定はできないので、depthの値を 1 に変更し、ヘッダーとフッターのデータは個別に取得するように変更しました。また、ヘッダーとフッターの値は、各ページで同じものを使用することが多いため、キャッシュできるような仕組みを用意しています。

具体的には、以下のような共通関数を1つ作成し、一度取得したヘッダーまたはフッターであれば、通信処理を介さずにデータを返すようにしています。

import client from '@/lib/client';
import { FooterData } from '@/types/footerData';
import { HeaderPcData, HeaderSpData } from '@/types/headerData';

const globalCache: Record<string, HeaderPcData | HeaderSpData | FooterData> =
  {};

const getHeaderFooterCachedData = async (
  endpoint: string,
  contentId: string,
): Promise<HeaderPcData | HeaderSpData | FooterData> => {
  const key = `${endpoint}-${contentId}`;
  if (globalCache[key]) {
    // console.log('cache hit');
    return globalCache[key];
  }

  const data = await client.get<HeaderPcData | HeaderSpData | FooterData>({
    endpoint,
    contentId,
  });
  globalCache[key] = data;
  return data;
};

export default getHeaderFooterCachedData;

まとめ

元々8分くらい掛かっていたビルド処理から30秒くらいの削減が見込まれることを確認できました。今回は一例ですが、Webサイト全体で扱う共通コンポーネントがあれば、同様のアプローチでキャッシュ化することも検討できるので、地道に積み重ねていくともう少し改善できそうです!

Amplify + Next.js + microCMSのプレビュー環境について

概要

株式会社トライビート| TRIBEAT CO., LTD. では、数年前からJamstackなWebサイトやECサイトの構築を推進(提案)しており、年々こういったご相談を受けることが増えています。 選定技術はそのときの顧客要件によって変わりますが、Amplify、Next.js、microCMSを使うことが多いです。Jamstackな構成を検討していると、SSGしてファイル生成する都合上、プレビュー機能をどうする?って課題に当たります。Headless CMSで下書きしたとき(公開前)に、Webページとしてどう見えるかを確認するユースケースですね。

アーキテクチャ的にSSGしたものをCDNホスティングすることで、「高速化、スケーラビリティ、セキュア」を担保する一方で、リアルタイムな表示は工夫が必要になります。プレビュー用のページもSSGすれば確認可能ではありますが、ビルドには大抵数分掛かるため、UXが悪くなり実用的ではないと考えています。

Next.jsでは標準的にPreview Modeが用意されていますが、昨年の調査時はAmplify上で正しく動作しませんでした。最近、Amplify hostingのGitHub issueを確認したところ、解決されていそうだったため、改めて検証してみました。

Next.js のPreview Modeとは

nextjs.org

上記の公式ドキュメントに実装方法が手順付きで記載されているので、詳細はこちらを確認するのが良いですが。色々端折って要約すると、Next.js には元々、APIを追加する機能が備わっており、 そのAPIの中で、 setPreviewData 関数を利用することで、プレビュー機能に必要なCookieが発行されます。 このCookieを保持しているブラウザーからアクセスされた場合、SSGで生成済みのページを返すのではなく、 getStaticProps というビルド時に実行されるページ生成用の関数が呼び出されます。

上記機能を利用することで、以下のようなフローが実現可能となります。Headless CMSからプレビュー用URLがリクエストされたときに、Cookieを発行して、対象のページにリダイレクトすると、新たにページ生成した内容で確認が可能となります。

Cookieを保持しているかどうかは、 getStaticProps のパラメーターにある context.preview のbool値で判断可能となります。Cookieを発行する手前の処理で、認証処理を実装することで、Headless CMSからアクセスされたときのみ、機能が有効になるように制御します。

Amplifyでは何が課題だったのか

github.com

github.com

1つ目のissueに関連付けられている、2つ目のissueで解決したようです。Next.jsのPreview Modeは、HTTP Response Header で Cookieを発行していますが、Amplify上にデプロイすると、これがうまく動作していなかったようです。解決されたのが、2022年の12月ということです。

実運用での課題

Amplify上でNext.jsのPreview Modeが動作するということで、課題はなくなったように思えますが、ページ更新のユースケースを想定すると、若干の工夫や考慮が必要になります。当初、本番(hosting)用のAmplify環境(env)上でプレビュー機能も動作させれば、環境構成もシンプルになり最適と考えてましたが、以下のようなケースで使いにくくなると想像しています。

前提

ニュースやブログなどを扱うWebサイトを(Cookieとかはよくわからない)非エンジニアの方がコンテンツ管理している場合

ケース1

  1. ニュースの下書きデータを Headless CMS からプレビュー機能で確認(このときにPreview Mode の Cookie が発行)。
  2. その後、別な作業をしている時に、Webサイトから現在のブログ内容を確認。
  3. 担当者は、公開中のブログページ内容を確認するつもりだが、意図せず下書きデータが保存されていると、下書き内容でページが表示される。

ケース2

  1. 既に公開済みの記事に不備があり、Headless CMSからデータ編集し、プレビュー機能で確認(このときにPreview Mode の Cookie が発行)。
  2. 現在公開中のページと更新後のページを目視で差分チェックしようとしたところ、下書き内容のページでしか確認ができない

上記のようなケースは、Cookieを保持しているブラウザーはSSGされたものではなく、常に新しくページ生成した結果が表示されるため発生します。以下の図のAとBは、同じサイトページをリクエストしていますが、Cookieの保持により表示されるコンテンツ内容は異なる可能性があります。 CMSの操作や現状サイトの確認などを繰り返し作業しているときに、Cookieの保持について意識するのは厳しいと想像しています。

Preview ModeのCookieを発行する setPreviewData 関数は、引数なしで使用するとSession Cookieとなります(ブラウザが閉じられると、Cookieが削除されます)。引数を指定することで、Cookieの有効期間(Expired)を設定することも可能ですが、最近のPCやMacの使われ方だと、ブラウザーを閉じないでアクセス作業することも多いと想像しています。 また、Next.jsでは、Preview ModeのCookieを削除する関数も用意されていますが、何をトリガーに呼び出すべきかは悩ましいです。

上記から、Amplifyプロジェクト内で、本番用とプレビュー用に環境(env)を分けて構築しようと思いましたが、同じGitブランチを別の環境(env)に分けることは想定されていないようで、Amplify Console上で環境(env)を追加するときに、既に接続済みのGitブランチは表示されないように制御されていました。

どうすべきか悩みましたが、本番用とプレビュー用でAmplifyプロジェクトを分けてしまい、同じGitブランチを接続させるのが、いまのところ良さそうです。microCMSなどのHeadless CMSからリクエストする先を、プレビュー用のAmplifyプロジェクトへ向けることで、本番環境の表示とプレビュー環境の表示を切り分けれます。

プレビュー用のAPIについて

blog.microcms.io

Next.jsの公式ドキュメントもわかりやすかったですが、microCMSのブログも丁寧な内容で助かりました。特に、 getStaticPathsfallback の設定変更については、公式ドキュメントに記載がないので参考になりました。 あまり大きな問題ではないですが、以下は今後検討して作り込む予定です。

  • microCMSのdraftKeyについては、Webhookのクエリストリングで渡すべきか、Amplify環境変数で設定すべきか?
  • プレビュー用のAPIについては、各ページごとに作るべきか、まとめるべきか?

AmplifyのSSR対応時のビルドについて

Amplifyは内部で、Next.jsのアプリケーションがSSGまたはSSRを判断して環境構築を変えているようです。Amplify + Next.jsの構成を最初に試したときは、2〜3年前でした。その当時、SSGモードではビルドが数分で終わるものが、SSRモードに変わることで、20〜30分掛かる事象にあたりました。今回のプレビューAPIは、SSRモードでなければ動作しないこともあり、ビルド時間に懸念がありましたが、AmplifyもNext.jsに合わせて進化しており、いまのところ、SSGとSSRでビルド時間はほぼ変わらないことを確認済みです。

Flutterで環境別のGoogleMap API Keyを設定する方法(dart-define-from-file)

概要

FlutterでGoogleMapを利用するとき、API KeyはOS毎に以下のファイルで設定する必要があります。

テスト環境と本番環境にそれぞれ発行したGoogleMap API Keyを、 ビルド(ipaまたはaabファイルの生成)時に、 dart-define-from-file オプションから設定する方法を記載します。

前提

Flutterで環境ごと設定値を分けるには、元々、Flutterビルドコマンドオプションの dart-define を使ってましたが、 dart-define-from-file オプションによって設定値をファイルにまとめることができるようになりました。
その方法については、以下の記事が大変参考になるので、こちらでは割愛した内容を記載します。

zenn.dev

dart-define-from-fileからの設定

dart_defines/dev.jsondart_defines/prod.json は既に用意している前提で、GoogleMap API Keyをそれぞれのjsonに追加します。

"iOSGoogleMapApiKey": "hoge"
"androidGoogleMapApiKey": "fuga"

iOS

  • ios/Runner/Info.plist に以下を追加し、swiftからdart-defineのjsonにアクセスできるように設定。
  <key>DART_DEFINES</key>
  <string>$(DART_DEFINES)</string>
  • ios/Runner/AppDelegate.swift を以下のように編集して、jsonファイルの内容を取得して使用することが可能。
import UIKit
import Flutter
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    // - Setup dart-define(dart-defineの値) 
    let dartDefinesString = Bundle.main.infoDictionary!["DART_DEFINES"] as! String
    var dartDefinesDictionary = [String:String]()
    for definedValue in dartDefinesString.components(separatedBy: ",") {
        let decoded = String(data: Data(base64Encoded: definedValue)!, encoding: .utf8)!
        let values = decoded.components(separatedBy: "=")
        dartDefinesDictionary[values[0]] = values[1]
    }

    GMSServices.provideAPIKey(dartDefinesDictionary["iOSGoogleMapApiKey"]!)
    // self.window.makeSecure()
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

extension UIWindow {
  func makeSecure() {
    let field = UITextField()
    field.isSecureTextEntry = true
    self.addSubview(field)
    field.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
    field.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
    field.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
    self.layer.superlayer?.addSublayer(field.layer)
    field.layer.sublayers?.first?.addSublayer(self.layer)
  }
}

参考URL medium.com

Android

  • android/app/build.gradle の defaultConfig に以下を追加し、dart-defineの値をAndroidManifest.xmlで利用できるように設定
    defaultConfig {
         manifestPlaceholders["googleApiKey"] = androidGoogleMapApiKey
     }
  • android/app/src/main/AndroidManifest.xml で上記の manifestPlaceholders に指定したKEYを使い設定
         <meta-data android:name="com.google.android.geo.API_KEY" android:value="${googleApiKey}"/>

参考URL

developer.android.com

Androidで正しく設定できているかどうかを確認する方法(値が正しく渡せているかを確認する)

apkファイルの拡張子をzipに変更して解凍することで、AndroidManifest.xml を見つけることはできるが、そのまま開いてもバイナリ形式にエンコードされているため確認することができない。

code.google.com

上記から AXMLPrinter2.jar をダウンロードし、以下のようなコマンドで確認用のAndroidManifest.xmlを生成することが可能。

 java -jar AXMLPrinter2.jarのPath AndroidManifest.xmlのPath > check_AndroidManifest.xml

PHP(ZTS)のxdebugインストール

概要

  • 同じチームのエンジニアが新しいプロジェクトに参画し、既に作成済みのDocker環境にxdebugを入れようとしたところ、うまく動かないという相談を受けて調査開始
  • peclコマンドでxdebugをインストールして設定しているが、確かに動かない、、、

結論

  • Docker環境のPHPは、ZTSで動いていたため、peclコマンドいれたxdebugは動作しない
  • ZTS用のxdebug.soを、自分で作ってあげる必要がある

ハマリポイント

  • peclコマンドでインストール後、以下のコマンドで確認をしていた。

      php -r "phpinfo();" | grep xdebug  
      php -v 
    
  • 上記コマンドだと、xdebugがちゃんと入っているように見えるため、最初はリモートデバッグ接続しようとしているVSCode側の設定とかを疑って時間がかかった。

  • Webブラウザから、phpinfo();出力したページを表示して、xdebugの設定が反映されていないことを確認できたので、ここからPHP側を疑って調査した。

解決方法

wget http://www.xdebug.org/files/xdebug-3.1.5.tgz
tar xzvf xdebug-3.1.5.tgz
cd xdebug-3.1.5/
zts-phpize
./configure --with-php-config=/usr/bin/zts-php-config
make
make install
  • 上記コマンドを実行すると、xdebug.soファイルが作成される
  • あとは、xdebug用のiniファイルを配置してあげる
zend_extension = "make installの出力先/xdebug.so"
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.log=/tmp/xdebug.log
xdebug.log_level=0

AmplifyプロジェクトでCognito送信者のLambdaトリガーを設定してみた

概要

  • Amplifyプロジェクトで普通にAuthを追加してMFA対応していた
  • SMS認証コードとEMail認証コードはそれぞれCognito標準機能を使っていたが、TwilioやSendGridなどの別サービスで送るための対応が必要になった
  • Cognitoから認証コードを送るときに、カスタム送信できるような機構が用意されているので、それを利用

ドキュメントや参考になるブログ

docs.aws.amazon.com

docs.aws.amazon.com

docs.aws.amazon.com

qiita.com

対応方法

  • AmplifyのCloud Formationに組み込みたかったが、現状、この機能はCloud Formationに対応されていないらしく、別途設定が必要になる
  • KMS(AWSコンソール)から、「カスタマー管理型のキー」を作成する
    • 特に考えずそのまま作成する
  • Lambda Layerを作っておく
    • package.jsonに以下を追加しておく。実際の認証コード送信に必要であれば、「@sendgrid/mail」や「twilio」も追加しておく
  • Lambdaを用意する
    • 公式ドキュメントにあるものを流用すれば良い
    • handler関数では、Cognitoから発火されたイベント名をみて、処理をハンドリングしてあげる(TwilioやSendGridを使って認証コード送信を書く)
  • LambdaにKMSの権限をアタッチしてあげる
    • Lambdaの[設定]→[アクセス権限]→[実行ロール]からKMSの権限をアタッチする
  • Amazon Cognitoサービスプリンシパルに、Lambda関数を呼び出すための、cognito idp.amazonaws.com へのアクセス権を付与

    aws lambda add-permission \
    --function-name <作成したLambdaのARN> \
    --statement-id "CognitoLambdaInvokeAccess" \
    --action lambda:InvokeFunction \
    --principal cognito-idp.amazonaws.com
    
  • Cognitoユーザープールを更新して、Lambdaトリガーを追加する

    aws cognito-idp update-user-pool \
    --user-pool-id <CognitoユーザープールID>  \
    --lambda-config "CustomSMSSender={LambdaVersion=V1_0,LambdaArn=<作成したLambdaのARN>},CustomEmailSender={LambdaVersion=V1_0,LambdaArn=<作成したLambdaのARN>},KMSKeyID=<作成したKMSのARN>" \
    --profile <AWSのプロファイル>
    
  • 間違ってLambdaトリガーを追加したときに解除するコマンド

    aws cognito-idp update-user-pool \
    --userpool-id <userpool_id> \
    --lambda-config "{}"
    

ハマったところ

たまに認証コードが同じユーザーに2度送られる事象が発生

  • SMS認証コードをTwilioのVerify APIを使って送信していたが、時たま、2度送られていた
  • 設定したLambdaトリガーのログを見ていると、以下が出力されており、失敗したLambdaの約1分後に同じイベントが発火されていた。
  • Verify API処理が3秒超える場合がたまにあり、そのときに再度Lambdaが実行され2度送信されていることを確認

    Task timed out after 3.00 seconds
    

対応方法

  • Lambdaのタイムアウトをデフォルトの3秒から10秒に変更し、メモリも256mbに変更
  • 設定値は、利用するサービス仕様に合わせる

www.twilio.com

カスタムLambdaトリガーが実行されない事象発生

  • aws cognito-idp update-user-pool コマンドでLambdaカスタムトリガーを紐付けても、Lambdaが呼ばれない
  • AmplifyでAuth追加した時点では、Cognitoの「どの属性を確認しますか?」の項目では、「Eメールまたは電話番号」を選択していたが、コマンド実行すると、「検証なし」に変更されていた。

対応方法

  • aws cognito-idp update-user-pool コマンドのオプションに auto-verified-attributes を追加
  • 上記オプションには、 sms-configuration が必要になる
    • sms-configuration に指定には、「SMS 送信に使用する IAM ロールの ARN」と「SMS 送信に使用する IAM ロールの、信頼ポリシーに設定されている sts:ExternalId の値」が必要になる。
    • このIAMロールは、Amplifyで適切にAuth追加していると既に作成されているので、それを使う。
    • Cognito(AWSコンソール)の「MFAそして確認」メニューの一番下に「新規ロール名」があるので、IAM(AWSコンソール)から検索して、信頼関係のタブを開くと「sts:ExternalId」の値が確認可能
    aws cognito-idp update-user-pool \
    --user-pool-id <CognitoユーザープールID> \
    --lambda-config "CustomSMSSender={LambdaVersion=V1_0,LambdaArn=<作成したLambdaのARN>},CustomEmailSender={LambdaVersion=V1_0,LambdaArn=<作成したLambdaのARN>},KMSKeyID=<作成したKMSのARN>" \
    --profile <AWSのプロファイル> \
    --auto-verified-attributes {email,phone_number} \
    --sms-configuration SnsCallerArn=<SMS 送信に使用する IAM ロールの ARN>,ExternalId=<SMS 送信に使用する IAM ロールの、信頼ポリシーに設定されている sts:ExternalId の値>
    

Next.js on Amplifyの503エラーの続き

概要

yasutomo.hatenablog.com

  • 上記ブログの続き
  • AmplifyでNext.jsをデプロイしたタイミングだと問題なく動くが、あるタイミングからCloud Frontの503エラーになっていた
  • 上記事象が発生するのは、同じAmplifyプロジェクト上で同じソースコードで構築したhostingでも再現するものとしないものがあった

原因

  • Next.js on Amplifyで構築される、Lambda@Edge に SQS の権限がアタッチされないことがある
  • SQS の権限がアタッチ「されるとき」と「されないとき」の2パターンあるのがポイントで、ケースは以下
    • Amplify hosting作成時、はじめてデプロイするタイミングのソースコードがISR対応している:SQSの権限がアタッチされる
    • Amplify hosting作成時、はじめてデプロイするタイミングのソースコードがISR対応していない:SQSの権限がアタッチされない
  • ISRのキャッシュが切れて裏でページ生成されるタイミングで、Lambda@edge から SQS に SendMessage しようとしたときに権限がないと503エラーとなる

対処方法

  • 解決するには、以下2つがある。(現状はどちらも試していて、どちらのケースでもうまく動いている)
    • Amplify hostingを作り直す
      • 事象が発生しているhostingを一度削除し、改めて作り直す
      • まだサイト公開前とかであれば、こちらの方法がシンプルで良さそう
    • 事象が発生している Lambda@edge にSQSの権限をアタッチする
      • 雑にやるなら、AmazonSQSFullAccess ポリシーを付ける。正しくやるなら、"Resource": "arn:aws:sqs:us-east-1:<アカウント ID >:", の Actionに "sqs:SendMessage" を指定する。
    • Lambda@edge や SQS のIDについては、Amplifyコンソールのデプロイ部分のログに出力されている
    • Lambda@edge に権限アタッチ後、改めてデプロイしても自分で割り当てた権限はついたままとなっている(Amplify側の挙動の保証はない?)

所感

  • プロジェクト構築時は、シンプルな状態から始めることが多いので、途中からISR対応ページなど追加したときに気をつける
  • Amplifyコンソールのデプロイフェーズのログが重要
    • ここに出力されている、CloudFront や Lambda@edge のIDから詳細ログを追う力が必要

調査のときに見ていたGitHubとドキュメント

github.com

github.com