Lean Baseball

No Engineering, No Baseball.

nuxt-coreuiでデータサイエンスの可視化ダッシュボードを自作してみる

このエントリーは、Vue.js #3 Advent Calendar 2018アドベントカレンダー初日の記事です.

開幕投手をつとめます、@shinyorkeと申します.

この記事では、「データサイエンスもいいけど自前で可視化ダッシュボード作ると面白いぞ!」っていうテーマで、

で実際作った時の勘どころやノウハウを紹介します.

TL;DR

  • データサイエンスに必要なダッシュボードを作りたくなったらnuxt-core-uiを使おうぜ!
  • nuxt-core-uiのグラフの動的更新はvue-chartjsのmixins.reactivePropで、API(データ)の呼び出しは普通にaxiosでやろう
  • オレオレ分析基盤は作ると楽しい

完成イメージ

データ(DBとか、何でもいい)をグラフで可視化する、Redash(の簡易版)っぽいダッシュボードの雛形をnuxt-core-ui(Nuxt.js + CoreUI)でやってみます.

f:id:shinyorke:20181129084441p:plain
nuxt-coreuiでのダッシュボード例

なお、こちらは今年のPyCon JP 2018で発表したモノでもあります.

おしながき

Who am I?(お前誰よ)

  • @shinyorke(しんよーく、と読みます)
  • 野球データ解析・分析のベンチャー「ネクストベース」の野球エンジニア兼CTO
  • バックエンドにインフラ、データサイエンスにフロントと何でもやってるが基本Pythonの人

なお、Vue.js(含むNuxt.js)歴は半年とちょっとです(今年から触り始めた)*1が、縁あってこんなエントリーを書かせてもらったりもしてます.

shinyorke.hatenablog.com

登場人物の紹介

このエントリーで登場するモノです.

なお、

  • Vue.jsおよびNuxt.jsはSPA(Single Page Application)を作るものだよ
  • Vue.jsおよびNuxt.jsで開発できる環境は揃ってるよ(そろってない人は、ここと、ここを見てね&やってね)
  • Vue.jsおよびNuxt.jsでグラフを書くならvue-chartjsやぞ

を前提条件としてこの先の話を進めます(要はvue-cliが使えればなんとでもなります).

CoreUI

まず、これはVue.jsに限った話ではないのですが、フロントエンドでWebの管理画面を作るテンプレートとして、CoreUIという便利なテンプレートがあります.

🆓 Free Bootstrap Admin Template · CoreUI

何ができるかは、ライブデモをご覧頂いたほうが早いと思いますが、大雑把に言うと

  • 画面遷移ができる状態のテンプレが手に入る、しかもレスポンシブ(Bootstrapがベースとなっている)
  • ダッシュボードに必要なコンポーネントがほぼ全て入ってる(グラフ、カードなど)
  • (後述の通り)いろんなjavascript frameworkの実装がある

ちゃんと使って作ればRedashのフロントエンドに親しいものは実装できるかと思います.

なお、無料版とプロ版があり、プロ版の方がコンポーネントが揃っていたりサポートもあるみたいですが、今回は無料版でのお話です.

nuxt-coreui(この記事のメイン)

前述の通り、CoreUIは様々な実装があり、

  • 通常版(framework使ってない)
  • Angular
  • React.js
  • Vue.js

これらが用意されています.

ちょっとJavaScriptに慣れている人だったり、「俺はVue.jsより○○(ReactとかAngular)何じゃ!」って方はそれぞれのVer.を使えば良いかなと思います.

そんなCoreUIのVue.js版をNuxt.js化したやつがこのエントリーで紹介するnuxt-coreuiというものです.

GitHub - muhibbudins/nuxt-coreui: 💫 NuxtJS + CoreUI Project — Unofficial Nuxt + CoreUI project, free to use boilerplate for every need.

nuxt-coreuiをはじめる

はじめ方はメチャクチャ簡単です(Vue.jsで開発したことある人にはおなじみの光景).

README.mdとおりにやれば動きます.

インストール

vue-cliが使えている人はこれで終わります.

$ vue init muhibbudins/nuxt-coreui hello_nuxt_coreui
$ cd hello_nuxt_coreui
$ npm install

ひとまず動かす

npmもしくはyarnで動きます(これもおなじみの光景)

$ npm run dev

デフォルトではPort 3000が開くので、ブラウザでhttp://localhost:3000とかしてあげれば、ライブデモとほぼ同等の内容が動くと思います.

あとはお好きなエディタでページやコンポーネントの中身を変えたり、新しいページを作ったりして中身の理解をしてみましょう.

ダッシュボードを作るための下ごしらえ

優秀なテンプレから実用的なダッシュボード(に限らず、Webサイト全般)を作るためには、

  • CoreUIのテンプレから不要なページを追い出す
  • assets類を変更する・捨てる
  • ページ構成に従い画面遷移を変更する

といった泥臭い仕事作業が必要です.

全部上げるとキリが無いので、私個人が最もハマった

  • グラフとページの連携
  • 複数のAPI呼び出しを束ねた上での処理

について解説します.

グラフのデータをPage側からの同期で変更する

元リポジトリの棒グラフのサンプル(BarExample.vue)のコードを見ると、

オリジナルの棒グラフ

<script>
import { Bar } from 'vue-chartjs'

export default {
  extends: Bar,
  mounted () {
    // Overwriting base render method with actual data.
    this.renderChart({
      labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
      datasets: [
        {
          label: 'GitHub Commits',
          backgroundColor: '#f87979',
          data: [40, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11]
        }
      ]
    })
  }
}
</script>

(サンプルという名前の通り)、labelsとdatasetsが直接記載されていて困っちゃいます、現実のアプリではこんな書き方をするわけがありません.

ので、公式の手順に従い、こんな感じでリアクティブに変更可能な書き方にしました.

リアクティブ後の棒グラフ

グラフライブラリの方

<script>
  import {Bar, mixins} from 'vue-chartjs'

  export default {
    extends: Bar,
    mixins: [mixins.reactiveProp],
    props: {
      chartData: Object,
      options: Object
    },
    mounted() {
      this.renderChart(this.chartData, this.options)
    }
  }
</script>

呼び出し方はこんな感じ.

chartDatasetsおよび、chartOptionsにそれぞれdatasetsとoptionを渡します(中身はmethods内とかで一生懸命作る).

        <b-card header-tag="header">
          <div slot="header">
            BarGraph Sample
          </div>
          <div class="chart-wrapper">
            <bar-graph :chartData="chartDatasets" :options="chartOptions"/>
          </div>
        </b-card>

RESTful API呼び出し(axiosを入れる・使う)

このブログのちょっと前のエントリーですが、こちらですこーし触れました.

shinyorke.hatenablog.com

大雑把に言うと、

  • axiosを使って呼び出しをシンプルにする
  • 複数APIを束ねて呼ぶときはasync / awaitをうまーく重ねて使う
<script>
  import axios from 'axios'

  const HTTP_HEADER = {headers: {'Content-Type': 'application/json'}}

  export default {
    name: "hourse",
    data() {
      return {
        // 省略
      }
    },
    methods: {
      search: async function (url) {
        const response = await axios.get(url, HTTP_HEADER)
        if (response.status !== 200) {
          console.error('エラー時の処理')
          process.exit()
        }
        const body = response.data
        this.getStatsJapan(body.stats_japan)
        this.getStatsOther(body.stats_other)
      },
      getStatsJapan: async function (url) {
        const response = await axios.get(url, HTTP_HEADER)
        if (response.status !== 200) {
          console.error('エラー時の処理')
          process.exit()
        }
        const body = response.data
        // TODO データを埋め込む
      },
      getStatsOther: async function (url) {
        const response = await axios.get(url, HTTP_HEADER)
        if (response.status !== 200) {
          console.error('エラー時の処理')
          process.exit()
        }
        const body = response.data
        // TODO データを埋め込む
      }
      // TODO 続く
   }
</script>

先程のグラフデータのdatasets/optionもこの流れで作れるので是非試してみてください!

まとめ

実際に作った分析ダッシュボードは前述の通り、PyCon JP 2018で発表させてもらいました.

発表のメインはnuxt-coreuiではなくてPython(Django、Luigiほか)でしたが笑.

実際作った・使った感想としては、

  • 社内のオレオレ分析ダッシュボードとして十分使えるクオリティ、キビキビ・ヌルヌル動いて体験的にも良い.*2
  • 追加開発・保守運用もメンバーがVue.js知ってる・使える前提でイケそう.*3
  • 基本のグラフ(棒とか折れ線とか)以外のやつもなんとかしたい、d3.jsとか組み込めないものか.*4

ということで、社内で作ってる別のVue.jsアプリと加えて社内基盤として整理できたらなと思っています.

というわけで、nuxt-coreuiいいぞ!の紹介はここまで、明日はpenton310さんよろしくお願いします!

*1:触り始めたキッカケは2月からJOINした弊社で、だいぶVue.jsが気に入ったので今はSPA開発をVue.js/Nuxt.jsでやってます(フロントエンドエンジニア募集中だよ)

*2:ベンチマークはとってませんが、ローカルでもサクサク動く、待ち時間も少ないといい感じです.Djangoでキャッシュ、DB側でインデックス張りまくりと他のノウハウを駆使しての結果ですが笑

*3:社内のSPAは基本Vue.jsと決めてるのでこの辺障害・障壁はなさそう.

*4:表現力必要な可視化とかどうしよう問題は常にある.

勉強会・もくもく会に参加する際に守ってほしい2つのこと - Webスクレイピングと立ちふるまいの話

f:id:shinyorke:20181112232514p:plain

人によっては割と耳に痛い話かもですが、エンジニアのコミュニティやイベント、ひいてはインターネットを私達が平和に優しく渡り歩くためにも、ちょっと言っとかないとアカンかも、と思い書きます.

具体的には先日、私が主催しているイベント(Pythonもくもく自習室)でちょっとしたヒヤリハット*1があったので、注意喚起および、もう同様のトラブル・事故が起きませんように!という訴えでもあります.

TL;DR

  • 大前提として、ネットもオフライン(勉強会・もくもく会)も社会に迷惑をかける行為は行わない
  • WebサイトやAPIに対する大量アクセス(Webスクレイピング)をもくもく会・勉強会の会場から行うのはご法度(会場じゃなくても注意すべき)
  • もくもく会・勉強会でのコミュニケーションで「粘着」「押し付け」はやめて、自然に距離をとって「相手を認め」てあげよう

もくじ

勉強会・もくもく会で社会に迷惑をかける行為 #とは

って書くとたくさん上がりそうですが、このエントリーでは以下の2つに限定します

  • 悪意がなくても、作成・実行したプログラムで他のウェブサイトやインターネットの世界に迷惑をかける行為.具体的には「相手サイトが不安定になったり落ちたりするようなクローリング・Webスクレイピング」など
  • 悪意がなくても、他の参加者に必要以上に付きまとったり、自分を押し売りするような行為(業務やプライベートの話を聞き出す、過剰な会社・自分の宣伝etc...)

Webスクレイピングをする上で守ってほしいお作法

Python・機械学習ブームで「ちょっとPython覚えたら次はデータ取得でスクレイピングを...」という方が増えているのと、(私主催の勉強会もそれ以外も)スクレイピングまわりでちょっと危なそうな人たちが見受けられるので改めてまとめました.

賛否両論はさておき、 書いてる意味がわかんない人は外の勉強会・もくもく会(&あなたの会社や学校)でスクレイピングはやめたほうがいいと思います!

クローラーやスクレイピングを作って運用するのはそれなりのエンジニア知識とスキルがないとアカンことだ!というのは十分ご理解の上やったほうがいいです.*2

まずはここを読んで理解しよう(法令や事例、技術的に守るべきこと)

以下にリンクを掲載しますが、一言で言うと

趣味・興味およびお勉強レベルでたとえ悪意がなくても、相手のサイトやAPIに迷惑をかけたらほぼ確実に実施した人に責任があります

勉強中だから、初心者だからという理由は通用しないと思ったほうがいいです.

vaaaaaanquish.hatenablog.com

岡崎市立中央図書館事件 - Wikipedia

法的な解釈や事例に対する意見、技術の各論は書くと長くなるので省略しますが、これらの中身を理解し、安全な形でクローラーやスクレイピングを書いて実行しましょう.

中途半端な解釈でやっちゃうと間違いなくやけどします

勉強会・もくもく会で絶対にやっちゃいけないWebスクレイピングのお作法

上記をふまえ、私が心がけている&やっちゃいけない!と決めてるルールです.

会場ネットワークから、相手サイトに影響を与えるような大量リクエストを行うスクレイピング・クローリングコードの実行

言うまでもなく、勉強会・もくもく会会場からの大量リクエスト(アタック)と解釈される可能性が高くなります

何かがあったときは、コードを走らせるあなたもですが、アクセス元の場所(会場)に連絡が行きます*3

APIの規約上、アクセスOKだったらいいとして...そのようなAPI・サイトは世の中そんなに多くはないです.*4

テストが不十分かつ、止められないスクレイピング・クローリング実行を長時間放置

相手サイトが不安定になる・止まる、会場ネットワークに迷惑が...と言ったときに対応できない状態ができるのはご法度です.

よくあるシチュエーションとしては、ランチの間に実行とか、これはホント危険なのでやめましょう(ちなみにネットワークアクセスをしない計算ならむしろやるべき).

必要以上に悲しみが広がります.

安全にWebスクレイピングをやる方法

とはいえ、スクレイピングをやっちゃダメ!というわけでないです.

相手サイトやAPI、そして他の参加者や会場に迷惑をかけない方法で行いましょう.

自分が持ってるリソース、クラウド環境やVPNからスクレイピングする

大量アクセスが発生するガチなクローラーやスクレイピングはAWSやGCP、個人でVPNがあればそこで実施

万が一大量アクセスで何かやらかしても、会場や他の参加者に迷惑をかけずに済みます.

「サーバー代が」とかケチってやらず、安全策をとりましょう.

キャッシュできるクローラー・スクレイピングのフレームワークを使う

代表的なものだとScrapy(Python)など.

標準でキャッシュ機構を持ってる(デフォルトは無効だが)なので、ファイルキャッシュを有効にする.

キャッシュ期間を適切に持てば同じページを何回アクセスしても初回の一回のみで済みます.

ローカルファイルに対して行う

事前にスクレイピングしたいサイトをブラウザ(Chromeなど)でページ構造ごと保存→file://hogehoge/index.html的なやつを相手にスクレイピングする.

ローカルなのでそもそもインターネットに行かないので安全.

ゼロからクローラーやスクレイピングコードを書くときに有効.

勉強会に行くなら守るべき、紳士淑女なふるまい

先月、こちらのエントリーを読んで、主催者の一人として、別のイベントに参加したりしてコミュニティの恩恵に授かってる身として思うところあったので改めて.

anond.hatelabo.jp

色んな論点の意見あると思いますが、Gender関係なく以下が紳士淑女が守るべきふるまいかなと思っています.

  • 粘着しない
  • 過剰に押し付けない

粘着しない

気になる他の参加者に粘着するような行為・コミュニケーション.

  • (相手が嫌な雰囲気出してるにもかかわらず)必要以上に話したり絡んだりする
  • 相手が話しにくそうにしてるにもかかわらず、必要以上にプライベートや業務の事を聞き出そうとする
  • その他、客観的に不自然なコミュニケーション

特にランチや懇親会のときにあるかな...主催者的には注意してみているポイントでもあります.*5

過剰に押し付けない

他の参加者にオススメやイベント、自社(製品)や自分などを必要以上に押し付ける行為.

特に他のモノをDisって自分が思ってるものをAgeて押し付けるような発言やLTなど.*6

自然に、少しずつコミュニケーションしましょう

もちろん、知り合って話したい人に話をしたり、交流したりは大事(そのためのもくもく会でもある).

基本的には「相手を認める」「話を聞く」を少しずつやっていけばきっとコミュニケーションとれます.

成果発表の時に質問したり、終わったあとに「いい話でしたよ」というだけでも話はできると思うのでぜひご実践を.

【我思う】データ分析・プログラミング初心者のお題≒スクレイピング、という風潮について

私もPythonイベントのメンターとしてスクレイピングを教えていたりしたので自省の意味も含めて.

データ分析や解析、機械学習といったところになるとどうしても統計学やプログラミング(Pythonとか)、手法やアルゴリズムそして事例に目が行くし、

「Python覚えるぞ!」ってなったら言語の次にデータ集め→そや!スクレイピングや!!ってなりがちなのですが!

Webを相手にするならWebやインターネットと友だちになる、知ることがやっぱ先なので順番間違ってるんじゃね?と思い始めてきました.

スクレイピングより先にもっと他に覚えることあるんじゃね?

最後、ポエムっぽい終わりですが言いたいことは以上です.

勉強会、もくもく会にインターネットは立派な社会なのでみんなちゃんと振る舞おう!(主催者としてももっと精進します...)

*1:諸々解決済みなのでここでの言及は行いません.また、関係者以外には情報開示はしません&犯人探しはやめてあげてください(当日いた人はあのことか、と思ってもられえば)

*2:ってのと本来はスクレイピングをしなくてもいいような代替データを入手するほうがよほど近道です、このエントリーの本筋から離れるのでここでは言及しませんが.

*3:プロバイダが会場で契約してるもので、固定IPとかだったりしたら一発で特定されます

*4:有名WebサービスのAPIだったり大企業のサイトくらい?いずれにしてもやりすぎは当然だめ

*5:ちなみに私のイベントで懇親会を滅多にやらないのはこの辺のコントロールを主催として責任持ってやれる状況が毎回作れないから、というのもあります.ランチは分散するので大丈夫ですが.

*6:よくあるのが初めて来たにもかかわらず自社製品の宣伝とかセミナーの宣伝とかをやる人、とか.仕事熱心だなと思いつつ浮いてるかつ誰も嬉しくないので控えたほうがいいよと.

Vue.jsとDjango REST FrameworkでSPAなWebをやる時の勘どころ - HATEOASと非同期処理(の触り)

f:id:shinyorke:20181029214247p:plain

ボストン・レッドソックスの皆さん、世界一おめでとうございます!*1

野球ってほんと面白いですね、こんばんは野球エンジニアです.*2

このエントリーでは楽しい野球技術、Vue.jsとDjango(Django REST Framework、以下DRFと略す)の話をサクッと書きたいと思います.

なお、このエントリーは先日開催されたPyLadies Tokyo - 4周年記念パーティのLTでやったことの自分メモをブログにしたものです.*3

元ネタ

PyLadies Tokyo - 4周年記念パーティのLTで話した内容がベースです.

speakerdeck.com

DjangoとVue.jsそしてOhtani-San - Pythonで二刀流しよう #PyLadiesTokyo 4周年

TL;DR

  • 複数カテゴリのデータをSPA + REST APIで扱うなら、最初からHATEOAS(Hypermedia As The Engine Of Application State)を考慮に入れて設計・実装したほうが幸せ
  • DRFの場合、「HyperlinkedIdentityField」で実現可能...だが、制約があるのでひと工夫が必要
  • Vue.js(に限らずJavaScriptのSingle Page Application)からHATEOASなAPIを使うときは非同期処理を配慮に入れるべき. async / awaitで比較的ラクに実現できる

おしながき

REST APIの設計パターン「HATEOAS」とは?

一言で言うと、「APIの戻り値にURIを含むことにより、次の行動を教えてあげる」APIの設計パターンのことです.

HATEOASはAPIの返すデータの中に、次に行う行動、取得するデータ等のURIをリンクとして含めることで、そのデータを見ればつぎにどのエンドポイントにアクセスをすればよいかがわかるような設計です。

※「Web API The Good Parts」第二章より引用.

なぜそれがいいのか、必要なのか?の説明はWeb API The Good Partsもしくは元の論文をご覧いただくのが良いかなと思います.

雰囲気的な例ですが、こういうAPIが欲しい時に有効です.

【例】とある競走馬の日本の成績と海外の成績のURIを返すAPI

{
    "horse_id": 12345678,
    "horse_name": "レイデオロ",
    "stats_japan": "http://example.com/stats/japan/12345678/",
    "stats_other": "http://example.com/stats/other/12345678/"
}

こういうのをDRFでなんとかします.

「HATEOAS」をDRFで実装する

結論から言うと、

パラメータが一個の場合

「HyperlinkedIdentityField」というそのまんまな名前・役割を果たすクラスを用いて実装可能です.

例えば生成したいURIが

example.com/stats/japan/{horse_id} ※horse_idはunique key

といった場合、

class HorseStatsSerializer(serializers.HyperlinkedModelSerializer):
    stats_japan = serializers.HyperlinkedIdentityField(view_name='japan')
    stats_other = serializers.HyperlinkedIdentityField(view_name='other')

    class Meta:
        model = HorseModel
        fields = ('horse_id', 'horse_name', 'stats_japan', 'stats_other')

DRFだとこんな感じで書けます、シンプルですね.

パラメータがN個(1..n)の場合

パラメータが一個のときはHyperlinkedIdentityFieldで事足りますが、以下のようなパターンだとちょっと手こずります.

example.com/stats/japan/{year}/{horse_id} ※とある🐎(horse_id)の年度別(year)成績がほしい

yearとhorse_id、パラメータが2つあります.

上記パターン(パラメータ2つ)はDRFでは標準対応していません(2018/10/29現在)

このような場合は「複数パラメータを扱うHyperlinkedIdentityField的な奴」を自前で用意する必要があります.

(同じような問題は結構起こり得るせいなのか)既にDRFのISSUEでこの話題があります.

Support multiple parametersfor Hyperlinked fields?

HyperlinkedIdentityFieldを継承して、複数パラメータで対応できるよう書き直すのが近道っぽいです.

# 元のISSUEからまんま引用しています.

from rest_framework.relations import HyperlinkedIdentityField
from rest_framework.reverse import reverse

class ParameterisedHyperlinkedIdentityField(HyperlinkedIdentityField):
    """
    Represents the instance, or a property on the instance, using hyperlinking.

    lookup_fields is a tuple of tuples of the form:
        ('model_field', 'url_parameter')
    """
    lookup_fields = (('pk', 'pk'),)

    def __init__(self, *args, **kwargs):
        self.lookup_fields = kwargs.pop('lookup_fields', self.lookup_fields)
        super(ParameterisedHyperlinkedIdentityField, self).__init__(*args, **kwargs)

    def get_url(self, obj, view_name, request, format):
        """
        Given an object, return the URL that hyperlinks to the object.

        May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
        attributes are not configured to correctly match the URL conf.
        """
        kwargs = {}
        for model_field, url_param in self.lookup_fields:
            attr = obj
            for field in model_field.split('.'):
                attr = getattr(attr,field)
            kwargs[url_param] = attr

        try:
            return reverse(view_name, kwargs=kwargs, request=request, format=format)
        except NoReverseMatch:
            pass

        raise NoReverseMatch()

利用する側はこんな感じになります(これも雰囲気コード).

from rest_framework import serializers

from sample_app import ParameterisedHyperlinkedIdentityField as UrlField

from .models import Horse


APP_NAME = 'keiba'


class HorseSerializer(serializers.HyperlinkedModelSerializer):
    stats_japan = UrlField(
        view_name=f'{APP_NAME}:stats_japan_year',
        lookup_fields=(('year', 'year'), ('horse_id', 'horse_id')),
        read_only=True
    )
    stats_other = UrlField(
        view_name=f'{APP_NAME}:stats_ohter_year',
        lookup_fields=(('year', 'year'), ('horse_id', 'horse_id')),
        read_only=True
    )

    class Meta:
        model = Horse
        fields = (
            'year',
            'horse_id',
            'horse_name',
            'stats_japan',
            'stats_other',
        )

これでDRFでHATEOASなAPIができます、あとは呼び出す側をなんとかしましょう.

HATEOASなAPIをVue.jsから使いこなす

一言で言うと、

async / awaitを使った非同期処理で解決可能です.

私がやった野球アプリの場合、

  • 最初に投手・打者の成績を呼ぶAPI...のURIを返す
  • それぞれのURIを別々に呼び出す、お互い関係はないので非同期にしちゃう(操作性・ストレス軽減のため)

という方針がシンプルに纏まっていたので、

  • Vue.jsのcreateもしくは画面のボタン(検索)を押したタイミングで最初のAPI(投手・打者成績のURIを返すAPI)を呼ぶ
  • 投手・打者のURIを別々に呼び出す

というのをasync / awaitで解決しました.

<script>
  import axios from 'axios'

  const HTTP_HEADER = {headers: {'Content-Type': 'application/json'}}

  export default {
    name: "hourse",
    data() {
      return {
        // 省略
      }
    },
    methods: {
      search: async function (url) {
        const response = await axios.get(url, HTTP_HEADER)
        if (response.status !== 200) {
          console.error('エラー時の処理')
          process.exit()
        }
        const body = response.data
        this.getStatsJapan(body.stats_japan)
        this.getStatsOther(body.stats_other)
      },
      getStatsJapan: async function (url) {
        const response = await axios.get(url, HTTP_HEADER)
        if (response.status !== 200) {
          console.error('エラー時の処理')
          process.exit()
        }
        const body = response.data
        // TODO データを埋め込む
      },
      getStatsOther: async function (url) {
        const response = await axios.get(url, HTTP_HEADER)
        if (response.status !== 200) {
          console.error('エラー時の処理')
          process.exit()
        }
        const body = response.data
        // TODO データを埋め込む
      }
      // TODO 続く
   }
</script>

Promiseを使っても良さそうですが、async / awaitの方がシンプルに書けるのでオススメです*4

まとめ

一言で言うとコードがキレイになってやりやすくなったのと、(これからやる)ページのキャッシュ実装に向けていい感じに準備できて最高でした.

やってみた感想

  • 元のコード(PyCon JP 2018の発表に向けて作ったやつ)からだいぶコードがスッキリして開発しやすくなった
  • 各データ・エンティティ毎にキャッシュ(API側)を設けるポイントが明確になり、キャッシュ戦略が立てやすくなった*5
  • DRFの複数パラメータ実装(ParameterisedHyperlinkedIdentityField)が若干イケてない気がする、パッケージないかな? or そもそもパラメータ2つ要するAPIがイケてないという説も*6
  • まだ実装してない、ユーザー認証とか入れる時に色々ありそうで怖い

これからやること

  • Redisベースのキャッシュ実装および、処理コスト高いAPI(月次・年次の平均処理とか)の高速化を進める.*7
  • そろそろユーザー認証を入れる

続きはちょこっとテーマを別けつつ、PythonやDjango、Vue.jsあたりのアドベントカレンダーで何かしら披露したいと思います.

久々の技術ネタ、これにて以上ですありがとうございました!

【Appendix】参考資料

HATEOASというワードとDRFでもいけるよ!という情報はc-bataさんの発表で知りました&参考にさせてもらいました.

www.slideshare.net

ちゃんとしたWeb APIの復習...という意味で、Web API The Good Partsを久々に読み返したりもしました.

Web API: The Good Parts

Web API: The Good Parts

*1:今年に合わせて戦力集めて運用したボストンのやり方が上手かったなあという印象、ドジャースはカーショウ(ry

*2:ドラフト、日本シリーズそして東京六大学野球(早慶戦)と満喫しています、仕事しながら⚾

*3:最近ちゃんと技術のブログを書いてないので、たまには書こうかと.アドベントカレンダーの素振りも兼ねて(今年もあと二ヶ月!)

*4:Promiseも試しましたがコードがごっちゃになって結局 async / awaitに落ち着きました

*5:キャッシュ大事なんだけど、無計画にやると後々面倒くさいバグが出たり作りがアレになるので...ここ数年嫌な思いしたのもありこの辺はトラウマですw

*6:真似して書いてる時点で悪い方の車輪の再実装なので...

*7:PyLadies Tokyo 4周年LTでAPI作ってテストした時、あまりにもAPI処理にコストが掛かってたので慌ててキャッシュを取り入れました、そのときはなんとかなったけど変なコードなのでやり直したい