Vue.jsでComposition APIを使ってクリーンアーキテクチャ

こんにちは!
Saas Product Teamの板倉です。

今回は少し前にバージョン3がリリースされたVue.jsとComposition APIを使ってクリーンアーキテクチャをどう組むのかを書いてみたいと思います。
クリーンアーキテクチャについてはこちらを参照ください

今回のエントリーで使用したバージョンは以下の通りです。

Vue: 3.0.0
Typescript: 3.9.7

作成したコードはこちら

準備

まずはプロジェクトを作っていきましょう!
vue-cliを使って作っていきます。
vue-cliが入っていない方は yarn global add @vue/cli を実行してインストールしましょう。

vue create <PROJECT_NAME>

Manually select features を選択して Typescript を追加で選択し、Vueのバージョンを3とします。
全体としては、以下のように選択しました。

Vue CLI v4.5.9
? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, TS, Linter
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use class-style component syntax? No
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)?
 Yes
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files

始める前に

そもそもComposition APIって何?

コンポーネントロジックの柔軟な構成を可能にする、関数ベースのAPIです。
こちらはVue2でも使用可能です。
Vue2ではオプショナルなプラグインとして提供されていましたが、Vue3では標準で使用可能となりました。
※ 2020年11月時点ではVue3はまだIE11に対応していません。レガシーブラウザに対応する必要がある方はVue2.xを使いましょう。

Introduction | Vue.js

作っていきましょう

モジュールを作る

クリーンアーキテクチャの登場人物である各レイヤーのモジュールを作っていきます。
依存関係については先に挙げた作成コードをご覧いただければと思いますので、説明については割愛します。

  • Entity
  • UseCase
  • Gateway
  • Controller
  • Presenter
  • Driver

上記の他に、画面に表示する内容を管理するためのViewStateを作ります。

モジュールの準備

Composition APIの inject, provideを使って実装します。
inject, provideは setup関数から実行する必要がありますので、モジュールの登録は Application のセットアップで行います。

export default defineComponent({
  name: "App",
  setup() {
    provide(Keys.TaskViewStateKey, reactive(new TaskViewState()));
    provide(Keys.TaskStorageKey, new TaskStorage());
    provide(
      Keys.TaskGatewayKey,
      new TaskGateway(inject(Keys.TaskStorageKey)!!)
    );
    provide(
      Keys.TaskPresenterKey,
      new TaskPresenter(inject(Keys.TaskViewStateKey)!!)
    );
    provide(
      Keys.TaskUseCaseKey,
      new TaskUseCase(
        inject(Keys.TaskGatewayKey)!!,
        inject(Keys.TaskPresenterKey)!!
      )
    );
    provide(
      Keys.TaskControllerKey,
      new TaskController(inject(Keys.TaskUseCaseKey)!!)
    );
  }
});

ここで大事なのは画面で参照するViewStateは reactive で包むことです。
そうすることでViewStateの値を更新するだけで画面が更新されるようになります。

Componentから利用する

VueComponentでモジュールを取得するのも同じようにsetup関数で行います。

<template>
  <h1>タスク一覧</h1>
  <task-list :tasks="state.values"></task-list>
</template>
export default defineComponent({
  name: "TodoList",
  components: {
    TaskList
  },
  setup: () => {
    const controller = inject(Keys.TaskUseCaseKey)!!;
    const state = inject(Keys.TaskViewStateKey)!!;
    onBeforeMount(() => {
      controller.find();
    });
    return {
      state
    }
  }
});

今回作ったアーキテクチャの処理の流れ

以下の図は処理の流れを簡単に示したものです。

f:id:diskit:20201120151333j:plain

VueComponentでは値を取得して更新するのではなく、処理の呼び出しを行うだけです。
上の図の青い部分以外はフレームワークに依存しないので、テストも書きやすいです!

まとめ

Vuexを使うと少し頑張らないとクリーンアーキテクチャは組みづらかったのですが、Composition APIを使うことでより簡単に組むことができると思いました。
作るアプリケーションのサイズによってはtoo muchだなと思われるかもしれません。状況に応じて崩すのもありだと思います。
ただ、しっかりとアーキテクチャを作っておくことでアプリケーションの成長に伴って手を出しづらくなるのを予め防ぎやすくなると思います。

さいごに

一緒にプロダクトを開発する仲間を募集しています!
Saas Product Teamってどんな感じなの?という疑問に対しては、以下の記事が参考になるかと思います!

journal.uzabase.com

少しでも興味のある方は一度お話ししませんか?
以下のリンクからエントリーしていただけると嬉しいです

SPEEDA/INITIAL/FORCAS Sales - ソフトウェアエンジニア(サーバー/フロント) - Uzabase, Inc.

© Uzabase, Inc. All rights reserved.