3度の改善を経て利用社数が急増したノーコード連携開発の裏側

はじめに

こんにちは!EventHub CTOの井関です。

これは EventHub Advent Calendar 2025 の24日目の記事です。

今回の記事では、今年2月にプレスリリースを出した「ノーコード連携」について、リリースまでの経緯とその後をまとめます。 実は、こうした連携機能は以前から提供していましたが、なかなか利用率が伸びず、3回の大きな改善を加えることでようやく今の形になりました。もちろん、プロダクトは常に進化しているため、現在の連携機能もまだまだ改善の途中です。

ノーコード連携は、EventHubが実現したい世界観の土台となる重要な機能です。そのため、「2月のリリースから9月末までの約7ヶ月間で、アクティブ利用社数を大幅に伸ばす」という高い目標を掲げていました。そして結果は……無事に目標としていた導入社数に到達し達成することができました!

この機能はお客さんの環境に合わせた設計が必要なため、セールス・カスタマーサクセス・カスタマーサポートといった様々な部署の協力なしには達成できませんでした。開発した機能が本当にお客さんに価値を届けるためには、全社的な協力がいかに重要かを再認識しました。

なぜ連携が重要なのか

EventHubは、出展、ウェビナー、小規模オフライン、オンラインカンファレンス、大規模オフラインまで、あらゆるイベントタイプに対応することで、イベントマーケティングの成果を最大化します。 成果を最大化するためには、単にイベントを開催するだけでは不十分です。開催データを可視化してPDCAを回すこと、そしてイベントで得た顧客データをその後の商談(CRM/SFA)にスムーズに紐づけることが重要になります。 だからこそ、EventHubの世界観を実現するために「データ連携」は欠かせない機能になります。

1回目のデータ連携:API公開

初期のデータ連携は、APIの公開から始まりました。この公開APIは現在も活用されており、ノーコード連携の礎となっています。 しかし、単にAPIを公開しただけでは、開発リソースを持たないお客様にとってはハードルが高く、商談データとして活用するまでには至りませんでした。結果として、利用率は低いままでした。

2回目のデータ連携:MA/SFA直接連携(ミニマム)

2回目は「商談での活用」に狙いを絞り、MA/SFA(Marketo, Salesforce, HubSpot)と直接連携できる機能を開発しました。 MA/SFA連携はできることが膨大で、全てに対応しようとすると開発工数が肥大化します。そのため、お客様側で仕組みを構築していただく「ミニマムな連携」を目指しました。 これにより利用率は向上しましたが、お客様に深いMA/SFAの知識が求められる仕様となってしまい、一部の方しか使いこなせないという新たな課題が生まれました。

3回目のデータ連携:柔軟性の追求

機能が限定されていることでお客様に高度な知識を求める形では利用促進が難しいため、お客様の利用用途に幅広く対応できるようにするためにより柔軟な仕組みを導入しました。ただ、今度は柔軟性を担保した影響でお客様自身が環境に合わせて設計と実装を行う必要が出てきてしまいました。柔軟性が高すぎたあまり、提案をしても「難しそう」と敬遠されてしまう結果となりました。

現在の「ノーコード連携」

そこで、実際に私自身も何度も商談に同席し、生のお客さんの声を拾い集めました。その結果、「ある程度の柔軟性を確保しつつ、EventHubが推奨する設計(ベストプラクティス)を簡単に取り込める方法」を模索し、リリースに至りました。 まだ課題はありますが、EventHub側での伴走が可能でありつつ、フルスクラッチの受託開発のようにはならない、「ちょうど良い塩梅」の形式に着地しました。これが功を奏し、利用率は飛躍的に伸びました。

最初のAPI公開(2023年1月)から約3年。この期間は、私たち開発チームにとって「何をもって完成とするか」の定義を問い直す時間でもありました。 最初は「連携機能が実装されていること」をゴールにしていましたが、それだけでは顧客の成果には繋がりませんでした。そこから3度の改善を経て、実際に商談成果が出るという状態まで解像度を高めたのが今回のノーコード連携です。 多くの利用実績が出た今、ようやくスタートラインに立てた気がしています。

得られたインサイト

今回の開発を通じて、いくつかの重要な学びがありました。

ヒアリングだけでなく「商談・オンボーディング」の現場に出る

「ご意見を聞かせてください」というヒアリングは色々な意見が聞けますが、そこで得られるものはどうしても「あってもいいかもね」といった温度感の内容も含まれます。 それよりも、「お客様がお金を払うかどうかの判断をする商談」に同席し、営業と共に提案する方が、シビアで本質的な情報を得られました。また、受注後もCS業務に入り込み、実際に課題が解決されたかを見届けることで、顧客解像度が劇的に上がりました。

既存だけでなく「新規」も見る

「利用率の改善」を目標にすると、母数の多い既存顧客にどうしても目が行きがちです。しかし、既存のお客様だけでは見えてこない視点や課題も多くあります。 これから運用を構築する新規のお客様の課題を理解しどのように解決するかも合わせて考慮しなければなりません。「誰にとっての価値か」を見誤らないよう、既存・新規のバランスを意識することが重要でした。

最初から「手離れ」を目指さない

SaaSプロダクトとして、お客様が自走できる(セルフオンボーディング)仕組みは必須です。しかし、最初からそこを目指してしまうと、顧客が躓く「本来の課題」を見逃してしまいます。 「技術的にできること」だけで自動化を進めると、「機能としては完成しているが、現場では使いづらい」プロダクトになりがちです。まずは手厚く伴走し、正解のフローが見えてからシステム化・効率化する順序が大切だと痛感しました。

インサイト以上に大切なこと

ここまでプロダクト開発の学びを書きましたが、今回高い目標を達成できた最大の要因は全社員がEventHubのバリューである「顧客を主語に」を体現してくれたことに尽きます。 実は今回の連携機能はセールス・カスタマーサクセス・カスタマーサポートにとっては、「負担」が増える側面もありました。

  • セールス: システム検証などの工程が増え、受注までのリードタイムが伸びる
  • カスタマーサクセス: MA/SFA環境に関する深い知識が必要になり、オンボーディングの工数が肥大化する
  • カスタマーサポート: 調査難易度が上がり、問い合わせ解決までの時間が増大する

自部門のKPIや効率だけを見れば、敬遠されてもおかしくない機能です。 しかし、それでもお客様の真の課題解決につながるならと部門の垣根を越えて一丸となり向き合ってくれました。適切な機能を作るためには前述の「インサイト」が重要ですが、その機能を真の価値としてお客様に届けるためには、全社的な連携が不可欠です。

改めて、今回の高い目標を達成できたのは様々な部署の協力があったからこそです。この場を借りて、全員に心から感謝します。

最後に

こうした形でエンジニアでも深く顧客を理解し必要な機能を考え抜いて開発を進めることをEventHubでは大事にしています。

even-eko.hatenablog.com

開発チームを拡大しようと考えた時からこの思想を打ち出して今の文化ができあがってます。顧客に向き合いながら開発することは時に大変ですが、大きなやりがいや事業成長に深く関わることができます。

ぜひ、そんな挑戦をしてみたいという方は連絡をお待ちしてます!!

jobs.eventhub.co.jp

次の25日目の記事は、弊社代表の山本です!! お楽しみに!

チーム開発の意思決定、どうすればスムーズにできる?

こんにちは、EventHubの井上です。昨年の12月から約1年ほどプロジェクトマネジメント※をやらせていただいてます。仕様の調整やテーブル設計などチームでの決め事をリードしなければならない場面が多かったのですが、なかなか苦労しました。

ちょっとした会議を進行するのも緊張しますし、何より会議の設計や進め方が未熟なために、議論が平行線になり、30分の予定が1時間経っても何も決まらない・・・なんてこともありました。

試行錯誤の日々でしたが、つい最近、上司から「会議の進め方が改善されてますね」とフィードバックをいただくことができました。

この記事では、周囲の方からのアドバイスや自身の経験を元に、チーム開発でスムーズに意思決定するためのtipsを5つご紹介します。

1つの事例として参考になる点があれば嬉しいです。

※ EventHubにはプロジェクトマネージャーという役職はなく、開発チーム内の任意のメンバーがその役割を担う体制になっています。

tips1: 提案ありきで受け手の負担を減らす

会議においてダメな方法は、「参加者に丸投げすること」です。自分の中でも整理できていないままダラダラと話して参加者の時間を消費し、結果、何も決まらなかったりします。

ではどのようにすると良いかというと、「提案とセットで相談すること」です。

例えばあなたが会議の参加者だとして、AさんとBさんがそれぞれ相談事を持ってきたとします。

Aさん:「〜〜なことで困ってまして、〜〜も調査したんですけどよく分からなくて、どうすればいいでしょうか?」

Bさん:「〜〜を解決したいと思ってます。原因は〜〜で、現状は〜〜なので、それを元にA~C案を持ってきました。〜〜な理由で、B案が良いと考えています。B案で進めていいでしょうか?」

どちらが答えやすいかは明白です。Aさんの相談には以下のような疑問が浮かび、参加者は何を答えればいいか頭を悩ませてしまいます。

  • そもそも何を解決したいんだろう?
  • 何をどこまで調査したの?
  • なんとなくこうするのがいい気がするけど、他にも良いやり方がありそうだな・・・。

一方で、Bさんの相談に対しては、それほど負担なく回答することができます。参加者は提案をベースに考えられるので、他の観点やより良い意見も出やすくなります。

提案をどう作るか

提案を準備するためのプロセスですが、自分はおおむね以下で進めています。

  1. 実現したいこと・目的を明確化する
  2. 現状どうなっているか調査し、整理する
  3. 確認すべきこと(= 論点)を洗い出す
  4. その中で重要な論点を絞る
  5. その論点について、複数案を用意して、自分なりの結論(= 提案)を決める

こう書くと難しく聞こえてしまいますが、要は、「相談する前にちゃんと調査して自分なりにどうすべきか考える」ということです。

以前の自分は次のような失敗をしていました。

  • 論点を絞らずに議論を進めてしまう。
    • 参加者は何を聞きたいのか、何を話せばいいかよく分からない。
      • 結果、何も決まらない。

ではどうすべきか。次のポイントを押さえると、決まりやすい会議になります。

  • 1回の会議で扱う論点は1つにする。
  • その論点について複数案を用意し、その中で1つ推奨案を提示する。

また、認識合わせや意思決定が必要な場面では、ドキュメント化して整理すると進めやすくなります。例えば、こんなフォーマットが役立ちます。

【背景】
- なぜこの議題が必要か?
- どのユーザー・どの機能に影響するか?

【現状と課題】
- 何が問題か?
- どの部分が曖昧か?

【論点】
- 今回決めたいことは何か?
- 仕様/実装/運用/UXのどれか?

【選択肢】
- 案A:
- 案B:
- ゼロ案(やらない選択肢):

【判断基準】
- 工数、UX、リスク、将来の変更コストなど

【推奨案】
- 私の提案はA案です(理由:●●)

【意思決定方式】
- 非同期でコメント募集(期限:●月●日●時)
- or 同期MTG実施(参加者:最小限●名)

【ログ】
- 決まったこと/決まらなかったことを残す

tips2: 論点を整理し、適切な人に相談する

何か困りごとがあった時に、相談相手を間違えてしまうことがあります。例えば、実装の制約についての困りごとをPdMやデザイナーに相談しても、「それは開発チーム内で決めてください」と返ってくるでしょう。一方で、どのような要件・仕様にすべきかを開発チームだけで決めてしまうのはよくないはずです。

なぜ相談相手を間違えてしまうのかというと、何を確認したいのか、自分の中で論点が整理できていないからです。tips1で紹介したようなプロセスで論点を整理しておけば、このようなミスコミュニケーションは防げるはずです。

自分の失敗例として、PdMに仕様のことを聞いたつもりが、実装観点の話が混ざってしまっており、「仕様観点か実装観点どちらの話ですか?」と聞き返されてしまったことがありました。これをきっかけに、論点を整理した上で、誰に何を聞くべきか意識してコミュニケーションをとるようになりました。

tips3: 図を活用する

提案書を作る時に、題材が複雑だと文章だけではうまく伝わらない場合があります。最近あった事例は、バッチ処理を行うタイミングをいつにするかの仕様を決める際、「現在時刻が〇〇で、設定時刻が〇〇の時、XX時に発火する」等と文章でまとめてみましたが、うまく伝わりませんでした。

そこで、Miroというツールで時系列を図表に起こしたところ、文章よりもずっと伝わりやすくなりました。自分自身も図に起こす過程で、見落としていたエッジケースに気づけたりと、思考の整理にもなると実感しました。

miroで作成した図

難点としては文章よりも手間と時間がかかるため、心理的に着手しづらいことです。正直に言うと、面倒なのでやりたくないと思ってしまいます。

面倒さを乗り越えるための心がけとして、以下を持つようにしています。

  • センスは不要。下手でも伝わればいい。
  • 最初は面倒なのは当たり前。
  • 皿洗いと同じでやり始めたら最後までやれる。

とは言え、文章よりも時間がかかることは事実なので、それほど複雑でない事柄は文章だけで十分だと思います。

生成AIで作る手間を省く

設計フェーズでよく使うER図、シーケンス図、状態遷移図などは、Mermaid記法で生成AI(Claude Code)に書かせ、Notion MCPでそのまま転記するような運用にすればそれほど手間をかけずに作成することができます。

また、近年はClaude CodeやCodexなどを使えば簡単なモックをすぐに作ることができるので、ローカル環境で動くものを作って共有すると、より共通認識を得やすくなると思います。

蛇足ですが、簡単なUIモックを作る時に、CursorのComposer 1に書かせてみると超高速で驚きました。公式によると、同等モデルの4倍の生成速度とのこと。一方で、より複雑なものを作る時は、Claude CodeやCodexの方が精度高くやってくれる印象です。

tips4: 期限を設定する

チーム開発で意思決定を行う上で、メンバー間の合意形成をすることが重要です。合意形成することでお互いに共通認識が持てますし、納得感のある状態で開発を進めることができます。

ただ、テーブル設計などではあるあるですが、それぞれ主義主張があるため、議論が紛糾し、なかなか決められないことがあります。また、設計書をSlackで共有したものの、誰もコメントしてくれない・・・なんてこともよくあります。

なぜこうなるのかというと、期限を決めていないからです。期限がないためにメンバーは以下のような心理になってしまいます。

  • まだ時間があるから、もう少し議論したい。
  • まだ時間があるから、あとでコメントしよう。

また、設計書をただ共有されただけだと、どうリアクションすればいいか分からず躊躇してしまうようなこともありえます。「どうコメントすればいいですか?」と聞いてもらうだけで解決しますが、起案者がそのあたりの案内をした方がスムーズに進むはずです。

以上を踏まえて、何かを提案するときは以下のように期限と決定方法を示します。

  • ◯月◯日の◯時に結論を決めます。
  • (簡単な事柄の場合)SlackのこのレスにYes/Noスタンプで投票してください。
  • (複雑な事柄の場合)設計書に疑問点など事前にコメントいただき、30分の会議で決めましょう。

多数決で意見が割れてしまったりしても、期限に達したら、提案者がベストだと思う案を選択し、他のメンバーは Disagree & Commit(賛同しないがコミットする) の精神で、チームでその案を進めていくことが大事です。

tips5: ログを残す

同じ議論を繰り返さないようにする、という観点で議論のプロセスや決定事項のログを残すことが重要です。

できれば決定事項は一箇所のマスタ文書に反映し、複数箇所に分散しないようにすると、後で混乱が生じるのを防げます。要件については要件書に、設計については設計書に更新を加え、常に最新の状態に保ちます。

議論プロセス(議事録)を残すことにも価値があります。例えば、別の開発者が後から改修を加える際に、「なんでこの設計になったんだろう。こうした方がいい気もするけど、何かこうしなければならない理由があったのかな」と疑問に思う場面があると思います。開発中のチームが実装時に「そういえばなんでこうしたんだっけ」と議事録を遡ることもよくあります。

EventHubではGoogle Meetを使用していますが、意思決定する会議では、録画と「Geminiによる自動メモ作成」機能を使い、負担なく議事録を残すようにしています。自動メモの精度は素晴らしく、要点が的確にまとまっていて、生成AIの進化を実感します。

まとめ

以上、5つのtipsをご紹介しました。チーム開発の経験が豊富な方は、無意識レベルでやっていることがほとんどだったかもしれません。こうして書いてみるとどれも当たり前のことだと感じるのですが、情報量が多く、タスクが山積みな状況ではつい疎かにしてしまうことがあるなと思いました。

抜け漏れがないように、以下のようなチェックリストをNotionテンプレートなどに貼っておくといいかもしれません。よかったら活用してみてください。

意思決定チェックリスト

- [ ] 目的は明確か?
 (なぜ決める必要があるのか、背景・課題が書けている)

- [ ] 相談相手は適切か? 
 (仕様→PdM/実装→開発/UX→デザイン etc)

- [ ] 論点は1つに絞れているか?
 (複数ある場合は順番に扱う or 別ミーティングに分ける)

- [ ] 選択肢(A案 / B案 / やらない案)は提示できているか?
 (推奨案と理由も示せているか)

- [ ] 図・モックなど、伝わる形に変換したか?
 (例:ER図 / シーケンス図 / 時系列表 / Before-After比較 / UIモック)

- [ ] 意思決定方法と期限は設定したか?
 (例:Slack投票・ドキュメントコメント・30min同期MTG)

- [ ] 決定後の次アクションが明確か?  
 (誰が何をいつやるか)

- [ ] 決めた内容を正しい場所に反映したか? 
 (例:要件書 / 設計書 / JIRAチケット / コーディング規約)

現在、EventHubではエンジニアを募集しています。本記事を読んで少しでも興味を持ってくださった方は、ぜひカジュアル面談からご連絡ください!

jobs.eventhub.co.jp

note.com

EventHub社内アンケートから見る、AI利用の現在地

はじめに

こんにちは、勝田です。

先日、社内で「Cursorの使い方とTips」に関する勉強会を開催しました。

そのときふと、「実際みんな、AIってどれくらい使ってるんだろう?」と気になって、エンジニアメンバーを対象に簡単なアンケートを取ってみました。

AIツールはもはや特別な存在ではなく、手元にあって当然の日常的な開発パートナーになりつつあります。

今回は、社内のリアルな声をもとに、AIとの向き合い方についてまとめてみました。

1. 普段使っているAIツール

EventHubでは、GeminiGitHub Copilot を契約しており、エンジニアがAIツールに触れるハードルは低く、日常的に活用されている環境が整っています。

 

実際に社内の声を聞いてみると、思った以上に複数のツールを状況に応じて使い分けている人が多く、「このケースならこのツール」といった選び方が自然に行われていました。

中でも興味深かったのは、Copilotに加えてCursorを積極的に選んでいる人が多かった点です。その理由としては、以下のような声がありました。

  • 補完の推測精度が高く、複数行や複数ファイルにまたがる補完が強力

  • プロジェクト全体を読み込んだうえで文脈に沿った提案をしてくれる

  • 操作が軽くストレスが少ない。反応も速い

 

ツール選定そのものがナレッジ化している今、「誰がどんな場面で何を使っているか」をオープンに共有し合える仕組みが、今後さらに重要になりそうだなと思いました。

2. サポートされている業務

コード補完、テスト作成、既存コードの意図の把握、ドキュメント生成、エラーの原因調査など。

「ゼロから創り出す」というよりは、既存の業務の加速や補助に活用しているというのが全体的な傾向でした。

僕自身も、まっさらな状態からAIに任せるより、ある程度書いたものに対して肉付けしてもらったり整理してもらったりするのが、一番フィットしていると感じています。

3. 生産性への貢献度

「非常に貢献している」「ある程度貢献している」という声が多く、補完を“切る理由がもはやない”と感じている人もいました。

実際、自分自身も最近はAIなしでコーディングするのが考えにくくなっていて、それくらい日々の開発に溶け込んでいる感覚があります。

ただし、「全部AIに聞けばOK」という状態では決してなくて、あくまで人間の判断があってこそ活きる道具という認識が、社内全体にしっかり共有されているのが印象的でした。

4. 印象的な成功体験

たとえば、

  • 「テストコード生成が圧倒的に速くなった」
  • 「SQLのパフォーマンスボトルネック調査で活躍した」
  • 「類似処理をテンプレ化して複製するのが爆速だった」

効率が上がっただけでなく、ストレスが減ったというニュアンスの回答が目立ちました。

とくに、「Cursorでコードの意図を要約してもらうと、他人のコードを理解するスピードが段違いだった」というコメントには、深く共感しました。

属人性を下げ、チームとしての可読性を底上げするツールになり得るという手応えを感じます。

5. 使いにくさや課題

  • プロンプト設計が難しい・面倒

  • 結局、自分で書いた方が早いこともある

  • 出力の正確性に不安がある

  • セキュリティ懸念(情報漏洩など)

このあたりは、自分もまさに日々感じているポイントです。

特に「正確な指示を出す」ための思考の流れは、これまでの“自分で手を動かして作る”スタイルとは明らかに違っていて、新しい思考回路が必要になる気がします。

結果として、「AIに聞く前にまず自分の思考を整理する」ことが求められるため、そこに負荷を感じることもあります。

いわゆるプロンプト疲れというやつですね。

6. ヒヤリハット体験

  • SQLにインジェクション対策が入っていなかった

  • ファイル構造が壊れそうになった

ツールが便利であるがゆえに、「どこまで信用していいのか?」という不安は常につきまといます。

だからこそ、信頼して任せる部分と、自分で必ず確認すべき部分の境界をどこに引くかが、エンジニアとしてのリテラシーになってきている気がします。

7. 今後AIの役割が増えることへの気持ち

「期待している」が多数派でしたが、同時に「正直不安もある」という声も一定数ありました。

ここでの不安は、AIの進化そのものよりも、自分がその変化に適応できるかという点にあるように思います。

キャッチアップのスピードに差が出やすいからこそ、学び方や使い方を支える設計がますます大切になっていきそうです。

8. 今後どう関わっていきたい?

積極的に関わりを増やしたい」「必要に応じて活用したい」という選択肢にすべての回答が集中しており、AIに対する前向きな姿勢が色濃く表れていました。

とはいえ、その前向きにもグラデーションがあって、日常的に使い倒していきたいという人もいれば、必要な場面で効果的に使えればいいという人もいます。

この違いはスキルやリテラシーの差というより、業務スタイルやタスク特性の違いによるものかもしれません。

だからこそ、誰もが同じように使いこなす必要はなくて、それぞれが自分にとってちょうどいい使い方を見つけていくことが、自然で無理のない関わり方なんだと思います。

9. その背景にある考えや期待、不安

  • 単純作業が減るのは歓迎

  • でも、間違いを見抜ける力も問われている

  • 「使える人」と「使えない人」の格差が広がりそう

  • ツールに振り回されたくはない

こうした声からは、AIへの期待と同じくらい、「本当にうまく使えるのか?」という戸惑いや、置いていかれることへの漠然とした不安も感じられました。

使えること自体をゴールにするのではなく、それをどう活かせるか/どこに効かせるかという視点のほうが、ずっと大事だと思います。

無理に使いこなすよりも、自分にとってのちょうどいい距離感や使いどころを見極めていくことが、これからのAIとの付き合い方の鍵になりそうです。

10. チームとして活用を進めるために必要なサポート

  • 社内Tipsの共有

  • 共通ツールの整備

  • 試せる余白

  • 各自の裁量を尊重する風土

今後も社内勉強会などを通じて、日々の開発に直結するナレッジをみんなで持ち寄りながら、よりよい活用環境をつくっていけたらと思っています。

このバランス感こそが、組織にとってのちょうどよいAIとの距離感を生み出していく鍵になるのではないでしょうか。

おわりに

AIツールが当たり前になった今、問われるのは「AIに何ができるか」ではなく、それをどんな文脈で、どう使いこなすか、どんなユースケースで価値を発揮できるかという視点です。

今回のアンケートを通じて、皆がとてつもないスピードで進むAIの進化にちょうどいい距離感で接しながら模索しているということでした。

 

その中で得た感覚や、工夫の共有が何よりのナレッジになると感じました。

こうしたナレッジが少しずつ社内にたまっていくことで、AIと協働しながら開発を進める精度やスピードが上がっていく可能性も、あると思ってます。

だからこそ、今回のようなアンケートや勉強会をきっかけに、無理なく自然にナレッジが循環する場を、今後も継続してつくっていけたらと思っています。

 

現在、EventHubではエンジニアを募集しています。本記事を読んで少しでも興味を持ってくださった方は、ぜひカジュアル面談からご連絡ください!

jobs.eventhub.co.jp

note.com

続・EventHubにおけるリリースフローの紹介

はじめに

こんにちは。EventHubの西内です。

前回はEventHubにおけるリリースフローの紹介というタイトルで記事を書かせていただきました。

tech.eventhub.jp

その際に「リリース後の話」や「開発着手前や着手中の細かい話」を話さないこととして挙げていたので、今回はそのあたりについて触れていければと思います。

また、EventHubではフルサイクルエンジニアを前提とした働き方を推進しており、いわゆる設計や実装およびテストといった一般的な開発業務以外で実際にどのようなことを行っているのかについてもお伝えしていければと思います。

リリース後の話

さて、リリース後の話についてですが、エンジニア採用資料のスライドから引用した図を元にすると”O&M”と”Support”が該当します。

O&M ( Operation & Maintenance )

いわゆる運用保守と呼ばれる活動を指します。組織によっては新規開発とは全く別の管轄になっているところもあるかも知れません。

EventHubだと以下のような活動例が含まれます。

  • 障害対応
  • インフラの監視や運用
  • 各種ライブラリ等のバージョンアップ

障害対応や監視を除けば、これらの対応を行うことが決定したら普段の開発スケジュールに乗ってくるので、特に難しく考える必要はないかと思います。

フルサイクルの文脈で言えば、自分たちで開発したプロダクトに責任を持ち運用することを踏まえて開発する必要がある、ということになります。「責任を持つ」と表現すると、プレッシャーを感じることになるかも知れませんが、その分だけ自由さを手に入れられることにもなると考えています。

たとえば、Aという設計や仕様だと運用が難しくなると思うなら、「A’という設計や仕様にしたい」と提案することができますし、EventHubはそういった提案への理解や尊重をしてくれる組織です。

Support

顧客や社内メンバー向けに、プロダクトに関してサポートする活動のことを指します。

O&Mと同様に活動例を載せると、以下になります。

  • 技術的な質問回答や調査
  • 不具合報告の一次受け

エンジニアがあらゆるサポートを行うかというとそういうわけではなく、EventHubにはCustomer Support(通称Cサポ)というポジションの方が数名在籍しており、社内外からの問い合わせに対して最初はCサポの方々に回答をしていただいています。そこからさらに技術的な調査や機能に関する詳細な振る舞いについての回答が必要な場合は開発組織に連携が行われます。

そうして連携された内容に関して、週替りの2名体制で対応する仕組みとなっています。

“技術的な質問回答や調査”では、先述の通りCサポだけでは回答が難しい技術的な質問やプロダクトの機能のエッジケースなどについて調査・確認を行い、結果をお伝えしています。

“不具合報告の一次受け”では、報告された内容を精査し不具合の重篤度を判断しています。このことをEventHubでは「バグレベル判定」と呼んでおり、判定のための目安になる定義も存在しています。また、同時に緊急度も判断し、必要に応じてエスカレーションを行うことがあります。

ちなみにこれらのやり取りはそれぞれ専用のSlackチャンネルがあり、Slackのワークフローを使って問い合わせや報告をテンプレートに沿って投稿できるようになっています。

問い合わせイメージ

その他

サイクルの図にはないですが他の活動で言えば、リリースした機能がどれぐらい利用されているかを不定期でチェックすることがあります。

このあたりはプロダクトマネジメントの文脈に当たるかとは思うので詳細は省きますが、こういった数値を調べることが顧客理解に繋がることがあると個人的には感じています。

開発着手前や着手中の細かい話

では次の話題に移ります。

前回、自分が記事を投稿したのは2024年の8月末頃でした。つまり、約8ヶ月ほど経過しています。

その間に、いくつかの記事がアドベントカレンダーやテックブログにて投稿されており、それらでも開発着手前や着手中の話について触れられているので、このタイミングで紹介しておきます。

もし良ければ以下の素敵な記事にも目を通していただければと思います!

note.com

note.com

tech.eventhub.jp

 

さて、まずは話の前提を揃えておければと思います。

  • ここで扱う”開発”とは?
    • お客様の要望に基づくスモールな開発(以下、スモール開発)
    • 今後のプロダクト戦略に沿った中・長期規模の開発(以下、エピック開発)
  • 開発フローの概要は?
    1. 要件定義
    2. 調査(Spike)&設計
    3. 実装&テスト
    4. リリース

EventHubではスクラムによるアジャイル開発を用いており、そしてどちらの“開発”についてもほぼ同じ開発フローで進め、規模感や要件に応じて適宜調整を行うイメージです。

スモール開発であれば、事前の調査や設計というフェーズを省いて実装しながら考えるということもありますし、事前にすり合わせてから実装に臨むこともあります。いずれにせよ、スケジュールは短めです。

エピック開発であれば、調査や設計に一定の時間を使うことが多いです。これは実現可能性の調査や後戻りできない(しづらい)設計にならないようにするためとなります。 また、実装とテストを進めていくうちに要件や仕様の調整や提案をエンジニアから持ちかけ、期日/品質/コスト/運用などのバランスを取りつつ、開発を進めていっています。

ここまでの話を聞くと「うちと大体一緒だな」と感じた方もいると思います。そしてその感覚はおそらく正しいと思います。大枠としては、他社と比較して特別な工夫をしているわけではないのかな、と感じています。

そのため、ここからは自分が感じたEventHubにおける開発組織の特徴を挙げる形で「開発着手前や着手中」の様子をお伝えできればと思います。

エンジニアからPdM/デザイナーに提案することがよくある

1つ目はこちら。

タイミングとしては随時発生する可能性があるものなのですが、実装に着手し始めてからが多い気がします。開発が進むにつれて解像度が上がってきたり、UIを実装して動くものが出来てきたりするからかなと思っています。

提案する際の観点としては、より良い機能にするためであったり、実装コストを抑えて素早くリリースするためであったりします。他の観点もあるとは思いますが、最たる例はこの2つだと感じます。

具体例としては、仕様に関して「なぜAという仕様ではなくBなのか」みたいな疑問を感じて質問や相談をすることがあるかと思います。そういった場合にはAやBの仕様のメリットとデメリットを考えたうえで、提案しつつ質問や相談することをEventHubでは推奨しています。

可能であれば提案ベースで話すことを推奨しているので「提案することがよくある」のは至極当然の話ではあるのですが、自分がこれまで在籍した3社と比較しても多いほうなんじゃないかと感じています。

いまやる必要があるのか?の判断軸での議論がよくある

2つ目はこちら。

たとえばPull Requestの内容として、本来のタスクでやりたかったことに加えてリファクタを含んでいたとします。そのリファクタについて「いまやる必要があるかどうか」の軸でレビュイーが意義を説いたりレビュアーが意見したりする、そういった場面を目にすることがあります。

言わずもがなですが、リファクタ自体を否定しているわけではなく、むしろ推奨しているぐらいではあります。しかし、EventHubではそれ以上にタイミングを大事にしているということかなと個人的には感じています。

「リファクタリングをしていてリリースが遅れた」となれば、顧客への価値提供がその分遅れることになります。反対に「今後の開発速度に影響を与えかねないから、いまリファクタリングをする」という判断をすることもあります。

だからこそ”いまやる必要があるのか?”を問う必要があるのだと思っています。

さいごに

けっこう具体例を盛り込んでみたのですが、いかがだったでしょうか。前回同様、開発組織の雰囲気が少しでも伝わっていれば良いなと思います。

あと、リリースフローの紹介というタイトルでしたが「EventHubにおけるフルサイクルエンジニアはどういったことをしているのか?」みたいなタイトルでもあんまり違和感がない話になったな、と書いた内容を振り返ってみて感じました。

…というわけで、ここまで読んでくださりありがとうございました!
いつもの採用情報などを記載しておいて本記事を締めさせていただきます。

jobs.eventhub.co.jp

note.com

初めてのプロジェクトマネジメントで学んだ3つのこと

こんにちは、EventHubの井上です。2024年12月から2025年3月の約3ヶ月に渡り、エピック(リリースまで数ヶ月を要する中規模以上の機能開発)のプロジェクトマネジメントを担いました。これまでマネジメント経験がない中での挑戦だったため、いくつもの壁にぶち当たりました。本記事では、プロジェクトを通して学んだ3つのことをご紹介します。

開発体制について

本題に入る前に、開発体制についてご紹介します。今回のエピックは以下のメンバー構成でスクラムによる開発を行いました。

  • プロダクトオーナー(PO)2名:メインPOとサブPO
  • デザイナー1名
  • エンジニア4名
    • チームリーダー1名
    • メンバー3名(私を含む)

上記エンジニア4名は普段から固定のチームで動いています。プロジェクトマネジメントは「エピック担当」という名称で、チームリーダーを含む人員の中から一人がエピックごとに交代制で役割を担います。今回は私がエピック担当として選出されました。

1. プロジェクトの完了に責任を持つ

マネジメントという言葉から、なんとなく「管理する」のが仕事だと思っていましたが、プロジェクトの完了に責任を持つのが仕事だと教わりました。メンバーの協力を得たり、自分自身でも手を動かしたりして、あらゆる手段を使って完了させる意識で取り組みました。

特に意識していたことは以下2点です。

  • 自身のコミットしたタスクは必ず期限内に終わらせること
  • 全てのPRに目を通し、In Reviewになった当日にレビューすること

EventHubでは1週間スプリントのスクラム開発を行っていて、JIRA上のスプリントに乗せた全てのチケットをDONEにするために、とにかく実装とレビューを頑張りました。メンバーからの協力を得るために、自分は本気でこのプロジェクトを完了させたいんだという姿勢を示すようにしました。

また、レビューサイクルを早めることでチーム全体の実装スピードが上がると感じたため、認知負荷はかかりますが、GitHubやSlackの通知がきたらすぐにレビューするようにしました。

少し根性論のようなやり方かもしれませんが、マネジメントの引き出しのない自分が小手先であれこれやるよりも、自分が誰よりもプロジェクトにコミットするのが、シンプルで効果的だと考えました。

2. 見積もりに時間をかけすぎない

今回のエピックは機能スコープが広く、設計やチケットの洗い出しに苦労しました。普段のエピックでは技術スパイクという形でプロトタイプ的なものを作り、それを元に精度の高い見積もりを行うようにしていますが、全てをカバーしたプロトタイプを作るのは現実的ではありませんでした。

そこで、機能全体に関わる部分だけスパイクを行い、その他は解像度の粗い状態でチケットを切りました。その後のチームでの見積もりも当然ざっくりしたものになりますが、各チケットの担当者が実装する中で精度を高めていった方が、結果的に早くリリースできると考えました。

副作用として、8ptと見積もっていたものが実際は21ptぐらいのサイズだったことが判明したりと、当初よりポイントが膨らんでしまうものもありましたが、不確実性の高いものから取り組むようにしたことで、大幅なスケジュール遅延は避けられました。この点については後述します。

粗い見積もりでプロジェクトをスタートすることにはリスクが伴いますが、実装してみないと分からないことも多いため、早めに実装フェーズに移ることで、結果的にリリースタイミングを早めることができたと思います。例えば、1ヶ月かけて精緻に見積もりし、2ヶ月半のスケジュールを引いてぴったり完了したらトータル3ヶ月半です。一方で、2週間で粗い見積もりをし、2ヶ月のスケジュールを引いて2週間の遅れで完了したら3ヶ月でリリースできたことになります。

ただ、計画より遅延すると今後の開発計画やユーザーコミュニケーションに問題が生じる可能性があります。そのリスクを抑えるために、序盤は仮スケジュールと周知しておき、エピックの中盤でスケジュールを確定するような形にするといいのかなと思います。実際、今回のエピックも「見積もりが粗いので仮スケジュールです」と関係者にお伝えしてから開始しました。

3. 不確実性の高いものから取り組む

見積もりの話と関連しますが、工数やスケジュールの確度を上げるためには、不確実性の高いタスクから取り組むと良いことを学びました。個々のチケットの不確実性の総和が、リリーススケジュールの不確実性だとすると、当然、不確実性の高いものから順に取り組めば、後に残るのは工数が読めているものばかりになるため、スケジュールが予測しやすくなります。何より心理的な面で安心です。

ただし、不確実性の高さよりも優先すべきは、チケット単体でユーザーに価値提供できるものです。結果的に同じ工数なら、ユーザーへの提供は早いに越したことはないからです。また、DBマイグレーションなど先に終わらせておくことで後続のタスクがスムーズに進むものは、並列でタスク消化できるように優先すべきです。

以上を踏まえ、今回のエピックでは以下の優先順位でチケットを配置しました。

  1. ユーザーに価値提供できるストーリー
  2. 複数のブロッキングになるタスク
  3. 不確実性の高いタスク
  4. その他のタスク

不確実性の高いタスクは実装方針が不明瞭なことも多く、難易度が高いです。そのため、着手することに不安を覚えます。「どのチケットやりたいですか?」という希望性で取っていくと、たいてい不確実性の高いタスクは敬遠されて後ろ倒しになってしまいます。なので、不確実性の高いことから取り組む重要性をチームで共有し、勇気を持って先に着手していくことが大事だと思います。私個人としては、少なくとも選り好みはしないように気をつけました。

余談ですが、今回のエピックでは、序盤に不確実ではないと判断していたものの、後半になって考慮漏れがあったことに気づき、大幅な作業量増になってしまったチケットがありました。この問題の原因や防止策はチームで改めて振り返る予定ですが、目の前の実装に集中するあまり、チケットの再整理に時間を割けていなかったのが個人的な反省です。このあたり、自身の技術力やマネジメントスキルの不足であると実感します。

さいごに

今回のエピックは3月末で一旦終了となる予定ですが、来月すぐに後続のエピックが控えていて、そのマネジメントも引き続き担当します。きっと最適なプロジェクトマネジメントの仕方があるのだと思いますが、たった数ヶ月で熟練することはできなさそうです。今回と同じく個人としてはフルコミットでやりつつ、周囲の方の助けをお借りして、完了責任を果たそうと思います。

初めて指揮役をやってみて気づいたのは、チームの中にプロジェクトにフルコミットしてくれる方がいるととても心強いということです。自分自身もメンバーとして入る際は、今まで以上に前のめりな姿勢でやろうと思っています。プロジェクトマネジメントはなかなかタフな仕事ではありますが、個人の成長という意味でもすごく価値のある経験でした。3ヶ月間、未熟なマネジメントで課題だらけの中、一緒に走りきってくれた皆さんに心から感謝しています。

現在、EventHubではエンジニアを募集しています。本記事を読んで少しでも興味を持ってくださった方は、ぜひカジュアル面談からご連絡ください!

jobs.eventhub.co.jp

note.com

EventHubのバリデーションを支えるclass-validatorについて

はじめに

こんにちは!! EventHub の Web エンジニアの須田です 🐱

今回は、EventHub で採用している技術の 1 つである class-validator についてと、その活用事例についてご紹介していきます 🐱

class-validator とは?

class-validator は、TypeScript および JavaScript 向けのデコレーターベースの検証ライブラリです。

主にクラスのプロパティにデコレーターを追加することで、データのバリデーションを簡単に実装できます。

ちなみに、TypeScript におけるデコレーターは、クラスやそのメンバー(メソッド、プロパティ、アクセサ、パラメータ)に特別な機能を追加するための構文(修飾子)で、@記号で始まります。

例えば、次のようなコードでは、@Contains@IsEmailなどのデコレーターを使用して、インスタンスのプロパティに対してバリデーションを実行しています。

import { Contains, IsEmail } from "class-validator";

export class PostReq {
  @Contains("EventHub") // textに'EventHub'という文字列が含まれているかをチェックする。
  text: string;

  @IsEmail() // emailが正しいメールアドレス形式かをチェックする。
  email: string;
}

github.com

class-validator のデコレーターについて

class-validator に標準搭載されているバリデーション・デコレーターの種類は、README.md に整理されているとおり、8 種類に分けられます 📝

これらの検証デコレーターを組み合わせることで、フォーム入力や API リクエストなどに対して強固なバリデーションを実装し、入力データの安全性や整合性を確保することができます。

詳細は、README.md の次のセクションをご覧ください。 https://github.com/typestack/class-validator?tab=readme-ov-file#validation-decorators

class-validator の特徴・どんな場面で利用するのか?

class-validator の特徴は、クラスの検証したいプロパティにデコレーターを付与して、バリデーションルールを定義するスタイルにあるかと思います。

既存の Class のプロパティに対して、バリデーション機能を追加できるため、Class を使ったフォーム入力や API リクエストなどのバリデーションに適しています。

EventHub で採用しているフレームワークである NestJS では、公式にも class-validator を利用していることが記載されています。

(Class ベースのプロジェクトと、class-validator は相性がいいため、NestJS では class-validator を利用しているのかと思われます。)

docs.nestjs.com

class-validator と class-transformer の関係性

class-transformer は、class-validator とセットで使用されることが多いので、その関係性について整理しておきます。

class-transformer は、プレーンオブジェクトをクラスインスタンスに変換したり、その逆を行ったりするためのライブラリです。

class-validator でバリデーションをするには、Class である必要があります。

そこで、プレーンオブジェクトをクラスインスタンスに変換するツールである class-transformer をセットで利用することが多いです。

また、変換先の Class にバリデーションデコレーターを付与していれば、class-transformer で Class 変換する際にバリデーションも合わせて実施するようにすることもできます。

上記のような組み合わせて使用できるため、EventHub では class-transformer と class-validator をセットで利用しており、 Form のプレーンオブジェクトを Request のクラスに変換する際や、API のレスポンスを Response のクラスに変換する際などに使用しています。

ちなみに NestJS 公式でも、class-validator と class-transformer はセットで紹介されています。

github.com

EventHub での class-validator の使用ルールについて 📝

次の引用にもある通り、class-validator は、ブラウザと node.js どちらの環境でも動作するので、フロントエンド でも バックエンド でも共通して使用することができます 👍

Allows use of decorator and non-decorator based validation. Internally uses validator.js to perform validation. Class-validator works on both browser and node.js platforms. 引用元: GitHub class-validator

フロントエンド でも バックエンド でも共通して使用することで、フロントエンドとバックエンドの間でデータの型を一致させることができるため、データの整合性を保ちやすいです。

そして、EventHub では、フロントエンドとバックエンド両方で TypeScript を採用しているため、どちらでも class-validator の恩恵を受けることができます。

また、EventHub のコーディング規約には、次のような記載があります。

バリデーター

  • バリデーションのロジックが class-validator に用意されているもので不十分な場合は、カスタムバリデーターとして切り出す。
  • 基本的にバリデーションはサーバー・フロントエンドの両方で実行する。何か特別な理由がある場合は都度判断してサーバーのみは許容する。フロントエンドのみでのバリデーションは基本はなしにする。(サーバーに関連がなくフロントエンドのみで完結する場合はフロントエンドのみで問題ない)

バリデーション(値の検証)はフロントエンド/バックエンドの両方で実行して、受け渡すデータの保証をしていく必要があるので、

そういう意味でも、フロントエンド でも バックエンド でも動作して共通利用できる class-validator はありがたい存在なわけです。

また、class-validator の標準デコレーターで対応できないバリデーションに関しては、独自のバリデーションルールも実装しています。

カスタム・バリデーターに関しては、後述します 📝

EventHub での class-validator の活用ポイント 📝

EventHub での class-validator の主な活用場面は、リクエストのバリデーションです。 (他にも活用場面はありますが、割愛させていただきます 🙏)

例えば、以下のようなリクエストの定義ファイルがあったとします。

import { IsNotEmpty, IsString, ValidateIf } from "class-validator";

import DataValidationError from "$shared/constants/DataValidationError";
import Spec from "$shared/constants/Spec";

// アイテム作成のリクエストクラス
export class CreateItemReq {
  @ValidateIf((_, v) => v != null)
  @IsString()
  @IsNotEmpty({ message: DataValidationError.DATA_IS_EMPTY })
  readonly nameJa: string | null;

  @ValidateIf((_, v) => v != null)
  @IsString()
  @IsNotEmpty({ message: DataValidationError.DATA_IS_EMPTY })
  readonly nameEn: string | null;

  constructor(arg: { nameJa: string | null; nameEn: string | null }) {
    arg = arg || {};

    this.nameJa = arg.nameJa;
    this.nameEn = arg.nameEn;
  }
}

上記のリクエスト(Request Class)をフロントエンドでの API Request 前にバリデーションする場合は、次のようなコードになります。 SampleCode は、一部省略していたり、命名を変更しています。

import { ValidationError } from "class-validator";
import { ItemApi } from "$cms/api/ItemApi";
import { CreateItemReq } from "$shared/types/req/cms/item/CreateItemReq";
import ClassUtils from "$shared/utils/ClassUtils";
import { isValidationErrors } from "$shared-front/utils/TypeGuardUtils";
// ・・・ 省略・・・

export const useCreateItemModal = () => {
  // ・・・ 省略・・・
  const [errors, setErrors] = useState<ValidationError[]>([]);

  const createItem = async () => {
    setLoading(true);
    try {
      // ・・・ 省略・・・

      const [nameJa, setNameJa] = useState<string>("");
      const [nameEn, setNameEn] = useState<string>("");
      const [errors, setErrors] = useState<ValidationError[]>([]);

      // サポートされている言語ではない場合は、nullを設定する
      const req: CreateItemReq = {
        nameJa: isSupportedJa ? nameJa : null,
        nameEn: isSupportedEn ? nameEn : null,
      };

      // リクエストクラスに変換する処理。
      // convertToClass()の内部では、"class-transformer"のplainToClass()や、
      // "class-validator"のvalidate()を実行している。
      const request = await ClassUtils.convertToClass(CreateItemReq, req);

      // API リクエストを実行する処理。
      // class-validator の バリデーションを通過したら、API リクエストを実行する。
      await ItemApi.create(eventKey, request);
      // ・・・ 省略・・・
    } catch (err) {
      // エラーハンドリングの処理。
      // エラーが、class-validator のバリデーションエラーの場合は、エラーをセットする。
      if (isValidationErrors(err)) {
        setErrors(err);
      } else {
        ErrorUtils.handleError(err);
        throw err;
      }
    } finally {
      setLoading(false);
    }
  };

  // ・・・ 省略・・・
};

上記の Code 内のコメントの通り、ClassUtils.convertToClass()という EventHub のユーティリティ関数で、次のような 2 つの処理を内部的に行なっています。

  1. データをクラスに変換する。("class-transformer"のplainToClass()を使っている。)
  2. バリデーションを実行する。("class-validator"のvalidate()を使っている。)

そして、このClassUtils.convertToClass()は、主にフロントエンド/バックエンドのリクエスト/レスポンス のデータをやり取りする際に使用されています。

そして、class-validator の バリデーションを通過したら、API リクエストを実行する。

もし、バリデーションでエラーが発生した場合は、catch内のエラーをセットして、UI にバリデーション結果を通知するような仕組みになります。

また、このリクエストクラスは、フロントエンドだけでなく、バックエンド(NestJS)で受け取る型としても利用されています。

import { Body, Controller } from "@nestjs/common";
// ・・・ 省略・・・

@Controller("/api/cms/item")
export class CmsItemController {
  // ・・・ 省略・・・
  @Post(`/:${ParameterKey.eventKey}`)
  async create(@EventBody() event: EHEvent, @Body() req: CreateItemReq) {
    await this.dataSource.transaction(async (em) =>
      this.service.create(em, event, req)
    );
    return {};
  }
}

このようにリクエストを検証することでデータの安全性を担保することができます。

さらに、型を共有することでフロントエンドとバックエンドの間でデータ型を統一できるため、データの整合性も保ちやすくなります。

EventHub での class-validator の活用 Tips 📝

ここからは、EventHub での class-validator の活用 Tips📝 をご紹介していきます。

条件付き検証: @ValidateIf

@ValidateIf を使うと「ある条件が成立する場合だけ」特定のバリデーションを実行することができます。

条件の判定結果がfalseの場合は、バリデーション自体がスキップされるため、エラーも発生しません。

フォーム入力や設定項目など、「条件次第で必須かどうかが変わる」ケースなどで、柔軟に使えるデコレーターになります!

条件付き検証は、ドメインロジックに依存するバリデーションを実装するのに適しています。

EventHub では、ドメインロジックに応じたバリデーション・ルールの作成に@ValidateIfはよく使われています。

例えば、多言語対応で、バリデーションメッセージを言語ごとにカスタマイズする必要があるため、次のようなバリデーションを実装しています。

  • @ValidateIfを使って、日本語イベントの時だけバリデーション・ルールを付与する。
  • @ValidateIfを使って、英語イベントの時だけバリデーション・ルールを付与する。
import { Type } from "class-transformer";
import { IsNotEmpty, IsString, ValidateIf, MaxLength } from "class-validator";

import DataValidationError from "$shared/constants/DataValidationError";
import Spec from "$shared/constants/Spec";

export default class VideoBaseReq {
  // only for ValidateIf
  @IsNotEmpty() isSupportedJa: boolean;
  @IsNotEmpty() isSupportedEn: boolean;

  // 日本語イベントの時だけ、displayNameを検証する。
  @ValidateIf((o: VideoBaseReq) => o.isSupportedJa, { always: true })
  @IsNotEmpty({
    message: DataValidationError.DATA_IS_EMPTY,
  })
  @IsString({
    message: DataValidationError.DATA_IS_INVALID,
  })
  @MaxLength(Spec.maxLength.video.displayName, {
    message: DataValidationError.DATA_IS_TOO_LONG,
    context: { constraint1: Spec.maxLength.video.displayName },
  })
  displayName: string;

  // 英語イベントの時だけ、displayNameEnを検証する。
  @ValidateIf((o: VideoBaseReq) => o.isSupportedEn, { always: true })
  @IsNotEmpty({
    message: DataValidationError.DATA_IS_EMPTY,
  })
  @IsString({
    message: DataValidationError.DATA_IS_INVALID,
  })
  @MaxLength(Spec.maxLength.video.displayName, {
    message: DataValidationError.DATA_IS_TOO_LONG,
    context: { constraint1: Spec.maxLength.video.displayName },
  })
  displayNameEn: string;
  // ・・・ 省略・・・

  constructor(arg: {
    isSupportJa: boolean;
    isSupportEn: boolean;
    displayName: string;
    displayNameEn: string;
    // ・・・ 省略・・・
  }) {
    arg = arg || {};

    this.isSupportedJa = arg.isSupportJa;
    this.isSupportedEn = arg.isSupportEn;
    this.displayName = arg.displayName;
    this.displayNameEn = arg.displayNameEn;
    // ・・・ 省略・・・
  }
}

デコレーターの重ね合わせ

デコレーターは重ね合わせて、1 つのプロパティに対して複数のバリデーションデコレーターを併用することができます。 (複数のバリデーションを実行することができます)

例えば、次のようなメールアドレスのバリデーションを考えてみます。

  • 未入力でないこと
  • 256 文字以内であること
  • ASCII 文字であること
  • メールアドレス形式であること

上記の条件をすべて満たしている場合は、メールアドレスとして有効であると判定している処理になります。

import { IsAscii, IsEmail, IsNotEmpty, MaxLength } from "class-validator";

export class MailCheckReq {
  @IsNotEmpty({
    message: "未入力です",
  })
  @MaxLength(256, {
    message: "{{constraint1}}文字までです",
    context: { constraint1: 256 },
  })
  @IsAscii({
    message: "不正な値です",
  })
  @IsEmail(
    {},
    {
      message: "不正な値です",
    }
  )
  email!: string;

  constructor(email: string) {
    this.email = email;
  }
}

class-validator のカスタムバリデーター (Custom validation classes/Custom validation decorators)

class-validator の提供する標準的なデコレーターで対応できないバリデーション・パターンの場合は、カスタムバリデーターを作成することができます。

カスタム検証には大きく分けて以下の 2 パターンがあります。

  • Custom validation classes を作成して、@Validateデコレーターから呼び出す。
  • Custom validation decorators を作成して、デコレーターとして使う。

Custom validation classes

Custom validation classes は、@ValidatorConstraintデコレーターとValidatorConstraintInterfaceのインターフェースを使用して作成します。

EventHub では、Custom validation classes を使って、様々なカスタム・バリデーションを実装しています。

次のようなVideoKeyValidatorという Custom validation classes では、videoTypeによって受け取ったvideoKeyの検証を違う方法で行っています。

import {
  isAscii,
  IsIn,
  IsNotEmpty,
  IsString,
  IsUrl,
  Validate,
  ValidationArguments,
  ValidatorConstraint,
  ValidatorConstraintInterface,
} from "class-validator";

import DataValidationError from "$shared/constants/DataValidationError";

enum VideoType {
  videoType1 = "videoType1",
  videoType2 = "videoType2",
}

/**
 * videoTypeによってvideoKeyをvalidateする。
 */
@ValidatorConstraint({ async: false })
class VideoKeyValidator implements ValidatorConstraintInterface {
  validate(videoKey: string, args: ValidationArguments) {
    const req = args.object;
    switch (req.videoType) {
      case VideoReq.videoType1: // VideoType1の場合は、URLであることを検証
        return IsUrl(videoKey);
      case VideoReq.videoType2: // VideoType2の場合は、URLではないことを検証
        return !IsUrl(videoKey) && isAscii(videoKey);
      default:
        // videoTypeが不正な場合は、バリデーションエラーとする。
        return false;
    }
  }

  defaultMessage(args: ValidationArguments) {
    const req = args.object;
    switch (req.videoType) {
      case VideoReq.videoType1:
        return DataValidationError.VIDEO_URL_IS_INVALID;
      case VideoReq.videoType2:
        return DataValidationError.VIDEO_ID_IS_INVALID;
      default:
        // videoTypeが不正な場合は、不正なデータとしてメッセージを返す。
        return DataValidationError.DATA_IS_INVALID;
    }
  }
}

Custom validation classes は、@Validateデコレーターを使って呼び出すことができます。

Custom validation decorators

Custom validation decorators は、registerDecoratorメソッドを使用して作成します。

EventHub では、Custom validation decorators を使って、様々なカスタム・バリデーションを実装しています。

例えば、ValidatePasswordという Custom validation decorators では、「password の値が必要な文字を 2 種類以上含んでいる」ことを検証するようにしています。

import { registerDecorator, ValidationOptions } from "class-validator";

import DataValidationError from "$shared/constants/DataValidationError";

/**
 * passwordの値が必要な文字を含んでいるかをvalidateする。
 * password文字列は次の4種類の内、2種類以上を含む必要がある。
 *
 * - 英小文字
 * - 英大文字
 * - 数字
 * - 記号
 */
export const ValidatePassword = (validationOptions?: ValidationOptions) => {
  return function (object: Record<string, any>, propertyName: string) {
    registerDecorator({
      name: "validatePassword",
      target: object.constructor,
      propertyName: propertyName,
      options: validationOptions,
      validator: {
        validate(value: string): boolean {
          if (typeof value !== "string") {
            return false;
          }

          const containsLowercase = /[a-z]/.test(value);
          const containsUppercase = /[A-Z]/.test(value);
          const containsNumber = /[0-9]/.test(value);
          // ASCIIコードの中からsymbolを順に書いた。
          const containsSymbol = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/.test(
            value
          );

          // 上のboolean変数4つのうち、trueの数を計算する。
          const numOfCharTypes = [
            containsLowercase,
            containsUppercase,
            containsNumber,
            containsSymbol,
          ].filter((e) => e).length;
          return numOfCharTypes >= 2;
        },
        defaultMessage() {
          return DataValidationError.PASSWORD_NOT_CONTAIN_EXPECTED_CHARS;
        },
      },
    });
  };
};

さいごに

今回は、EventHub で採用している技術の 1 つである class-validator についてと、その活用事例についてご紹介していきました 🐱

class-validator でフロントエンド, バックエンドの値を検証する仕組みは、プロダクトの信頼性を高めるので、これからも活用していきたいです 💪

もしこれを見て EventHub に興味をお持ちいただけたら、ぜひ以下のリンクから詳細をご覧ください!

jobs.eventhub.co.jp

note.com

参考・引用

class-validator GitHub

https://github.com/typestack/class-validator

class-validator npm

https://www.npmjs.com/package/class-validator

TypeScript Decorators

https://www.typescriptlang.org/docs/handbook/decorators.html#introduction

NestJS Validation

https://docs.nestjs.com/techniques/validation

class-transformer GitHub

https://github.com/typestack/class-transformer

class-transformer npm

https://www.npmjs.com/package/class-transformer

マネージャへの挑戦の壁をなくすために

はじめに

こんにちは!EventHub CTOの井関です。

これは EventHub Advent Calendar 2024 の24日目の記事です。昨日はCorpIT/RevOpsマネージャの横山さんの 2024年は、私の筋トレ元年 でした。そちらもぜひご覧ください!

adventar.org

今年は、普段人前に出るのが苦手な私が人生初のピッチイベント(CTO of the year 2024)に出場することになりました。これは、人事の磯さんのサポートのおかげです。イベント当日の内容については、ログミーさんが記事にまとめてくれていますので、興味のある方はぜひご覧ください!

logmi.jp

ピッチは6分間という限られた時間内で伝えたいことを絞り込む必要があり、内容を省略する作業は非常に難しかったです。今回は、その中でも特に省略した「マネジメント体制」について詳しくまとめたいと思います。

現在EventHubでは14名のエンジニアで4つのチームを組成しています。

各チームのマネージャにはプロダクト・テクノロジー・プロジェクト・プロダクトマネジメントの4つのマネジメント領域全てを任せる構成をとっています。

なぜ、このような体制をとったのか事業面・組織面に関しては伝えたことがありますが思いの部分をあまりまとめたことがなかったので、その点を今回はまとめます。

マネージャの役割

ピープルマネジメントの壁

創業初期は代表の山本と二人で創業したため、肩書はCTOでありながら1人で設計から実装までの全てをこなすような体制でした。

最初は資金が少なく、正社員を雇う余裕もなかったため、業務委託を中心にエンジニアを少しずつ増やしていきました。そして、次第にプロダクトマネジメントやプロジェクトマネジメントなどの領域も私が担当するようになりました。 事業が成長するにつれて、エンジニアも増え、ピープルマネジメントの必要性を感じ始めました。しかし、この「人をマネジメントする」ことは予想以上に難しく苦労しました。

この壁を乗り越えようと、知り合いや紹介してもらったCTO・EMの方々10人ほど話を聞いたのですが、それぞれの意見がまったく異なっていて、逆に悩みが深くなりました。

階層構造を作らずに50人ぐらいまではCTO一人で組織を見た方が良いという意見があると思ったら、早い段階で構造化し組織を分割すべきという意見もありました。CTOはコードを書かずに組織設計に回るべきという意見もあれば、CTOが率先してコードを書き方針を示すべきという意見もありました。

どの方も現職で成果を出していたのでどれも正解なのは間違いないとは思いますが、事業・組織、そしてCTOの思考によって全く逆の方針をとっていることもあり驚きました。ただ、全員が一貫していたのは自身で考え抜いて経験を重ね今のスタイルを構築したことが分かり、単に数時間ヒアリングしただけの自分が見よう見まねで取り入れても成果につながるような単純なものではないことも思い知らされました。

壁の乗り越え方

組織を成長させるためには、私自身がピープルマネジメントのスキルを身につけることが必要でした。しかし、スキルの向上と組織の成長のペースが一致せず、スキルが追いつかないという現実に直面しました。

そのギャップを埋めるために、代表のりえさんにサポートを頼んだり、チームメンバーに支えてもらいカバーしてもらうこともたくさんありました。ピープルマネジメントを一人でこなすのは難しかったですが、周りの力を借りて自分の得意領域で成果を出しながら進めることができました。

周りに支えてもらい苦労しながらの挑戦でしたが、少しずつスキルを身に着けたことで見えた景色は大きく変わりました。ピープルマネジメントのスキルが身についたことでプロダクト・プロジェクトマネジメントにまつわる業務での動きに影響があったりと、各スキルが他の領域で生きる場面が多いことにも気づきました。

チームとしての成果を最大化するために

チームマネージャに求めている役割は端的にまとめてしまうとチームの成果の最大化です。

それを実現する方法はチームマネージャ自身の強みや志向性も影響しますし、メンバーの強み・志向性にも大きく影響します。そのため役割を限定してしまい、渡す権限が不十分になってしまうとその人にあった方法を模索することができず思うような成果を出せなくなってしまいます。

私がピープルマネジメントの壁にぶつかった時にテクノロジー・プロジェクト・プロダクトの領域から離れてピープルマネジメントだけに集中しなければならないという制約が課されていたら、相当しんどい状況になっていたと思いますし、どこかで挫折してしまったと思います。自分の得意領域を活かしつつ、周りの協力を得ながら足りないスキルを補填していける環境は創業者としての権限や信頼があったからこそ実現ができたものだと思います。

このような環境を創業者でなくても作れる要素は何かと考えてとった方法が4つのマネジメント領域を全て任せる形です。

マネージャという役割に関して

組織が成長してくためには、マネージャーの数を増やすことが重要です。しかし、これは単にマネージャの肩書を持った人を増やすことを意味するわけではありません。本質的には、チームや組織の成果に責任を持ち、そのために行動する人を増やすことが求められます。

二日前に岡本さんが記事の最後の方で エンジニアの「より良くしたい」を拡げる仕事 とまとめてくれてますが、これはまさに上のようなメンバーの割合を増やすための仕事です。

メンバーとマネージャの間には根本的には壁があります。任せられる領域・責任が広がることでどうしても求められるスキルなどは変わってきます。そのため、メンバーからマネージャになることは非連続で急な変化を求められてしまうものになります。

ただ、ここの段差が大きいとよりメンバーとマネージャの意識にも差が出てきてしまいもっとマネジメントに挑戦しづらい環境になります。誰しもがチームの成果に向き合い行動ができる環境を作り出すためには、メンバーからマネージャへの挑戦をよりなめらかで連続的にする必要があります。

メンバーとマネージャの段差をなめらかにすることで、少しずつマネージャに挑戦できる環境を作ることができ意欲がある方には少しずつひとつ上の業務に挑戦できる環境を作れます。また、挑戦したいメンバーが増えればチーム成果に責任を持とうと行動する方が増えてよりマネージャへの挑戦のハードルを下げることができ挑戦しやすい環境になります。

この状態を作るために4つのマネジメント領域全ての権限を渡せる環境を作りました。

一見すると4つのマネジメント全てを求めることは何でもできる万能人間しかマネージャになれないというようにハードルを上げてしまっているように見えますが、駆使できる能力の幅を広げることでむしろ挑戦するハードルを下げることができます。

最後に

メンバーとマネジメントの段差を少なくするための施策はまだまだ途中で現時点でも多くの段差がある状態です。ただ、今ではマネジメント未経験でも挑戦してくれた方が増えてその知見を蓄積していくことで少しずつその段差をなめらかにしていくことができています。

ぜひ、そんな挑戦をしてみたいという方は連絡をお待ちしてます!!

jobs.eventhub.co.jp

次の25日目の記事は、弊社代表の山本です!! お楽しみに!