チーム開発でボーイスカウト・ルールを実践する

こんにちは、EventHubの井上です。今回は『プログラマが知るべき97のこと』で紹介されている「ボーイスカウト・ルール」について、チーム開発でどのように実践しているかをお話しします。このルールを適用することで、コードの品質向上や技術的負債の削減につながることを実感しており、具体的な手法を共有します。

ボーイスカウト・ルールとは何か

ボーイスカウト・ルールとは、既存のコードに手を加える際に、そのコードに関連する箇所をより良い状態にリファクタリングすることです。簡単にいうと「来た時よりも美しく」です。例えば、以下のようなJavaScriptコードがあったとします。

const add = (a, b) => {
return a + b;
}

これを以下のように修正します。

// 整数を足し合わせる
const add = (num1, num2) => {
  return num1 + num2
}

説明コメントを追加し、変数名を変更し、インデントを整えました。挙動に変更はありませんが、変更前と比べるとコード・リーディング時に生じる疑問を減らすことができました。このように、実装時に関連処理をできる範囲できれいにするのが、ボーイスカウト・ルールです。

ボーイスカウト・ルールを実践しないとどうなるか

日々の開発で軽微な修正を重ねていくことをしない場合、処理の複雑性が増したり、サポート終了が近づいているライブラリを使い続けるなど、技術的負債が溜まっていきます。それを返済するには、どこかのタイミングで時間を確保してリファクタリングする必要が出てきます。競合のプロダクトが日々進化していく中で、その工数を確保する意思決定は簡単ではありません。実装者だけでなく、QAを含めたレビュワーの工数も必要になります。

また、リファクタリングの規模が大きくなるほど、その過程で新たなバグを生み出してしまう(デグレ)リスクが高まります。家の掃除に例えると、ゴミ屋敷になってしまってからではもう手遅れなので、日々の小さな工夫で、少しずつ綺麗にしていけばいいのではないでしょうか。

ボーイスカウト・ルールを実践する上での注意点

ボーイスカウト・ルールを実践する際には注意すべき点があります。「デグレのリスク増加」と「デリバリースピードの低下」です。平たく言うと、余計なことをしてバグを発生させたり、リファクタリングに時間をかけすぎることでリリースが遅くなってしまうリスクです。こういった負の側面から、触らぬ神に祟りなしということで、よろしくないコードに目を瞑りながら開発されている方も多いと思います。では、これらのリスクを最小化しつつ実践するにはどうすればよいのでしょうか。

デグレのリスクを最小化する

デグレのリスクを下げる上で、以下2点が重要だと考えています。

  1. 基本的に軽微な修正に留める
  2. テストコードがある状態で実施する

軽微な修正とは、コメント追加、lint修正などです。これらの修正は挙動に影響を与える可能性が低いため、安全に実施することができます。

ただ、処理の共通化やライブラリの刷新など、軽微ではない修正を実施することもあります。その際に、テストコードがない状態で修正を加えてしまうと、壊れていることに気づけないままリリースしてしまうかもしれません。なので、挙動に影響のある変更は、テストコードがあることを前提にすべきだと思います。

デリバリースピードの低下を抑える

実装工数が増えることは避けられないため、レビュー負荷削減の観点で、以下2点を意識するようにしています。

  1. コーディング規約に沿った形で改修する
  2. 差分が大きくなる場合はPR(Pull Request)を分ける

「来た時よりも美しく」の美しいの定義は人によって異なります。実装者はこれが美しい状態だと思っていても、レビュワーがそうではないと感じれば、PRの議論が白熱し、なかなかマージできなくなってしまいます。一方、コーディング規約に沿った変更であれば、意思決定がスムーズにいきやすく、レビューのリードタイムを削減することができます。

また、修正範囲が広がるとレビュワーの負荷が高くなるため、状況に応じてリファクタリング用のPRとメイン実装のPRを分けることで、レビューがスムーズにいきやすくなります。

ボーイスカウト・ルールの実践例

おさらいになりますが、個人としてボーイスカウト・ルールを実践する際に意識しているポイントは以下の4つです。

  1. 基本的に軽微な修正に留める
  2. テストコードがある状態で実施する
  3. コーディング規約に沿った形で改修する
  4. 差分が大きくなる場合はPRを分ける

ここからは、私が直近でどのようにボーイスカウト・ルールを実践したか非常に簡単な例を一つご紹介します。

コメントで@deprecatedアノテーションを追加する

EventHubではクラウドインフラとしてAWSを利用しています。AWS関連の関数はAWSUtilsというファイルにまとめられており、今回はある関数に改修を加えました。

この関数ではAWS SDK for JavaScript v2が使用されていましたが、v2は2025年9月にサポートを終了するというアナウンスがされていることを知りました。

aws.amazon.com

これを受けて、この関数については、SDK v3を使った書き方に変更しました。

この変更を行うだけでは、通常の実装と何ら変わりません。ここからがボーイスカウト・ルール実践の話になります。

他にもSDK v2を使用した関数がありましたが、他の関数に手を加えるのはデグレのリスクや工数の観点で断念しました。そこで、以下のようなJSDoc形式の関数コメントを追加することにしました。

/**
* AWS S3にファイルをアップロードする
*
* @deprecated
* sdk v2は2025年9月にサポートが終了するため、v3での実装を推奨します。
* 新たに処理を追加する場合は、v3を使用して再定義してください。
* @see https://aws.amazon.com/jp/blogs/developer/announcing-end-of-support-for-aws-sdk-for-javascript-v2/
*/
public static async uploadToS3(

このように@deprecatedアノテーションを付けることで、ソースコード上では以下のように打ち消し線が引かれ、非推奨の旨が表示がされるようになります。(VSCode、TypeScriptの場合)

@deprecatedアノテーションを付けた時の表示

この変更によって、後続の開発者はdeprecatedの関数を避け、v3対応を検討しやすくなります。結果として、将来のコード修正コストを削減し、技術的負債の再生産を未然に防ぐことができます。また、当然ですが、コメント追加のみのためデグレの心配はありません。

このように、小さな変更でも積み重ねることで、ソースコードの品質向上に繋がります。

tipsとして、直したい気持ちはすごくあるけど時間がないという時には、とりあえずコメントだけ残しておくのをおすすめしたいです。

直すだけではなく負債を増やさない心がけも大事

有名な話で、ソースコードの割れ窓理論というのがあります。建物の窓を割れた状態で放置すると、他の窓を割る心理的ハードルが下がりどんどん割られてしまう現象のことです。

ソースコードも同じで、汚いコードを残すと再生産されていってしまう傾向にあります。一度レビューを経たものだから、真似しておけば合意を得られるはずと考えるためです。ボーイスカウト・ルールで少しずつ綺麗にしていくのと同様に、日々の実装やレビューでそのようなコードを生まないようにする心がけが大事だと思っています。

まとめ

ボーイスカウト・ルールは、日々の小さな改善を積み重ねることで、プロダクトの健全性を保つための強力な指針です。チームの誰か一人がこのルールを実践し始めると、その姿勢が自然と周囲に伝わり、広がっていきます。私自身、今回ご紹介したボーイスカウト・ルールは、他のメンバーに影響を受けて実践し始めました。ぜひ、皆さんの開発現場でも取り入れてみていただければと思います。

EventHubにはコード品質を良くしていこうという文化があり、私はそれがとても気に入っています。テックブログ以外にも会社全体の雰囲気を知るためのブログやXもありますので、もし興味を持っていただけたらそちらもぜひ見てみてください!(エンジニア絶賛採用中です!)

jobs.eventhub.co.jp

note.com

x.com

 

Reactで動画プレイヤーを作成するならreact-playerがおすすめ!

はじめに

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

今回、React で新しい動画プレイヤーを実装するための技術調査をしたので、その内容についてと、 React で動画プレイヤーを作成するなら react-player が簡単に実装できて、おすすめなのでご紹介していきます🐱

React 動画ライブラリ比較結果

2024年9月時点で、React の動画プレイヤー・ライブラリで調べて出てきたものを比較してみました👀

候補は「React 動画ライブラリ」で調べてでてきた次の 5 つです 🙏

  1. react-player
  2. mux-player-react
  3. react-video-js-player
  4. video-react
  5. video.js

npmtrends.com

結論:react-player がよさそう

判断基準は、次のとおりです👀✨

  1. 要件を満たす機能を備えていること。
  2. React 対応のライブラリであること。
  3. 正式リリースがされており、安定版であること。
  4. 定期的にメンテナンスがされていること。
  5. npm trends や GitHub Star などを参照して、使用率や人気がある状態であることが望ましい。

調べた感じ「React ✖️  動画ライブラリ」の選択肢が、あまりない状況に見えました👀

react-player とは?

react-player は、React アプリケーションで動画や音声などのメディアを再生するためのオープンソースの React コンポーネントです。

YouTube、Vimeo、Mux、Twitch、SoundCloud、Facebook など、さまざまなプラットフォームからのコンテンツをサポートしています。

また、シンプルな API と豊富なプロパティを提供しており、メディアプレーヤーのカスタマイズや制御が簡単です 💪🥺🔥

npm や GitHub は、次のとおりです 👀✨

npm: react-player

www.npmjs.com

GitHub: react-player

公式 Doc がない代わりに、GitHub の README.md にて設定できる Option に関する説明が細かく掲載されています👀✨

github.com

React Player デモ環境

react-player のデモで、いろいろな動画タイプや、パラメーターを試すことができます 🙏

cookpete.github.io

ちなみに、react-player のメンテナンスは、Mux チームが引き継ぐらしいです 🙌

(Mux は、アメリカの同名のソフトウェア会社が運営する、API ベースの動画配信サービスです)

参照:https://www.npmjs.com/package/react-player#-the-future-of-reactplayer

react-player で動画プレイヤーを作成する

それでは、実際に react-player で動画プレイヤーを作成していきます。

react-player をインストール

npm install react-player

yarn add react-player

シンプルな Video Player を実装してみる

試しに、シンプルな Video Player を実装してみると、 これぐらいのCode量で、簡単な Video Playerが実装できます👀✨

import ReactPlayer from "react-player";

interface SimpleVideoPlayerProps {
  videoUrl: string;
  isLoop?: boolean;
  isAutoPlay?: boolean;
  isControls?: boolean;
}

export const SimpleVideoPlayer = ({
  videoUrl,
  isLoop,
  isAutoPlay,
  isControls,
}: SimpleVideoPlayerProps) => {
  return (
    <ReactPlayer
      url={videoUrl}
      width={"100%"}
      height={"100%"}
      playing={isLoop} // 自動再生
      loop={isAutoPlay} // ループ再生
      controls={isControls} // 動画の操作が可能かどうか
    />
  );
};

React Player の Option について

react-player の README には、渡せる Props や Callback 系などの設定値の一覧がまとまっているので、これを見れば何ができるのかは、把握できます🙆‍♂️

また、Youtube, Vimeo, Mux などの各動画配信サービスの設定プロパティにも対応しているのも、Goodなポイントです🙌

Props

主な Props は、次のとおりです👀✨

Prop Description Default
url 再生するビデオまたは曲のURL。array または MediaStream オブジェクトを指定可能。
playing true または false を設定してメディアの再生や一時停止を行う。 false
loop true または false を設定してメディアをループ再生する。 false
controls ネイティブプレーヤーコントロールを表示するかどうかを設定。Vimeoビデオの場合、コントロールを非表示にするにはビデオ所有者が有効にする必要がある。 false
light true に設定するとビデオのサムネイルのみが表示され、クリックでフルプレーヤーが読み込まれる。プレビュー画像をオーバーライドするには画像URLを渡す。 false
volume プレーヤーの音量を 0 から 1 まで設定。null の場合、全プレーヤーでデフォルトの音量が使用される。 null
muted プレーヤーをミュートにする。volume が設定されている場合のみ機能。 false
playbackRate プレーヤーの再生速度を設定。YouTube、Wistia、およびファイルパスでのみサポートされている。 1
width プレーヤーの幅を設定。 640px
height プレーヤーの高さを設定。 360px
style ルート要素に inline styles を追加。 {}
progressInterval onProgress コールバックの間隔をミリ秒単位で設定。 1000
playsinline 対応している場合に playsinline 属性を適用。 false
pip true または false を設定してピクチャー・イン・ピクチャーモードを有効化または無効化する。ファイルURLで再生している場合のみ、特定のブラウザでサポートされている。 false
stopOnUnmount pip を使用している場合、stopOnUnmount={false} を使用してReactPlayerがアンマウントされてもピクチャー・イン・ピクチャーモードで再生を続けるようにする。 true
fallback 遅延読み込みを使用している場合に使用するフォールバック要素またはコンポーネント。 null
wrapper コンテナ要素として使用する要素またはコンポーネント。 div
playIcon ライトモードで再生アイコンとして使用する要素またはコンポーネント。
previewTabIndex ライトモードで使用するタブインデックスを設定。 0
config 各プレーヤーの設定を上書きするオプションを指定。config propを参照。

参照・引用: https://github.com/CookPete/react-player?tab=readme-ov-file#props

Callback Props

主な Callback Props は、次のとおりです👀✨

Prop Description
onReady メディアがロードされ再生の準備が完了した際に呼び出される。playingtrue に設定されている場合、メディアは即座に再生される。
onStart メディアの再生が開始されたときに呼び出される。
onPlay メディアが再生または一時停止後やバッファリング後に再開されたときに呼び出される。
onProgress playedloaded の進捗状況を割合で提供するコールバック。秒単位で playedSecondsloadedSeconds も提供される。例: { played: 0.12, playedSeconds: 11.3, loaded: 0.34, loadedSeconds: 16.7 }
onDuration メディアの長さ(秒単位)を含むコールバック。
onPause メディアが一時停止されたときに呼び出される。
onBuffer メディアがバッファリングを開始したときに呼び出される。
onBufferEnd メディアのバッファリングが完了したときに呼び出される。ファイル、YouTube、Facebookで動作する。
onSeek seconds パラメータでメディアがシークされたときに呼び出される。
onPlaybackRateChange プレーヤーの再生速度が変更されたときに呼び出される。YouTube、Vimeo、Wistia、およびファイルパスでサポートされている。
onPlaybackQualityChange プレーヤーの再生品質が変更されたときに呼び出される。YouTubeでのみサポートされている(有効にした場合)。
onEnded メディアの再生が終了したときに呼び出される。looptrue に設定されている場合は発火しない。
onError メディアの再生中にエラーが発生したときに呼び出される。
onClickPreview ユーザーが light モードのプレビューをクリックしたときに呼び出される。
onEnablePIP ピクチャー・イン・ピクチャーモードが有効になったときに呼び出される。

参照・引用: https://github.com/CookPete/react-player?tab=readme-ov-file#callback-props

Youtube Video Player を実装する

続いて、Callback Props を活用した Youtube Video Player を実装してみます。

Callback Props の挙動を確認するために、再生中かどうかを確認するようなFlag判定を持たせています。

また、onProgressに関しては、動画の再生状況のデータの内容を確認できるようにlog出力を仕込んでみます。

実際に実装した画面は、次のような感じです👀✨

import { Fragment, useState } from "react";
import ReactPlayer from "react-player";
import type { OnProgressProps } from "react-player/base";

interface YoutubeVideoPlayerProps {
  videoKey: string; // youtube video key
  autoPlay?: boolean; // 自動再生フラグ
  originUrl?: string; // ルートURL
}

// TODO: 実際に実装する際は Logic は Custom Hook に切り出す
export const YoutubeVideoPlayer = ({
  videoKey,
  autoPlay,
  originUrl,
}: YoutubeVideoPlayerProps) => {
  // 再生中フラグ
  const [isPlaying, setIsPlaying] = useState(false);

  const handlePlayOn = () => {
    setIsPlaying(true);
  };

  const handlePlayOff = () => {
    setIsPlaying(false);
  };

  // 再生中に定期実行されるコールバック
  const handleProgress = (progress: OnProgressProps) => {
    console.log("onProgress Called", progress);

    const {
      played, // 動画の再生済み部分を全体の割合で示した値 (0〜1)
      playedSeconds, // 動画の再生済み時間を秒単位で示した値 (秒)
      loaded, // 動画の読み込み(バッファリング)済み部分を全体の割合で示した値 (0〜1)
      loadedSeconds, // 動画の読み込み済み時間を秒単位で示した値 (秒)
    } = progress;

    // 再生済みのパーセンテージを表示
    console.log(`再生位置(%): ${(played * 100).toFixed(2)}%`);
    // 再生済みの時間を表示
    console.log(`再生位置(秒): ${playedSeconds.toFixed(2)}秒`);

    // 読み込み済みのパーセンテージを表示
    console.log(`読み込み済み(%): ${(loaded * 100).toFixed(2)}%`);
    // 読み込み済みの時間を表示
    console.log(`読み込み時間(秒): ${loadedSeconds.toFixed(2)}秒`);
  };

  return (
    <Fragment>
      <ReactPlayer
        url={`https://www.youtube.com/watch?v=${videoKey}`}
        width={"100%"}
        height={"100%"}
        playing={autoPlay} // 自動再生
        loop={false} // ループ再生
        controls={true} // 動画の操作が可能かどうか
        onStart={handlePlayOn} // 再生開始時 Callback Func
        onPlay={handlePlayOn} // 再開する時 Callback Func
        onPause={handlePlayOff} // 一時停止時 Callback Func
        onEnded={handlePlayOff} // 再生が終了した時 Callback Func
        progressInterval={1000} // onProgressの実行間隔(秒) Default: 1000(1秒)
        onProgress={handleProgress} // 再生中に定期実行される Callback Func
        // 各プレーヤーごとの独自設定
        config={{
          /**
           * YouTubeのプレーヤーのパラメータ設定 Docs
           * https://developers.google.com/youtube/player_parameters?playerVersion=HTML5&hl=ja
           */
          youtube: {
            playerVars: {
              autoplay: autoPlay ? 1 : 0, // 自動再生
              playsinline: 1, //  iOS 上の HTML5 プレーヤーで動画をインライン再生する
              origin: originUrl, // ルートURL指定
              rel: 0, // パラメータの値が 0 に設定されている場合、関連動画は表示されません。
            },
          },
        }}
      />
      <p className="flex items-center justify-center mt-5 text-lg">
        再生中: {isPlaying ? "Yes" : "No"}
      </p>
    </Fragment>
  );
};

さいごに

今回は、React で新しい動画プレイヤーを実装するための技術調査として、react-playerを使って動画プレイヤーを試しに実装してみました。

react-player は、動画プレイヤーを簡単に実装できオプションも豊富なので、おすすめです。

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

jobs.eventhub.co.jp

note.com

参考・引用

zenn.dev

www.npmjs.com

github.com

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

はじめに

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

今回はEventHubで開発した機能がどのようなフローで本番環境にリリースされるかについて、ご紹介できればと思います。

採用面接の際、リリースフローに関する質問をいただくことがありますし、質問はせずともCI/CDがどんな感じなのか知っておきたい方もいるかと思いますので、ぜひ参考にしていただければなと考えています。

記事のスコープ

話すこと

  • 開発サイクル
  • ブランチの運用フロー
  • CI/CDの概要

話さないこと

  • リリース後の話
  • 開発着手前や着手中の細かい話

開発サイクル

EventHubでは週に1度リリースを行うサイクルで開発を行っています。

そのサイクルでリリースを行えるようにするために日々テストコードをメンテナンスしており、何らかの機能開発や修正を行ってもデグレードが起きていないことをできる限り確認できるようにしています。

さらに本番環境(production)と同等の構成で検証可能なQA環境(qa)を用意しており、そこでテストを行ったり開発以外の部署に方に触ってもらったりもしています。

開発サイクルの話題を冒頭に持ってきた理由としては、どのぐらいの速度感でプロダクトをデプロイしていきたいかがブランチの運用やリリースフローといった設計の妥当性に関わると考えているからです。

ブランチの運用とリリースフロー

先程の開発サイクルは、以下の図のようなブランチ運用によって成り立っています。

なお、ローカルでのブランチ運用に関してはエンジニアによって異なるので、一例として捉えていただければと思います。

通常のブランチ運用

  1. 開発時にはmainブランチから開発用にローカルブランチを作成
    • 開発が済んだらPull Requestを作成してレビューを行い、2件以上の承認を得たらmainにマージ(Squash)します
    • ちなみにEventHubではtopicブランチごとに動作確認が可能な環境がAWS上で自動的に構築されるので、その環境を使って都度検証を行います
  2. 週に1度、mainブランチでリリースバージョンのタグを作成し、それを元にしてqaブランチ向けにPRを作成
    • productionブランチ向けのPRは前回リリース後に、その時点のqaブランチを元にして作成してあります
    • その後、qaとproduction向けのPRをマージしておき、リリースに備えます
  3. 翌日のリリース予定時刻になったら、qaとproductionのCIを開始させて自動デプロイ

このフローは私が入社(2023年6月)するよりも前から存在しており、ブランチ運用やリリースフローのドキュメントの履歴を見ると2021年9月には既に出来上がっていたようで、そこからほぼ変わっていないみたいです。

さて、ここまで紹介してきたEventHubでのブランチ運用やリリースフロー、および開発サイクルですが、以下のような良さがあるのではないかと考えています。

  • 機能追加や修正内容に応じてヘルプページの作成/編集などを行う猶予がある
    • 週に一度Sprint Reviewと称して、QA環境や本番環境にリリースされる内容を社内に共有しています。小さい追加修正などはそこで初めて知ることがあるものの、実際にリリースされるまでの時間があるのでヘルプページの作成/編集や特定の顧客への連絡などが必要であっても、うまく回せていると感じています
  • 別の機能を開発中にQA環境でも発生する不具合が見つかったら、本番環境にリリースされるまでの間に修正ができる
    • だからといって、普段の検証をおろそかにしてもいいわけではないのですが、リカバリー可能であることで多少は心に余裕ができますね
  • いわゆるトランクベースな開発のメリットを一部享受できる
    • わかりやすい例としてはコンフリクトが起きづらくなりますし、PullRequestも小さくなるのでレビューしやすくなることでしょうか

mainブランチにマージしてから約2週間後に本番環境へリリースされるわけですが、もしかすると人によっては遅いと感じられたりするかも知れません。

しかし、以上のような事柄を加味すると、ちょうど良いサイクルなのではないかなと私は感じています。

CI/CDの概要

EventHubではCI/CDのツールとして、Circle CIを採用しています。

CI/CDにはQA環境や本番環境へのデプロイ以外に、日々の開発を支えるパイプラインも用意しています。開発ブランチ用のパイプラインでは、主に「自動テスト」と「ブランチごとの検証環境構築」のJOBが実行されるようになっています。

EventHubの自動テストの種類の割合は、バックエンドのコードに対してのテストがざっくり90%を占めているのではないかと思います(カバレッジ等の指標は追っていないため個人の感覚です)。

もちろん、フロントエンドでも必要であればコンポーネントのテストを書いたり、独立したfunctionのテストを書いたりすることもあります。また、ヘッドレスブラウザを使ったEnd-to-Endの自動テストを書くこともあります。

Cicle CIはSlackと連携させているので、失敗するとすぐにわかるようになっています

また、EventHubのリポジトリはモノレポの構成を採用しているため、フロントエンドとバックエンドのソースコードが1つのリポジトリで管理されています。このモノレポの良さの1つとして挙げられそうなのは、CI/CDの構築のしやすさがあるのではないかなと感じています。

先程も少し触れましたが、topicブランチごとに動作確認が可能な環境がAWS上で自動構築されるようになっています。

こういった仕組みを異なるリポジトリを組み合わせて行おうとすると、なかなか複雑なものになると思いますし、後から参画した人にとっても触れづらい領域になってしまいかねないと思うので、EventHubぐらいの規模感であればモノレポで開発できることはメリットだと捉えています。

さいごに

「EventHubの開発やリリースってこんな感じでやってるんだな」というのが良くも悪くも伝わっていれば幸いです。

そして、テックブログ以外にも会社全体の雰囲気を知るためのブログやXもありますので、もし興味を持っていただけたらそちらも是非見てみてください!

jobs.eventhub.co.jp

note.com

x.com

TSKaigi 2024では配信プラットフォームとしてEventHubが採用され、弊社CTOの井関がスポンサーLTで登壇したときの動画がありますので、こちらも是非。

www.youtube.com

EventHub メール配信の仕組み

こんにちは!EventHubの勝田です。

イベントマーケティングにおいて、メール配信は欠かすことができません。 イベント開催前のリマインド、開催後のアンケート、次回イベントの告知など、参加者とのエンゲージメントを高める重要な役割を担っています。 適切なタイミングでメールを送ることで、参加者の関心を継続的に高めることができます。

EventHubが扱うメール

EventHubでは、大きく分けて2種類のメールを扱っています。

  1. 参加者のアクションをトリガーに配信するメール
    イベント申込み完了時などの特定のアクションに紐づいて自動で送信されるメールです。

  2. 主催者が自由に文面、送信対象、送信タイミングを制御できるメール
    主催者が柔軟にメールの内容や送信先、タイミングを設定できる自由度の高いメールです。

今回は、2つ目の主催者制御メール(以降、リマインドメールと呼びます)について解説していきます。

リマインドメールの特徴

リマインドメールでは、主催者が自由にメールの文面を編集でき、送信対象や送信タイミングを細かく制御できます。(HTML形式でリッチなデザインを施すことも可能です。)

リマインドメールの流れ

この自由度の高さによって、以下のようなユースケースが可能になります。

  • 参加者を属性(会社、役職など)別に分けて、それぞれに合わせた文章やタイミングでメールを送る
  • イベント前と当日で異なるメールを送る
  • アンケート実施時にメールを送る
  • 次回イベントの告知メールを送る

メール文面は、参加者ごとの氏名やその他属性情報でカスタマイズすることもできます。

実現方法

EventHubはメール配信基盤としてSendGridを利用しています。 SendGridは、メール配信だけでなく、送信メールのイベント(開封、クリックなど)をWebhookを通してPOSTする機能を提供しています。 EventHubではこれらの機能を活用し、配信と配信状況の可視化を実現しています。

配信状況の可視化

配信状況の可視化には、SendGridのEvent Webhookを使用しています。

Event Webhookとは、SendGrid経由でメールを送信した際に発生するイベント(バウンス、到達、開封、クリックなど)を、指定したURLにPOSTする機能です。

SendGrid の Event Webhook は、SendGrid経由でメールを送信する際に発生するイベントを、指定したURLにPOSTすることができます。 このデータの用途は、配信停止アドレスの削除、迷惑メール報告への対応、エンゲージできなかった受信アドレスの判定、バウンスされたメールアドレスの特定、メールプログラムの高度な分析などです。

https://sendgrid.kke.co.jp/docs/API_Reference/Webhooks/event.html

EventHubでは、以下のようにEvent Webhookを活用しています。

  1. Event WebhookからPOSTされたイベントデータ(メール開封、クリックなど)をAWS API GatewayでJSON形式で受け取ります。
  2. API GatewayがAWS Lambdaを呼び出し、イベントデータを渡します。
  3. Lambdaから、EventHubのAPIにイベントデータを送信します。
  4. EventHubのAPIでは、受け取ったイベントデータをもとに送信対象の配信ステータスを更新します。
  5. 管理画面上で、メールごとの送信対象別の配信ステータス(バウンス、到達、開封済/未開封)を確認できます。

配信ステータスのモニタリングを行うことで、以下のようなメールマーケティングの効果測定が可能になります。

  • 開封済み率を測定し、メール文面や送信タイミングの改善ポイントを特定する
  • 一定期間が経過した後の未開封者に対してリマインドメールを送るなどのフォローアップを行う

このように、配信状況可視化によって、PDCAサイクルを回しながらメール施策を最適化していくことが可能となります。

取りこぼし対策

Webhookによる各ユーザーへの配信ステータス更新が失敗する可能性に備え、メール配信ステータスが更新されていないユーザーを特定し、バッチ処理で強制的に更新を行うリカバリーメカニズムを導入しています。これにより、実際にメールが届いているにもかかわらず、不達とされてしまうケースを防ぎ、主催者が各ユーザーの配信ステータス情報を正確に把握できるようにしています。

まとめ

本稿では、EventHubにおけるリマインドメール機能の概要と、SendGridを活用した配信状況可視化の仕組みについて解説しました。 メール送信時の本文のカスタマイズ(置換タグの利用、カスタムオブジェクト)などの紹介もしたいのですが、次回の機会とします。 今後、メールマーケティングをより効果的に実施できるよう、機能拡張を続けていく予定です。

最後に

テックブログ以外にも会社全体の雰囲気を知るためのブログやXもありますので、もし興味を持っていただけたらそちらも是非見てみてください!

https://x.com/i/flow/login?redirect_after_login=%2FEventHub_inc

note.com

jobs.eventhub.co.jp

型共有の恩恵を享受するEventHub

はじめに

こんにちは!! エンジニアの川井です!

テックブログの最初の技術記事ということで、EventHubではフロントとバックで型共有をしています。 今日はそれについて書いていきたいと思います。

EventHubの技術構成

フロント及びバックはTypeScriptで統一されています。 フレームワークとしては、フロントはReact、バックはNestJSを採用しています。

なぜTypeScript、React、NestJSを採用したかはこちらの記事を見てみてください! https://note.com/eventhub/n/n642967756043

ディレクトリ構造について

EventHubはMonorepoで構成されています。 ルートディレクトリはざっくり下記(一部割愛してます)みたいな構成になっています

.circleci
.github
client
├ package.json
└ src
cms
├ package.json
└ src
server
├ package.json
└ src
shared
├ package.json
└ src
shared-front
├ package.json
└ src

clientやcmsはwebアプリケーション、serverはAPIサーバー、sharedは共通の型定義やユーティリティ関数を置いています。 shared-frontはclientやcmsで共通化できるコンポーネントが置いてあります。 ここらへんの詳しい説明は別の機会に!

型共有について

お待たせしました!! 本題の型共有についてです。 前述でsharedに型定義が置いてあることを記載しました。 ここではもう少し詳細に書いていきたいと思います。 まず、フロントとバックで共有したい型定義ですが、主にAPIへのRequest定義やResponse定義になると思います。 もちろん、他にもEnum等の共通の型定義もあるかと思いますが、今回はAPIのRequest/Responseに絞って書いていきます。 例えば、Request情報を基にEventを作成するようなAPIがあったとします。 そのAPIのRequestとResponseは以下のようになるかと思います。 (EventHubではclass-validatorでRequestのvalidateを行っていますが、今回は説明用として割愛します)

export class EventCreateReq {
  name: string;

  constructor(arg: {
    name: string;
  }) {
    this.name = arg.name;
  }
}

export class EventRes {
  eventId: string;
  name: string;

  constructor(arg: {
    eventId: string;
    name: string;
  }) {
    this.eventId = arg.eventId;
    this.name = arg.name;
  }
}

これらのRequest/Responseはフロントとバックで共有したいですよね。 なので、これらをsharedに定義していきます。 例えば、フロントがこれらの定義を参照してAPIを叩くときは以下のようになるかと思います。 (今回はわかりやすさ重視で色々な部分を簡略化しています)

import { EventReq, EventRes } from 'shared';
import { useState } from "react";


const useEventHooks = () => {
  const [name, setName] = useState("")


  const createEvent = async () => {
    const req = new EventReq({ name });

    await fetch(`${APIのURL}`, {
      body: JSON.stringify(req)
    });
  }

  return { name, setName, createEvent }
}

では、どのような恩恵があるかというと、例えばEventに説明という項目を追加したいとします。 まず、Request/Responseの型定義を変更してきます

export class EventCreateReq {
  name: string;
  description: string;

  constructor(arg: {
    name: string;
    description: string;
  }) {
    this.name = arg.name;
    this.description = arg.description;
  }
}

export class EventRes {
  eventId: string;
  name: string;
  description: string;

  constructor(arg: {
    eventId: string;
    name: string;
    description: string;
  }) {
    this.eventId = arg.eventId;
    this.name = arg.name;
    this.description = arg.description;
  }
}

すると、フロントの下記部分でコンパイルエラーが発生します。

import { EventReq, EventRes } from 'shared';
import { useState } from "react";


const useEventHooks = () => {
  const [name, setName] = useState("")


  const createEvent = async () => {
    const req = new EventReq({ name }); ← ここでdescriptionが必要になったのでエラーが発生する

    await fetch(`${APIのURL}`, {
      body: JSON.stringify(req)
    });
  }

  return { name, setName, createEvent }
}

このように、変更時には影響箇所が検知でき、修正範囲や修正の規模感も把握できます!

こういった形でEventHubでは型共有の恩恵を受けています!

さいごに

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

https://jobs.eventhub.co.jp/

https://x.com/i/flow/login?redirect_after_login=%2FEventHub_inc

note.com

テックブログ始めます!!!

はじめに

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

実は、テックブログを開設しよう!という話が社内でこれまでも何度かあがっていたのですが、開発スピードを優先したり、新しく参画してくれたメンバーのオンボーディングを優先していたため、「挑戦してみたいけれど、結果的に優先的に取り組めていない・・・」という状況でした。ただ、この度、満を持して!? EventHubのテックブログを開設することにしました!!

今回はなぜテックブログを始めるかを今までの経緯を含めてまとめます。

過去の立ち上げ

テックブログを立ち上げようとしたのは今回が初めてではなく、2021年の8月に開始しようと動きがありました。ただ、その時は開発がたて込んでいたタイミングと新しく入ってきた方も多くまずは業務に慣れることが重要ということで流れてしまいました。それからも何度か話にあがりましたがまだ時期ではないということで開始にはいたらなかったです。

そんな状況でしたが、2023年からコーポレートチームに手伝ってもらいエンジニアチームのインタビュー記事や雰囲気を知れるための記事を公開し始めたことで徐々にブログを見て弊社の雰囲気を知ってくれる方が増えてきました。

note.com note.com note.com note.com

テックブログをやる目的

テックブログを始める理由はいくつかありますが、より重要なのは情報発信文化の醸成・維持に意義があると考えてます。 エンジニアとしての業務を続けていると日々新しい気付きや、得られる知識が多くあると思います。ささいな気付きでもそれらの情報を発信することはチームの誰かや、世の中の誰かを助けることにつながります。

今の開発組織はお互いの知見や疑問に思っている点などを社内のSlackに積極的に発信している方だと思います。知見を共有することで開発組織全体のスキルがあがりより良いプロダクトを作ることができるようになります。 ただ、こういった雰囲気や文化はチームのメンバーが増えていくことで次第に失われていきやすいと思います。(正確には人数が増えたことにより全体発信のハードルが上がってしまい、エンジニア内でも特定のチーム内で閉じてしまうことが原因です)

もちろん採用広報という意味もありますが、今の規模だから生まれやすい気軽に議論しあって情報を共有し合う文化をこれからも維持もしくはより強く発展させるためにテックブログを開始したいと考えてます。

さいごに

テックブログ以外にも会社全体の雰囲気を知るためのブログやXもありますので、もし興味を持っていただけたらそちらも是非見てみてください!

note.com

https://twitter.com/EventHub_inc