🌟

TypeScriptと共に組織の開発体験を向上させる取り組み

に公開

Vue.jsではTypeScriptサポートがどんどん拡大し、3.3でPropsにインポートした型を使えるようになったことで決定的になりました。少し前とは状況が変わり、Vue.jsはTypeScriptの恩恵を十分に受けられる体制が整ったかと思います。
この記事では、TypeScriptと共に型の概念を組織に導入して、開発体験をよりよくするために取り組んだことをまとめます。

組織が持っていた課題

LaravelのViewでVue.jsを使っているという小規模の開発会社ではよくある技術だったのですが、既に出来ているサービスをいざ改修、機能追加をしようとした際に困ったことになってしまいました。これには様々な[1]要因があるのですが、そのうちの一つにインターフェイス関連の問題がありました。以下はその例です。

APIレスポンスでjson構造を都度書いているため、都度他の所からコピペしないといけない上、コードにTypoがあるとフロント側の参照が出来なくなる

何が原因でnull/undefinedエラーになるのかの調査が頻繁に発生し、↑が原因なだけだったことがそこそこあり、時間が無駄になっているということがありました。

Propsが分からない

TypeScriptサポートをしておらず、親和性がないと言われていた時代からVueを使っていたため、propsの型が定義されていません。そのためボタンや表などの既存のコンポーネントを使いまわしたいという場合でも都度ビルドして、propsが正しいものであるかコンソールでチェックする手間がかかっていました。

型を意識する姿勢がない

型がなくても動くコードはかけますし、何なら.tsファイルでも型付けしなくても書けます。コードの品質を軽視していて、スピード及び出来上がったものの品質のみを評価する傾向があったため、TypeScriptのキャッチアップが出来ていなかったりしました。

TypeScriptで取り組んだこと

既存のコードについてはリファクタリングする話が進まなかったので、新規開発の部分からTypeScriptを導入することにしました。以下はその取り組みです。

Storybookで作ったコンポーネントにTypeScriptを導入

プロダクトの進捗に依存せずUI部分から都度開発、テスト出来る環境にするためStorybookでUIコンポーネントを作成し、Github Packagesとして組織アカウントのPATでインストール出来るパッケージにすることにしました。
その際にTypeScriptでdefinePropsを記述することで、パッケージのインストール先でもPropsの型推論をしてくれるようになります。
特に↓の例でいうvariantのように、何種類かのコンポーネントのタイプを切り替えるためにユニオン型を使えるのが便利です。

Button.vue
const props = withDefaults(defineProps<{
  text: string
  variant?: 'primary' | 'secondary'
  disabled?: boolean
  color?: string
  backgroundColor?: string
}>(), {
  variant: 'primary',
  color: '#1a73e8',
  backgroundColor: '#ffffff',
})

スキーマ駆動開発でAPIの型を決めて開発[2]

まずAPIレスポンスの型として、SuccessとError時の二つを以下のように定義しました。

api.ts
export interface SuccessResponse<T = any> {
  status: 'success';
  data?: T;
  message?: string;
  message_code?: string;
}

export interface ErrorResponse {
  status: 'error';
  error_code: string;
  message?: string;
}

バックエンド側では、app/traits/ApiResponse で共通処理を作り、return $this->successResponse($data); のようにsuccessResponse/errorResponse + 渡したいデータでレスポンスを統一しました。
既存のプロダクトが数年くらい改修され続けていて、色々な社員や業務委託の人がコードに触れていることがコミット履歴から分かったので、混乱やプロパティ参照できないバグを防ぐためにもレスポンスの型は作っておきたいと思っていました。

TypeScript以外で取り組んだ部分

設計や意識付けの部分で取り組んだものです。

バッチ処理サーバーを別にした

従来のプロダクトではLaravelサーバーの設定されているEC2インスタンス内で、Pythonで書かれたアルゴリズム/バッチ処理を、shell_exec で実行していました。使うデータをcsvにして、Python側でそのcsvのファイル名からデータを読み込ませていたためデータエンジニアの方が、Laravelの設計を待たないと開発ができず、結合度が高い状況でした。
Pythonを使う処理はFastAPIで建てたAPIサーバー内で処理させるようにしたことで、これらの課題の解決を図りました。

  • Laravelの設計に依存せず、並列でアルゴリズムの開発ができる
  • Pythonサーバー側でDBからデータを取得することで、いちいちcsvに加工する必要がなくなった
  • サーバーの環境が分かりやすい:以前はPHP+Python+Node.jsが全部入っていたのでインストールしているパッケージ等の管理が大変でした。

疎結合・オブジェクト指向の考え方の学習

TypeScriptが型の概念を用いて何を楽にしているかというと、疎結合となるように設計したコンポーネントや関数等を使う際に、インプット及びアウトプットの形が分かりやすくなる/静的チェックをエディタがしてくれるといったものかと思います。
でもこの利点を組織で共有するためには、なぜ疎結合がいいのか、オブジェクト指向とは何なのかといった部分からの学習が必要だと感じました。どちらも直感で理解するのが、結合度が「低い」方が「高い」より開発の観点では良いという日本語のニュアンスに反したものだったり、「オブジェクト」という抽象度の高い話があったりで大変かと思います。
自分は経営学を専攻していたので、製品アーキテクチャのモジュラー型の概念を、ゲーミングPCのBTOパソコンを元に説明した上で、結合度とオブジェクトの概念を説明する順序で行いました。

まとめ

自分がTypeScriptは入門生であったということと、TypeScriptは導入すれば必ず良くなる話ではないので、TypeScript使い始めの段階では簡単な型の定義と、TypeScriptを効果的に使うための設計思想の学習に終始していたと思います。
TypeScriptが開発体験を良くしてくれるというより、TypeScriptを効果的に使えるように学習することで、開発体験が良くなる設計やコーディングが出来るスキルが身につく くらいで捉えるのがよさそうです。
今後はもう少し深いTypeScriptの記事を書いていくために精進します!

脚注
  1. 数か月で作った受託開発のコードをそのまま流用する、、クローンコード、クエリ文、アーキテクチャの構造 ↩︎

  2. HonoだとRPCがあって共通の型を使えるからより便利らしい👀 ↩︎

Discussion