やっはろー!Gaiaxの@miniturboです!
この記事はGaiax Engineers' Advent Calendar 2015の19日目の記事として書いています。
最近では、システム障害対応に特化したサービスReactioの開発を行っております。今回は、そのReactioにReactを導入したお話となります。
React導入の背景
Reactioは、システム障害が発生した際、チームで迅速に対応・解決できるよう、タイムラインでのやり取りや障害情報の記録をリアルタイムに行えるようなWebアプリケーションになっています。
このリアルタイムアプリケーションの部分は、もともと社内での課題を解決するための社内ツールとして2013年に生まれたこともあり、当時流行していたBackbone.jsとWebSocketを使って実現しています。
素のBackbone.jsということもあり、当初はシンプルで見通しも良かったのですが、機能が増えるにつれてロジックは複雑になり、様々なイベントが飛び交い、どこで何が起こっているのか、データがどう流れているのかを追うのが困難になってしまいました。
そこで、何かしらの仕組み・枠組みを導入し、機能が複雑化してもデータやイベントの流れが把握しやすくなる必要が出てきました。
これがReact/Fluxを導入しようと思い立った背景です。
※図はFlux公式リポジトリより引用
一部分を置き換える
React/Fluxを導入しよう!と、思い立ったはいいものの、既存のコードをすべてReactに置き換えていくのはとても大変です。影響範囲も広く、実装工数もとても大きくなってしまいます。
そこで、各機能のうち一番小さい機能を対象に、その部分だけReactに置き換えていく戦略をとり、Reactを小さく導入していくことにしました。
Reactioでは、システム障害1つをインシデントとして扱っています。
今回は、そのインシデントの名前の表示部分をReactioに置き換えています。
置き換えの準備
まずは、Reactを書き始められるよう、必要なモジュールを入れるところから始めました。
また、今回はFlux実装としてReduxを利用しているので、そちらも合わせて入れました
$ npm install --save react react-dom redux react-redux
ディレクトリ構成は下記のような感じです。
./js
├── actions
│ └── index.js
├── components
│ └── IncidentName.js
├── index.js
├── reducers
│ └── index.js
├── store
└── utils
固定の文字列を表示してみる
まずはじめに、インシデント名の部分に固定の文字列をReactで表示してみることにしました。
通常ではアプリケーション全体をReactでrender
することが多いと思いますが、今回はインシデント名の部分のみReactで表示するため、HTML上のインシデント名の部分に空の<div>
を設置します。
<html>
...
<body>
...
<div id="incident-name"></div>
...
</body>
</html>
次に、インシデント名のコンポーネントIncidentName
を用意します。
import React, { Component } from 'react';
export default class IncidentName extends Component {
render() {
return (
<div>インシデント名をReactで表示したい!</div>
);
}
}
そして、起点となるindex.js
で先ほどのIncidentName
コンポーネントをrender
してみます。
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import configureStore from './store/configureStore';
import IncidentName from './components/IncidentName';
render(
(
<Provider store={configureStore()}>
<IncidentName />
</Provider>
),
document.getElementById('incident-name')
);
これで、ブラウザを読み込み直してみます。
無事に表示されました!
API経由でインシデント名を表示する
次に、APIサーバから本来のインシデント名を取得して、表示してみることにしました。
IncidentName
をマウントする際に、インシデント名を取得するfetchIncidentName
というActionを実行してみることにします。
import React, { Component } from 'react';
import { fetchIncidentName } from '../actions'
export default class IncidentName extends Component {
componentDidMount() {
this.props.dispatch(fetchIncidentName());
}
render() {
return (
<div>インシデント名をReactで表示したい!</div>
);
}
}
fetchIncident
はこのように実装してみました。
export const fetchIncidentName = () => (dispatch) => {
api.get('インシデント名取得APIのエンドポイント').then((json) => {
dispatch({ payload: json });
});
}
取得したインシデント名を、Reducerで処理してStoreに格納します。
import { combineReducers } from 'redux'
function incident(state = {}, action) {
return { ...state, name: action.payload };
}
export default combineReducers({ incident });
最後に、IncidentNameとReduxをつなぎこんで、Storeの値をprops経由で表示できるようにします。
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { fetchIncidentName } from '../actions'
@connect((state) => { incident: state.inident })
export default class IncidentName extends Component {
componentDidMount() {
this.props.dispatch(fetchIncidentName());
}
render() {
return (
<div>{this.props.incident.name}</div>
);
}
}
export default connect((state) => ({ incident: state.incident }))(App)
これで、ブラウザを読み込み直してみます。
本来のインシデント名が表示されましたね!
表示をリアルタイムに更新されるようにする
Reactioでは、インシデント名が変更された際に、WebSocketを使って変更内容がリアルタイムに伝わるようになっています。既存の実装では、WebSocketクライアントをBackbone.Events
でラップしたもので実装しているのですが、今回は置き換えはせずそのままReactから利用することにしました。
インシデント名の取得と同じ要領で、WebSocketからインシデント名の変更を受け付けた際にreceiveIncidentName
というActionを実行してみることにします。
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { fetchIncidentName, receiveIncidentName } from '../actions';
@connect((state) => { incident: state.inident })
export default class IncidentName extends Component {
componentDidMount() {
this.props.dispatch(fetchIncidentName());
Stream.on('updated-incident-name', (name) => {
this.props.dispatch(receiveIncidentName(name));
});
}
render() {
return (
<div>{this.props.incident.name}</div>
);
}
}
export default connect((state) => ({ incident: state.incident }))(App)
fetchIncident
と同様にreceiveIncidentName
を実装します。
export const fetchIncidentName = () => (dispatch) => {
api.get('インシデント名取得APIのエンドポイント').then((json) => {
dispatch({ payload: json });
});
}
export const receiveIncidentName = (name) => {
dispatch({ payload: name });
}
これでインシデント名がリアルタイムに更新されるようになりました!
まとめ
実際にはもう少し工夫していますが、概ねこの流れでReactioでReactを(ちょこっとだけ)導入することができました!これで、導入の背景にあった課題も解決し、「ReactioはやっぱりReact使っているんですか?」という質問にも安心して答えられそうです
現在は、この置き換え部分を足がかりに、新規/既存のコードを少しずつReact/Fluxに置き換えていっています。一度に置き換えようとせず、一つずつ改善を繰り返していくのがいいのだなあ、と改めて思った次第です。どこかで参考になれば幸いです。
明日は北海道が生んだ弊社インフラチームのエース、 @akms くんです!お楽しみに!