Slide 1

Slide 1 text

エムスリー株式会社 鈴木 健太 @suusan2go 2018/11/10 Vue Fes Japan 2018 Reject Conference レガシーアプリケーション リニューアルにNuxt.jsで戦う #vuefes_reject

Slide 2

Slide 2 text

自己紹介: 鈴木 健太 ● GitHub / Twitter @suusan2go ● Software Engineer @ M3, Inc ● リニューアル プロジェクトで 、Kotlin APIサーバ / フロント Nuxt.jsを担当 ● 現在 AI・機械学習チームというところで機械学習 部分以外 こと全部やってます ● 自慢 娘(2歳)が同年代で 可愛いことです(当親 比)。今月第二子が生まれます! 2

Slide 3

Slide 3 text

エムスリーご紹介 3

Slide 4

Slide 4 text

エムスリーご紹介 4 医療に関するWebサービスを多数展開 全世界で約400万人 医師会員 日本で約27万人 医師会員

Slide 5

Slide 5 text

VueFesで Silver SponsorとBeer Sponsorでした! 5

Slide 6

Slide 6 text

1. リニューアルプロジェクトについて 2. なぜNuxt.jsを選択した か 3. Nuxt.js 開発におけるTips 4. まとめ 今日お話すること

Slide 7

Slide 7 text

1. リニューアルプロジェクトについて 2. なぜNuxt.jsを選択した か 3. Nuxt.js 開発におけるTips 4. まとめ 今日お話すること

Slide 8

Slide 8 text

● 10年前 レガシーシステムをサーバサイドKotlinで、フロントエンドを Vue.js(Nuxt.js)でリニューアルしました。 ● リニューアルしたレガシーシステム 2つ ○ 1. 医師 キャリア支援システム 企業向け画面 ○ 2. 薬剤師 キャリア支援システム 企業向け画面 ○ 事業規模感: 年間売上合わせて100億円超 ○ システム 規模感: それぞれテーブル数150〜200程度 ○ いずれ システムも10年前 技術スタック (独自Java FW、View XSLT) ○ 新規 機能開発がとても難しい状態になっていた リニューアルについて 8

Slide 9

Slide 9 text

9 リニューアル 話をエンジニアHubで取材していただきました

Slide 10

Slide 10 text

10 サーバーサイドKotlin 話 Kotlin Festでもお話しました

Slide 11

Slide 11 text

医師キャリア支援システム リニューアル後 E APIサーバー 11

Slide 12

Slide 12 text

薬剤師キャリア支援システム リニューアル後 E APIサーバー 12

Slide 13

Slide 13 text

薬剤師キャリア支援システム リニューアル後 E APIサーバー 13 今日話す こちら!

Slide 14

Slide 14 text

どれくらいレガシーかというと 14

Slide 15

Slide 15 text

_人人人人人人人人人人人人人人_ > Internet Explorer 6.0以上 <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄ ※今回 リニューアルで当然見直し 15

Slide 16

Slide 16 text

_人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人_ > InternetExplorer10.0で 正しく動作しない可能性があります <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄ 16

Slide 17

Slide 17 text

17

Slide 18

Slide 18 text

● 通常 機能開発チームと 別に、リニューアルチームを立ち上げ ● 薬剤師キャリア リニューアルチーム 2名 ○ suusan2go(メイン エンジニア) ○ maeharin (リーダー、他プロジェクト兼任、0.2稼働) ● リニューアル 開発完了後に、徐々に通常 機能開発チームに引き継 ぎ ● 現状 通常 機能開発チームで開発・運用されています リニューアル 体制 18

Slide 19

Slide 19 text

● サーバーサイド DB そ ままに、アプリケーション部分をKotlinでAPI 化 ○ 独自FW、既存仕様 ドキュメント 当然まったくないところからスタート ○ まずコードや、出力されるSQL ログを読み解いて現状 仕様調査 ○ ほぼフルスクラッチで書き直して、 Kotlin + SpringBoot化 ● フロントエンド UIからスクラッチで刷新 ○ XSLTというテンプレートを使っておりそ まま移行する が困難だった ■ サーバーサイドでやるべきロジックがかなり漏れ出している・・・ ○ ただし見た目 変わっても、画面構成 できるだけ変えないようにした ■ 何年も変わってないUIをいきなりかえると混乱が起きる ■ 画面を大きく変えると仕様 考慮漏れ、ユースケース考慮漏れが起きがち リニューアル 方針 19

Slide 20

Slide 20 text

ところでVueFesで 素晴らしい発表がありました 23

Slide 21

Slide 21 text

24

Slide 22

Slide 22 text

かぶってる部分も多いですが 皆さん 知見になるような お話できるように頑張ります! 25

Slide 23

Slide 23 text

1. リニューアルプロジェクトについて 2. なぜNuxt.jsを選択した か 3. Nuxt.js 開発におけるTips 4. まとめ 今日お話すること

Slide 24

Slide 24 text

リニューアル計画時 検討事項 ● チームで 既にRails + Vue.jsで開発を経験しており、フロントエンド 技術にVue.jsを使うこと ほぼ確定していた ● 先行する医師キャリアチーム SPAで なく、バックエンド API Rails が叩いて、フロント 一部でVue.jsを使うMPAになっていた 27

Slide 25

Slide 25 text

当時先行していた医師キャリア支援システム リニューアル構成 E APIサーバー 28

Slide 26

Slide 26 text

Rails ほぼバックエンドAPI プロキシ ような存在になる(コード サンプル) 29 class Ajax::FilmsController < ApplicationController def show film = film_api.staff_get_film(params[:id]) # APIへのリクエスト render json: film end def create film_api.staff_create_film(_film_create_params) # APIへのリクエスト end private def _film_create_params params.require(:film).permit! end end

Slide 27

Slide 27 text

こ アーキテクチャ メリット・デメリット 30 ● メリット ○ セッション管理 Railsに任せられる ○ Vueが必要 ない画面で Rails 知識だけで構築できる ○ サーバーサイド 経験が中心 メンバーでも比較的キャッチアップ しやすい ● デメリット ○ Vue.js エコシステムに乗り切れない ■ 例え Vue Routerが使えない / 使いにくい ○ VueとRails 責務が曖昧になる ■ 例え Vue.js フォームでポストしたあと 画面遷移・バリデーションエラー どっちで出す? ○ せっかくバックエンド APIがSwaggerで型情報付き API定義をくれている に、 Railsを通すことで型情報が失われてしまう・・・ ○ Vue UIフレームワークをRails側 テンプレートで使いにくい

Slide 28

Slide 28 text

Rails ほぼバックエンドAPI プロキシになっている 31 ● メリット ○ セッション管理 Railsに任せられる ○ Vueが必要 ない画面で Rails 知識だけで構築できる ○ サーバーサイドが中心 メンバーでもキャッチアップが容易 ● デメリット ○ Vue.js エコシステムに乗り切れない ■ 例え Vue Routerが使えない / 使いにくい ○ VueとRails 責務が曖昧になる ■ 例え 、Vue.js フォームでポストしたあと 画面遷移 どっち 責任? ○ せっかくバックエンド APIがSwaggerで型情報付き API定義をくれている に、 Railsを通すことで型情報が失われてしまう・・・ Vueで構成されたページが多くなってくると、 VueによるSPAにUIをよせたほうが確実に設計 綺麗になりそう…… でもサーバーサイド中心 メンバーがキャッチアップできるだろうか?

Slide 29

Slide 29 text

そこでNuxt.js 32 ● 当時(2017年11月) まだベータ ● リニューアル リリースまでに 1.0が出るだろうという見込みで選択 ○ 実際、リニューアル開発中に1.0がリリース ○ 破壊的な変更 少なく、スクロール挙動 調整くらいでなんとかなった

Slide 30

Slide 30 text

強力な規約と実用的な機能 33 ● pages/ 配下がそ ままルーティングになる ● layoutプロパティでレイアウトを切り替え ● 組み込み Vuexストア ● 共通処理をmiddlewareで管理できる ● データ 取得処理をどこで書け いいか明確(fetch / asyncData) ● 組み込み エラーハンドリング(context.error) ● プラグイン機構(Sentry / Google Analyticsなど) 実用的なアプリケーションがすぐに開発できる 設計 ベストプラクティスに乗ることができる

Slide 31

Slide 31 text

豊富なドキュメント 34 ● 「XXXってどうしたらいいんですか ?」に「公式ドキュメント読んで見ま しょう」で大体答えられる ● 自前で1から仕組みを作っていたら、そ ドキュメンテーションとメンテナ ンス コスト それなりに重かった ず ● 日本語ドキュメントが豊富(メンテナ 方ありがとうございます :bow: )

Slide 32

Slide 32 text

Nuxt.jsを採用してよかったところ 35 ● フロントエンド 開発にレールができた ● 以下 ような問いに公式ドキュメントが答えてくれる ○ 初期データどこでとってくる? ○ ルーティングどうする? ○ こ ページ レイアウト変えたいんだけど ○ Webpackでこれどうやって設定する ? ● セットアップに時間が取られがちなところ、最初からアプリケーション 開発に集中できた! ● SPAを作るコストをめちゃくちゃ下げられた

Slide 33

Slide 33 text

1. リニューアルプロジェクトについて 2. なぜNuxt.jsを選択した か 3. Nuxt.js 開発におけるTips 4. まとめ 今日お話すること

Slide 34

Slide 34 text

Nuxt.js開発におけるTips 37 ● Nuxt.js + TypeScript ● components/配下 管理 ● SSR ● Nuxt.jsで テスト

Slide 35

Slide 35 text

Nuxt.js開発におけるTips 38 ● Nuxt.js + TypeScript ● components/配下 管理 ● SSR ● Nuxt.jsで テスト

Slide 36

Slide 36 text

● リニューアル対象 システム 、いわ 企業向け 採用管理業務システム ● 歴史的な経緯により巨大なフォームを作らざるをえない ● サーバーサイド ロジックをできるだけ変えないようにするに 、数十プロパティ でネストした構造を持つJSONを送らざるをえない ● サーバーサイド レスポンス / 要求するリクエストが変わったときに、きちんと追 随していく が辛い ● サーバーサイドと 連携に型がほしい なぜTypeScriptが使いたかった か 39

Slide 37

Slide 37 text

サーバーサイド 実装からTypeScriptクライアントを自動生成する 41 Kotlin + Spring Boot API サーバ Spring Foxで実装から Swagger Definition作成 Swagger Codegenで TS APIクライアントを自動生成

Slide 38

Slide 38 text

レスポンス プロパティが補完される! 42

Slide 39

Slide 39 text

存在しないプロパティを参照しようとするとコンパイルで落ちる! 43

Slide 40

Slide 40 text

Nuxt.jsをTypeScript対応させる 44 ● 実 Nuxt.js examples配下にTypeScript サンプルがあります! ○ https://github.com/nuxt/nuxt.js/tree/dev/examples/typescript ● modules/typescript.jsを用意して、nuxt.config.jsで読み込め OK ● Vue2.5からTypeScirptサポートが強化された で、通常 オブジェクトリテラル 構文でも殆ど問題なかった(クラススタイル 使わなかった) ● プロジェクトがでかくなると、初回 ビルドに1分以上かかるようになる で fork-ts-checker-webpack-plugin で、TypeCheckを別プロセスに

Slide 41

Slide 41 text

Nuxt.jsをTypeScript対応させる 45 const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); export default function() { this.nuxt.options.extensions.push('ts'); this.extendBuild(config => { // ~~省略 ~~ config.plugins.push( new ForkTsCheckerWebpackPlugin({ vue: true, }), ); }); };

Slide 42

Slide 42 text

Vue 型定義をNuxt.js用に拡張する 46 ● Nuxt.jsが拡張しているコンポーネント オプション(layout, middleware, fetch, asyncData, watchQuery….) ために、型定義 自前で拡張する必要がある ● もしかしたら公式で近々サポートがあるかも? ( #4050 #4164)

Slide 43

Slide 43 text

pages配下で拡張されるオプション 型定義 47 declare module 'vue/types/options' { interface ComponentOptions { layout?: string; middleware?: string | string[]; fetch?: (context: NuxtContext) => void; asyncData?: (context: NuxtContext) => void; watchQuery?: string | string[]; head?: () => { title?: string; meta?: Array<{ hid?: string; name?: string; content?: string }>; }; } }

Slide 44

Slide 44 text

interface NuxtContext { isClient: boolean; isServer: boolean; isStatic: boolean; idDev: boolean; isHMR: boolean; route: Route; store: Store; env: object; query: object; nuxtState: object; req: Request; res: Response; redirect: (path: string) => void; error: (params: { statusCode?: string; message?: string }) => void; beforeNuxtRender: ({ Components, nuxtState }) => void; } Nuxt.jsが提供するContextオブジェクト 型定義 48

Slide 45

Slide 45 text

declare namespace NodeJS { export interface Process { client: boolean; server: boolean; } } process.client / process.server 型定義 49

Slide 46

Slide 46 text

TypeScriptで Linterについて 52 ● TypeScriptな で普通だったらtslint……な だけれどeslint-plugin-vueが使い たい ● typescript-eslint-parser でTypeScriptにeslintをかけられる ● こ 辺り joe_reさん ブログにかなり詳しく書かれてて参考にさせていただき ました。

Slide 47

Slide 47 text

Nuxt.jsにTypeScriptいれてよかった? 54 ● 多少最初 設定 時間とられるけど間違いなくYES ● ハマりどころがなかったわけで ありませんが、型で守られるメリット 方が大き かったです。 ● 開発中 テンプレート部分に型がなくても充分恩恵受けられるなーと思ってたけ ど、QAでテンプレート部分も型で守られてたら・・・的なバグが沢山見つかった でやっ りテンプレートにも型ほしいです

Slide 48

Slide 48 text

Nuxt.js開発におけるTips 56 ● Nuxt.js + TypeScript ● components/配下 管理 ● SSR ● Nuxt.jsで テスト

Slide 49

Slide 49 text

components/配下 管理 57 ● Nuxt.js components配下 管理 仕方について ノータッチな で、自分た ちでど ように管理していくか考える必要がある ● 自分たち プロジェクトで ElementUIというUIフレームワークを使っていて、自 作 コンポーネント 数 それほど多くなかったが、コンポーネントが増えてくる と、コンポーネント 粒度に応じて階層をつけたくなる ● Atomic Design 考え方でコンポーネントを管理していくことにしました

Slide 50

Slide 50 text

ところでVueFesで 素晴らしい発表がありました 58

Slide 51

Slide 51 text

59

Slide 52

Slide 52 text

Vuex 参照ルールもpiece of cakeさんとほぼ同じ 60 ●

Slide 53

Slide 53 text

ElementUI 話をします 61 ● ElementUI Vue.js デスクトップUIフレームワーク ● TypeScript 型定義も提供してくれる ● 特にフォームでリッチな表現が簡単にできる、バリデーションルール(中身 yiminghe/async-validator)が柔軟に組める で重宝してました ● 詳しく @maeharin 資料をご確認ください ○ 10年前 レガシーシステムをVue.js TypeScript Elementでフルリニューアルしてい る話 ● こういうコンポーネント集、業務要件に合わせようとするとカスタマイズが面倒だったり不可 能だったりしますが、ElementUI 拡張ポイントというかカスタマイズ性が高く、ほぼ困りま せんでした

Slide 54

Slide 54 text

ElementUI コンポーネント 例 62

Slide 55

Slide 55 text

● 正直Nuxt.js関係ないんですけど、ElementUIでパフォーマンス問題、特定ブラ ウザで 対応が必要でした ● Edgeで インプットコンポーネント パフォーマンス問題 ● Windows7 / Chrome 組み合わせで み起こるインプットコンポーネント 不 具合 ElementUI で ハマりどころ 63

Slide 56

Slide 56 text

● EdgeだけInputで一文字打つごどに、500msくらいかかる現象が発生 ● Chrome、そ 他ブラウザだと問題なし ● ElementUI 実装を覗いてみることに Edgeだけ激重になるInput 64

Slide 57

Slide 57 text

ElementUI Input実装 (packages/input/src/input.vue) 65 // Inputイベントのたびに高さ調節のためにこのメソッドが呼ばれる resizeTextarea() { if (this.$isServer) return; var { autosize, type } = this; if (type !== 'textarea') return; if (!autosize) { this.textareaCalcStyle = { minHeight: calcTextareaHeight(this.$refs.textarea).minHeight }; return; } const minRows = autosize.minRows; const maxRows = autosize.maxRows; this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows); },

Slide 58

Slide 58 text

if (!hiddenTextarea) { hiddenTextarea = document.createElement('textarea'); document.body.appendChild(hiddenTextarea); } let { paddingSize, borderSize, boxSizing, contextStyle } = calculateNodeStyling(targetElement); hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`); hiddenTextarea.value = targetElement.value || targetElement.placeholder || ''; let height = hiddenTextarea.scrollHeight; ElementUI Input実装 ( packages/input/src/calcTextareaHeight.js ) 66

Slide 59

Slide 59 text

scrollHeight リフローが発生する負荷 高い処理 67 scrollHeightに 529.51ミリ秒

Slide 60

Slide 60 text

Elementにプルリク 出しているも マージされず 68

Slide 61

Slide 61 text

● export default ElInput.extend({ methods: { // override 自動での高さ調節が必要なとき以外は、minHeightは初回の計算結果をキャッシュしておく resizeTextarea() { if (this.$isServer) return; const { autosize, type } = this; if (type !== 'textarea') return; if (!autosize) { this.textareaCalcStyle = { minHeight: this.textareaCalcStyle.minHeight || calcTextareaHeight(this.$refs.textarea).minHeight, }; return; } const minRows = autosize.minRows; const maxRows = autosize.maxRows; this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows); }, }, }); カスタムコンポーネントを作ってワークアラウンド 69

Slide 62

Slide 62 text

● 文字を入力しているとカーソルがテキストフィールド 末尾に移動してしまう ● Chrome使ってくださいって言おうとおもったら、Windows7 + Chromeで発生し ていた・・・ ○ エンジニアになって初めて、不具合 案内で「 IE使ってください」って言いました Windows7 + Chrome 組み合わせでだけ起こる不具合 70

Slide 63

Slide 63 text

原因っぽいIssue 71

Slide 64

Slide 64 text

ElementUI botが英訳してくれる 72

Slide 65

Slide 65 text

中国語でよくわからないが、発生環境 同じっぽい 73

Slide 66

Slide 66 text

中国語でよくわからないが、発生環境 同じっぽい 74 ElementUI自体 バージョンアップで解決

Slide 67

Slide 67 text

ElementUIまとめ 75 ● 管理画面・業務アプリ色 強いアプリケーションで使えるコンポーネントが沢山 で便利 ● 辛かったポイントを上げましたが逆にいうとInput回り以外で ほぼ問題ありま せんでした ● おそらくElementUIに限らずInputまわり ブラウザ間で 差異・問題がでやす い で、要動作確認 ● Issueが中国語でもめげない心

Slide 68

Slide 68 text

Nuxt.js開発におけるTips 76 ● Nuxt.js + TypeScript ● components/配下 管理 ● SSR ● Nuxt.jsで テスト

Slide 69

Slide 69 text

Nuxt.jsで SSR ● Nuxt.jsでSSR 確かに簡単に実現できます! ● サーバーサイドと、クライアントサイド 環境 違いを意識していない と、見つけにくいバグを生んだり、ハマったりします 77

Slide 70

Slide 70 text

● 例え fetch APIを考えてみる ● 認証をCookieで行っている場合を考える ● サーバーサイドとクライアントサイドでCookie リクエスト付与 仕方が 異なる SSRで 認証含むAPIリクエスト 78

Slide 71

Slide 71 text

● 例え fetch APIを考えてみる ● 認証絡む場合に Cookieを含めてリクエストを送る必要がある クライアントサイドでfetch APIでCookieを送る 79 // クライアントサイドはこれでOK fetch("https://example.com", {credentials: "include"}) .then(e => e.text()) .then(text => { console.log(text) })

Slide 72

Slide 72 text

● 例え fetch APIを考えてみる ● 認証絡む場合に Cookieを含めてリクエストを送る必要がある サーバーサイドでfetch APIでCookieを送る 80 // サーバーサイドでは、Cookieはリクエストオブジェクトに含まれる const parsedCookie = cookie.parse(context.req.headers.cookie); fetch("https://example.com", { headers: { Cookie: `session_id=${cookie["session_id"]}` } }) .then(e => e.text()) .then(text => { console.log(text) })

Slide 73

Slide 73 text

● こ 辺り 差分を吸収できる設計にしておかないとあとで大変になる ● プロジェクト固有 問題ですが、APIクライアントを自動生成してた で、 そ 辺吸収するようなコード書く が辛かった・・・ SSRで 認証含むAPIリクエスト 81

Slide 74

Slide 74 text

window / document へ アクセス 82 ● 不用意に window、 documentにアクセスするようなコードがあると、 SSRするときだけエラーが起きてしまう ● ただしこれ 書いていれ すぐ気がつける、Vueを使っていると window / document を書く必要がある機会 少ない で、それほど問 題にならないかも

Slide 75

Slide 75 text

SSR非対応なプラグインへ 対応 83 ● document / window を参照するようなプラグイン 読み込むだけでエ ラーになってしまう // plugins/wysiwyg.js // window / documentをめちゃくちゃ参照しそうなプラグイン import Vue from 'vue'; import HogeWysiwygEditor from 'hoge-wysiwyg-editor'; Vue.use(HogeWysiwygEditor);

Slide 76

Slide 76 text

SSR非対応なプラグインへ 対応 84 ● SSR時に 読み込まないようにnuxt.config.jsで ssr: false オプションを つける // nuxt.config.js plugins: [{ src: '~plugins/wysiwyg', ssr: false }],

Slide 77

Slide 77 text

Nuxt.jsで SSRについて 85 ● 結論として SSRする 途中でやめることにしました ○ SSRしたときだけ起こる見つけにくいバグを生んでしまいそう ○ QAが大変になりそう ● ほぼログイン前提かつ、SEOが必要ないアプリケーションで、SSR 必 須要件で なかったため、途中からmode: spaに切り替え ● デプロイ mode: spaでビルドしたも を配信 ● SSR簡単にできるけど、認証含んだAPIが必要だったり、外部ライブラリ 読み込んだりしていくと、辛くなってくる で、最初 設計でサーバーサ イドとクライアントサイド 違いをよく考慮しましょう〜

Slide 78

Slide 78 text

Nuxt.js開発におけるTips 86 ● Nuxt.js + TypeScript ● components/配下 管理 ● SSR ● Nuxt.jsで テスト

Slide 79

Slide 79 text

Nuxt.js アプリケーションをテストしやすくするために ● components/とpages/ 棲み分けを意識する ● components/ にマウントしただけでAPIアクセスしたりするコンポーネントがある と、扱いにくいしテストしにくい ● 初期データ 取得など 基本的に pages/ 配下で行うようにする ● pages/コンポーネント Nuxt.jsに依存する で、できるだけ薄くしておくと、コン ポーネント テストがしやすくなる 87

Slide 80

Slide 80 text

Nuxt.jsで テスト 考え方 90 ● pages/ テスト E2Eテストで担保する ● components/配下 テストをjest + vue-test-utilsで頑張る ● コンポーネント テスト どうする?

Slide 81

Slide 81 text

ところでVueFesで 素晴らしい発表がありました 91

Slide 82

Slide 82 text

92

Slide 83

Slide 83 text

とても勉強になる資料だった で みなさんチェックしましょう 93

Slide 84

Slide 84 text

まとめ ● レガシーなアプリケーション UIをNuxt.jsでリニューアルしました ○ Nuxt.jsを使うとフロントエンド 開発にレールができる ○ ドキュメントが豊富でキャッチアップが容易 ● Nuxt.js開発 Tips ○ TypeScriptも使えます!おすすめです! ○ components/配下 管理 ○ SSR クライアント / サーバー環境 違いを意識して、設計初期段階から 抽象化しよう ○ pages/配下 薄く保って、テストしやすい状態を作ろう ● VueFes 資料 知見に溢れている で皆さん読みま しょう!!!(来年 採択されるぞ!) 94