型共有の恩恵を享受する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