JavaScriptでClean Architectureを導入してみた - Vue.js・Reactのサンプルつき

# 事の発端

始まりはこちらのツイートから。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Usecasesレイヤーを充実させていったらVuex Actionsほとんど使わなくなるな笑</p>&mdash; Andy (@andoshin11) <a href="https://twitter.com/andoshin11/status/1007492950987702272?ref_src=twsrc%5Etfw">2018年6月15日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

それはどういうことだよ・・・
フロントでどう使うんだ・・・？
と疑問に思い、自分なりに検証・実装してみたいと思ったのが事の発端です。

# Clean Architectureとは？

まず根本の理解がほぼなかったので調べることにしました。

こことか
https://qiita.com/koutalou/items/07a4f9cf51a2d13e4cdc

こことか
https://blog.tai2.net/the_clean_architecture.html

こことか
https://qiita.com/Tueno@github/items/705360b357c2a00c9532


こことか
https://github.com/apavamontri/nodejs-clean

こことか
http://techblog.reraku.co.jp/entry/2017/08/08/184313
https://www.youtube.com/watch?v=fWVE1fH0MVE
https://github.com/Shinpeim/NekogataDrumSequencer

こことか
http://azu.github.io/slide/2016/child_process_sushi/almin-javascript-architecture.html
https://github.com/almin/almin
https://github.com/azu/large-scale-javascript

いろいろ漁り弄りました。
がしかし、なんだかふわっとした、雲を掴んでいるような感覚で、
理解には及びませんでした。。
（記載させていただいたページはどれもわかりやすく書かれていると思います！）

これは実装・検証が必要だ。。。

# 全体の構造

全体の構造は
https://github.com/almin/almin
の[examples](https://github.com/almin/almin/tree/master/examples)をめちゃくちゃ参考にさせていただきました。

プログラム全体を以下のリストに分割して考えることにします。
※　かなり個人的な見解を含むと思います。

- Domain
    - プレーンなClassで、扱いたい ”もの” をわかりやすいプロパティとメソッドで書く
    - 可能な限りライブラリ等は使わない
- Infrastructure
    - 環境依存なものを書く
    - APIなど外部との連携を書く
    - Repositoryから呼び出される
- Repository
    - インスタンス化されたDomainを永続化させる（保存先はInfrastructureを使ってサーバだったりメモリだったり）
    - シングルトン
    - Infrastructureの存在を知っている
- UseCase
    - Presenterから呼び出される
    - ちょっとControllerっぽい動き（極力処理の流れのみを書く感じ）
    - RepositoryからDomainを受け取ってDomainのメソッドを使ったり
- Presenter
    - いわゆるUI実装（スタイル等も込み）
    - 本記事だとVue・React

# 本記事の目標

本記事では、Clean Architectureを用いて、Presenterの入れ替え（つまりVueとReactを入れ替え）を行い、Presenterがビジネスロジックに依存しないで実装できかの検証とClean Architectureがフロントエンドでどんな実装になるのかを考えます。
個人的にはClean Architectureを理解することも目標としています。

# 何を作ったか

できる限り簡素な実装を行いたいので、ボタンをクリックしたら値が増減するだけのカウンターをサンプルに実装します。
増減した値はローカルストレージに保存して永続化します。

書いたコードは全てCodePenで公開します。

**↓できたもの**

- Domain / Infrastructure / Repository / UseCase  
  https://codepen.io/RikutoYamaguchi/pen/pKmZaY
- Presenter (Vue.js)  
  https://codepen.io/RikutoYamaguchi/pen/MXdQOV
- Presenter (React)  
  https://codepen.io/RikutoYamaguchi/pen/zaQaPJ

Presenterでは「Domain / Infrastructure / Repository / UseCase」をExternal Penとして追加し、
Vue.jsとReactで同じものを利用しています。

**↓使用ライブラリ等**

- https://cdnjs.cloudflare.com/ajax/libs/EventEmitter/5.2.5/EventEmitter.js
- https://cdnjs.cloudflare.com/ajax/libs/localforage/1.7.2/localforage.js
- https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.29/browser-polyfill.min.js
- https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js
- https://unpkg.com/react/umd/react.development.js
- https://unpkg.com/react-dom/umd/react-dom.development.js


# Domainの実装

まずは、アプリケーションの基盤となるDomainを実装します。
今回はカウンターをプレーンはClassで表してみました。

- Domain
    - プレーンなClassで、扱いたい ”もの” をわかりやすいプロパティとメソッドで書く
    - 可能な限りライブラリ等は使わない

```javascript
class Counter {
  /**
   * @param {Number} count
   */
  constructor(count = 0) {
    this.count = count;
  }

  countUp() {
    this.count++;
    return this._update(this);
  }

  countDown() {
    this.count--;
    return this._update(this);
  }

  /**
   * 更新した新しいインスタンスを返す
   * @param {Counter} updated
   */
  _update(updated) {
    return new Counter(updated.count);
  }
}
```

- `count`というプロパティを持つ
- `countUp`という`count`を1足すメソッドを持つ
- `countDown`という`count`を1引くメソッドを持つ
- `_update`という新しいインスタンスを返すメソッドを持つ

こう見ると非常にシンプルなClassが出来上がりました。
またライブラリ等は使用していないプレーンな状態となりました。

ついでにですが、この`Counter`Domainをインスタンス化するにあたり、
Factoryも作りました。

```javascript
class CounterFactory {
  static createFromLocalStorage(counter) {
    let count = 0;
    if (!counter) {
      return new Counter(count);      
    }

    if (typeof counter.count !== 'number') {
      count = 0;
    } else {
      count = counter.count;
    }
    return new Counter(count);      
  }
}
```

例えばですが、DomainをJSONから作る必要がある場合など、
Factoryからインスタンスを生成するようにすれば、
Domain内でデータ変換等をする必要がなくなります。
（→ つまりDomainではJSONの仕様を知る必要がなくなる。　＝　依存しない）

今回はローカルストレージにデータを保存しようと思うので、
ローカルストレージのデータから`Counter`Domainを作るメソッドを作成しました。

# Infrastructureの実装

次に環境依存なInfrastructureを実装しました。
今回はローカルストレージへの書き込み、読み出しの実装となります。

- Infrastructure
    - 環境依存なものを書く
    - APIなど外部との連携を書く
    - Repositoryから呼び出される

```javascript
class LocalStorage {
  /**
   * ローカストレージから値を取得する
   * @param {String} name
   * @return {Promise}
   */
  get (name) {
    return localforage.getItem(name);
  }

  /**
   * ローカストレージへ値を入れる
   * @param {String} name
   * @param {*} value
   * @return {Promise}
   */
  set (name, value) {
    return localforage.setItem(name, value);
  }
}
```

ここでは`localforage`に依存して実装を行っている事がわかります。
これは、`Web API`だったり`DOM API`だったりしても同じようにInfrastructureに実装します。

# Repositoryの実装

続いてはRepositoryの実装です。

- Repository
    - インスタンス化されたDomainを永続化させる（保存先はInfrastructureを使ってサーバだったりメモリだったり）
    - シングルトン
    - Infrastructureの存在を知っている

```javascript
const REPOSITORY_CHANGE = 'REPOSITORY_CHANGE'; // Repository内部のemit名
class CounterRepository extends EventEmitter {
  /**
   * @param {Map} memory
   * @param {LocalStorage} localStorage
   */
  constructor(memory = new Map(), localStorage = new LocalStorage()) {
    super();
    this._memory = memory;
    this._localStorage = localStorage;
  }

  /**
   * ローカルストレージに格納されている
   * カウンターを取り出す
   */
  async getLocalStorageData () {
    return await this._localStorage.get('counter');
  }

  /**
   * メモリ上に永続化されたカウンタードメインを返す
   */
  getCounter () {
    return this._memory.get('counter')
  }

  /**
   * カウンタードメイン永続化
   * @param {Counter} counter
   */
  save (counter) {
    this._memory.set('counter', counter);
    this._localStorage.set('counter', counter);
    this.emit(REPOSITORY_CHANGE, counter);
  }

  /**
   * emitterへハンドラ設定
   * @param {Function} handler
   */
  onChange(handler) {
    this.on(REPOSITORY_CHANGE, handler);
  }
}

// リポシトリーのシングルトン
const counterRepository = new CounterRepository();

```

`CounterRepository`ではメモリ上に永続化させるための`Map`と、
先ほどInfrastructureで作成した`LocalStorage`を引数に、
EventEmitterを親クラスとしたシングルトンとします。

今回の実装はとても簡単なので問題にならないですが、
複雑になってくると、UseCaseとRepositoryどっちに実装しよう。。。と迷うことがありそうです。
RepositoryはInfrastructureへの参照を利用する、保持するという役割以外のことは極力さけるほうが良さそうです。

また、RepositoryはDomainを永続化させる際（`save`メソッドが実行されたら）、
自身のemitを実行し、その引数には永続化されたDomainを代入します。

`onChange`メソッドはPresenterから更新時のハンドラを登録するために実装してあります。

# UseCaseの実装

Presenter以外の実装はこれで最後です。

- UseCase
    - Presenterから呼び出される
    - ちょっとControllerっぽい動き（極力処理の流れのみを書く感じ）
    - RepositoryからDomainを受け取ってDomainのメソッドを使ったり

```javascript

// UseCase
class CounterUseCase {
  /**
   * @param {CounterRepository} counterRepository
   */
  constructor(counterRepository) {
    this.counterRepository = counterRepository;
  }

  /**
   * CounterUseCaseを作って返す
   */
  static create() {
    return new CounterUseCase(counterRepository);
  }

  /**
   * ローカルストレージから値を取得して、初期化する
   * 1.ローカルストレージから値を取得する
   * 2.取得した値を使ってドメインを作成する
   * 3.作成したドメインを永続化する
   */
  async initialize() {
    const value = await this.counterRepository.getLocalStorageData();
    const counter = CounterFactory.createFromLocalStorage(value);
    this.counterRepository.save(counter);
  }

  /**
   * 値を一つ足す
   * 1.Repositoryから永続化されたドメインを取得する
   * 2.ドメインのcountUpメソッドを呼び出し、更新したドメインを取得する
   * 3.更新したドメインを永続化する
   */
  countUp() {
    let counter = this.counterRepository.getCounter()
    counter = counter.countUp();
    this.counterRepository.save(counter);
  }

  /**
   * 値を一つ引く
   * 1.Repositoryから永続化されたドメインを取得する
   * 2.ドメインのcountDownメソッドを呼び出し、更新したドメインを取得する
   * 3.更新したドメインを永続化する
   */
  countDown() {
    let counter = this.counterRepository.getCounter()
    counter = counter.countDown();
    this.counterRepository.save(counter);
  }
}
```

UseCaseではRepositoryから永続化されたDomainのインスタンスを受け取り、
そのインスタンスやRepositoryを使った処理の流れ自体を記述していきます。

UseCaseは実際に必要な時のみ存在するよう、
`static create`メソッドで自身のインスタンスを返す実装にしました。

# Domain / Infrastructure / Repository / UseCaseまでの実装

<p data-height="265" data-theme-id="0" data-slug-hash="pKmZaY" data-default-tab="js,result" data-user="RikutoYamaguchi" data-embed-version="2" data-pen-title="FrontEnd Clean Architecture Base" class="codepen">See the Pen <a href="https://codepen.io/RikutoYamaguchi/pen/pKmZaY/">FrontEnd Clean Architecture Base</a> by Rikuto Yamaguchi (<a href="https://codepen.io/RikutoYamaguchi">@RikutoYamaguchi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# Presenter (Vue.js)

さてここからがUI部分の実装に入ります。
まずはVue.jsから。

```javascript

const VuePresenter = Vue.component('VuePresenter', {
  template: `
<div>
  <p>カウント：{{ count }}</p>
  <button @click="onClickCountUp">countUp</button>
  <button @click="onClickCountDown">countDown</button>
</div>
`,
  data () {
    return {
      count: null
    }
  },
  beforeCreate() {
    // Domainを初期化
    CounterUseCase.create().initialize();
  },
  created() {
    // Repositoryを購読
    counterRepository.onChange(counter => {
      this.count = counter.count;
    })
  },
  methods: {
    onClickCountUp() {
      CounterUseCase.create().countUp();
    },
    onClickCountDown() {
      CounterUseCase.create().countDown();
    }
  }
})

// render
new Vue({
  el: '#vue-app',
  template: '<VuePresenter />'
})

```

`beforeCreated`でまずDomainを初期化します。
ここで重要なのはPresenter側では処理の流れ自体は何も知らないということです。

`created`ではRepositoryの購読を行います。
onChangeのコールバックでは、引数で渡されるDomainを使って、
ローカルデータの更新を行います。
※ PresenterがDomainの実装を知っている状態になるので、本当は一つ経由するもの（Presenterが使用するデータの形に変換するもの）を用意したほうがよいかもしれません。

`methods`では各ボタンが押された際の、UseCaseの呼び出しを行います。

<p data-height="265" data-theme-id="0" data-slug-hash="MXdQOV" data-default-tab="js,result" data-user="RikutoYamaguchi" data-embed-version="2" data-pen-title="FrontEnd Clean Architecture (Vue.js)" class="codepen">See the Pen <a href="https://codepen.io/RikutoYamaguchi/pen/MXdQOV/">FrontEnd Clean Architecture (Vue.js)</a> by Rikuto Yamaguchi (<a href="https://codepen.io/RikutoYamaguchi">@RikutoYamaguchi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# Presenter (React)

次にReactで実装してみました。

```jsx
class ReactPresenter extends React.Component {
  constructor() {
    super();
    this.state = {
      count: null
    };
  }

  componentWillMount() {
    // Domainを初期化
    CounterUseCase.create().initialize();
    
    // Repositoryを購読
    counterRepository.onChange(counter => {
      this.setState({
        count: counter.count
      })
    })
  }
  
  onClickCountUp() {
    CounterUseCase.create().countUp();
  }
  
  onClickCountDown() {
    CounterUseCase.create().countDown();
  }
  
  render () {
    return (
      <div>
        <p>カウント：{ this.state.count }</p>
        <button onClick={() => this.onClickCountUp()}>countUp</button>
        <button onClick={() => this.onClickCountDown()}>countDown</button>
      </div>
    )
  }
}

// render
ReactDOM.render(
  <ReactPresenter />,
  document.getElementById('react-app')
);

```

やっていることはVue.jsのときと一緒です。
`componentWillMount`でDomainの初期化とRepositoryの購読を行っています。

<p data-height="265" data-theme-id="0" data-slug-hash="zaQaPJ" data-default-tab="js,result" data-user="RikutoYamaguchi" data-embed-version="2" data-pen-title="FrontEnd Clean Architecture (React)" class="codepen">See the Pen <a href="https://codepen.io/RikutoYamaguchi/pen/zaQaPJ/">FrontEnd Clean Architecture (React)</a> by Rikuto Yamaguchi (<a href="https://codepen.io/RikutoYamaguchi">@RikutoYamaguchi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# まとめ・思ったこと

ひとまずは、Presenterの入れ替え（Vue.jsとReact)を行うことはできました。
これの意味するところはPresenterの修正はビジネスロジック部分へ全く影響を及ぼさないということになるかと思います。

ただ、まだこの実装がClean Architectureといえるのか・・・？ふわっとした感覚で、実感はない状態です。
（依存関係は外側に向かっている用に見えるけど・・・うーーん。。。）

もっと複雑な実装を行った時どうなるんだろう・・・とまだまだわからないことだらけです。
みなさんのご意見・ご指摘お待ちしております！！！