Nuxt.jsのSPAにvuexでサイドメニューを実装する

はじめに

サイドメニューを実装する機会があり、手っ取り早い方法がvuexだったためvuexを使って実装してみました。

そのやり方を残しておきます。

どんなアプリか

実際の動きをみてもらったほうが早いです。

f:id:kogayushi:20190824233448g:plain

こんな感じでメニューをクリックしたら対応するページに遷移しつつ、そのページのメニューが選択状態になります。

今回もgithubにあげています。

使っているもの

実装時に意識して使っていたもの雑多に並べます

  • nuxt
  • vuetify
    • v-app-bar
    • v-menu
    • v-list
    • v-tabs/v-tab
  • vuex
  • nuxt-link

どんな設計になってるか

概要はこんな感じです。

  1. ページ遷移のタイミングでmiddlewareを使ってroute.nameをvuexに保存する
  2. 現在のページに対応したサブメニューの情報をvuexのgetterから取得する
  3. 取得したサブメニュー情報を元に動的に画面を表示する
  4. グローバルナビはホバーするとサブメニューのリストを出す。
  5. 現在表示されているページに対応するグローバルナビのメニューを選択状態にする
  6. 現在表示されているページに対応するサイドメニューを選択状態にする

(ざっくり)コード解説

設計とはきれいに対応していません。あしからず。

ページ遷移のタイミングでmiddlewareを使ってroute.nameをvuexに保存する

vuexに現在のページを保存するためのstateとmutationを作る

~/store/index.js
export const state = () => ({
  currentPage: 'index'
})

export const mutations = {
  setCurrentPage(state, payload) {
    state.currentPage = payload
  }
}

middlewareを使ってroute.nameを取得して保存する

~/middleware/pageId.js
export default function({ route, store }) {
  store.commit('setCurrentPage', route.name)
}
nuxt.config.js
  router: {
    middleware: 'pageId' // nuxtに教えておかないと動かない
  }

現在のページに対応したサブメニューの情報をvuexのgetterから取得する

各カテゴリのサブメニューの定数を定義する

~/store/index.js
const TOP_SUBMENU = {
  title: 'index',
  menus: [
    {
      pageId: 'awesome',
      name: 'awesome',
      url: '/awesome/menu1'
    },
    {
      pageId: 'wonderful',
      name: 'wonderful',
      url: '/wonderful/menu1'
    }
  ]
}

const AWESOME_SUBMENU = {
  title: 'awesome',
  pageGroup: 'awesome',
  menus: [
    {
      pageId: 'awesome-menu1',
      name: 'awesome-menu1',
      icon: 'phone',
      url: '/awesome/menu1'
    },
    {
      pageId: 'awesome-menu2',
      name: 'awesome-menu2',
      icon: 'email',
      url: '/awesome/menu2'
    },
    {
      pageId: 'awesome-menu3',
      name: 'awesome-menu3',
      url: '/awesome/menu3'
    }
  ]
}

const WONDERFUL_SUBMENU = {
  title: 'wonderful',
  pageGroup: 'wonderful',
  menus: [
    {
      pageId: 'wonderful-menu1',
      name: 'wonderful-menu1',
      icon: 'inbox',
      url: '/wonderful/menu1'
    },
    {
      pageId: 'wonderful-menu2',
      name: 'wonderful-menu2',
      icon: 'move_to_inbox',
      url: '/wonderful/menu2'
    },
    {
      pageId: 'wonderful-menu3',
      name: 'wonderful-menu3',
      icon: 'send',
      url: '/wonderful/menu3'
    }
  ]
}
stateにsubmenuを登録する
export const state = () => ({
  currentPage: 'index',
  submenu: {
    index: TOP_SUBMENU,
    awesome: AWESOME_SUBMENU,
    'awesome-menu1': AWESOME_SUBMENU,
    'awesome-menu2': AWESOME_SUBMENU,
    'awesome-menu3': AWESOME_SUBMENU,
    wonderful: WONDERFUL_SUBMENU,
    'wonderful-menu1': WONDERFUL_SUBMENU,
    'wonderful-menu2': WONDERFUL_SUBMENU,
    'wonderful-menu3': WONDERFUL_SUBMENU
  }
})
pageIdからサブメニューを取得するためのvuexのgetterを定義する
export const getters = {
  submenuOf: (state) => (pageId) => {
    if (state.submenu[pageId]) {
      return state.submenu[pageId]
    }
    return TOP_SUBMENU
  }
}

ちなみにこのgetterにはディスパッチテーブルと言われるデザインパターンを使っています。 これにより、今後カテゴリが増えたとしても、それに対応するサブメニューの定数を宣言し、それをstateに設定すれば対応完了できます。

取得したサブメニュー情報を元に動的に画面を表示する

現在のページを取得するためにvuexのgetterを定義する

export const getters = {
  currentPage(state) {
    return state.currentPage
  },
  submenuOf: (state) => (pageId) => {
    if (state.submenu[pageId]) {
      return state.submenu[pageId]
    }
    return TOP_SUBMENU
  }
}

サイドメニューコンポーネントを作る

<template>
  <v-card class="mx-auto" max-width="300" tile>
    <v-list>
      <v-subheader>{{ title }}</v-subheader>
      <v-list-item v-for="menu in menus" :key="menu.pageId" :to="menu.url" nuxt>
        <v-list-item-icon>
          <v-icon v-text="menu.icon"></v-icon>
        </v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title v-text="menu.name"></v-list-item-title>
        </v-list-item-content>
      </v-list-item>
    </v-list>
  </v-card>
</template>

<script>
export default {
  computed: {
    title() {
      return this.$store.getters.submenuOf(this.$store.getters.currentPage)
        .title
    },
    menus() {
      return this.$store.getters.submenuOf(this.$store.getters.currentPage)
        .menus
    }
  }
}
</script>

vuexから値を取得し、その値に応じてメニューが書き換わるようになってます。

読めばわかると信じて解説はしません。雰囲気で感じ取ってください。

f:id:kogayushi:20190824233534j:plain

ヘッダー(グローバルメニュー)コンポーネントを作る

<template>
  <v-app-bar color="#6A76AB" dark :scroll-target="scrollTarget">
    <v-toolbar-title>
      Side Menu Example with Nuxt.js and Vuex
    </v-toolbar-title>
    <template v-slot:extension>
      <v-tabs align-with-title background-color="transparent">
        <v-tab
          v-for="menu in grobalMenus"
          :key="menu.pageId"
          :to="menu.url"
          :class="{ 'v-tab--active': isSamePageGroup(menu.pageId) }"
          nuxt
          v-on="on"
        >
          <v-menu open-on-hover offset-y>
            <template v-slot:activator="{ on }">
              <span text v-on="on">
                {{ menu.name }}
                <v-icon right>arrow_drop_down</v-icon>
              </span>
            </template>
            <v-list class="grey lighten-3">
              <v-list-item
                v-for="submenu in menus(menu.pageId)"
                :key="submenu.pageId"
                :to="submenu.url"
                nuxt
              >
                {{ submenu.name }}
              </v-list-item>
            </v-list>
          </v-menu>
        </v-tab>
      </v-tabs>
    </template>
  </v-app-bar>
</template>
<script>
export default {
  props: {
    scrollTarget: {
      type: String,
      required: true
    }
  },
  computed: {
    grobalMenus() {
      return this.$store.getters.submenuOf('index').menus
    },
    menus() {
      return (pageId) => {
        return this.$store.getters.submenuOf(pageId).menus
      }
    },
    isSamePageGroup() {
      return function(pageId) {
        return this.$store.getters.isSamePageGroup(pageId)
      }
    }
  }
}
</script>
  • ホバーするとサブメニューが表示されるようにしてあります。
  • サブメニューはvuexから取得しています。
  • 本当はcomputedで定義している内容はpropsで渡したほうが良いと思いますが、今回の趣旨ではないのでサボっています。

読めばわかると信じて詳しい解説はしません。雰囲気で(ry

作成したコンポーネントを利用するようにdefault.vueを修正する

<template>
  <v-app>
    <v-content>
      <v-card class="overflow-hidden" height="100%">
        <the-header scroll-target="#scrolling-techniques-4" />
        <v-sheet id="scrolling-techniques-4">
          <v-container grid-list-md text-xs-center>
            <v-layout row wrap>
              <v-flex xs3>
                <side-menu />
              </v-flex>
              <v-flex xs>
                <v-card tile height="100%">
                  <nuxt />
                </v-card>
              </v-flex>
            </v-layout>
          </v-container>
        </v-sheet>
      </v-card>
    </v-content>
    <v-footer fixed app>
      <span>&copy; 2019</span>
    </v-footer>
  </v-app>
</template>

<script>
import TheHeader from '~/components/TheHeader'
import SideMenu from '~/components/SideMenu'

export default {
  components: {
    TheHeader,
    SideMenu
  }
}
</script>

ヘッダーとサイドメニューを配置しているだけです。

まとめ

このような感じで、vuexを使って現在表示しているページに対応するサブメニューを表示してみました。

割と簡単に実装出来たし拡張もしていけるようにしたつもりですが、いまいちこれで本当に良いのか自信がありません。

もっと良い実装方法をご存知の人は是非教えて下さい。

参考にしたサイト

https://qiita.com/natsumi527/items/189d8e122bf2ee02f099

TypeScriptを学んでいる

TypeScriptを学び始めている中で感じていること

実践TypeScriptをざっくり読んで、ざっくりとTypeScriptについて理解したので現時点の感想を残しておく。

はじめに

自分用メモの位置づけの記事です。

現時点で好印象を抱いています。

感じた違和感

今まで身につけてきたオブジェクト指向を前提とした言語のパラダイムとはなんかちょっと違う感じがする。

特に、気に入らないわけじゃないが型定義ファイルに慣れない。違和感の正体はまだわからない。

理解したメリット

ピュアなJSだと、戻り値の型が不明で脳内デバッグしながら書くコストがあるのを少し問題に感じていた。

また、Vueだとコンポーネントにどんなオブジェクトを渡せば良いのかが対象のコンポーネントの中身を読まないとわからなかったりする問題があると感じていた。

型定義があるおかげでそれらが改善されるのは理解した。

null許容とnull非許容には好印象

null安全を実現できる言語仕様は良い。kotlinを学んだときも思ったが、コードの書き手にnullのときのハンドリングを強制できるのは良い。

絞り込まれていく型推論

型定義には違和感がありはするが、型チェックで型推論される型がどんどん絞り込まれていくのには書きやすそうな印章を受けた。この良さを実感すれば型定義を書くことの違和感は消えるのかもしれない

VS Codeが前提

ただし、型推論の恩恵を受けるためにはVS Codeを利用するのが大前提になる…のかな

まだ、Intellijでの書き心地を試してないがきっとそうだと思う

重厚感はある

確かに壊れたらある程度はコンパイルエラーで気づけるので大規模なアプリには適していそう。

同様の理由で長期間利用される想定のアプリにも適しているだろう。

例えば、propsの渡し方が変わったときとか。

寿命が短かったりずっと小規模のままが想定されるアプリにはちょっと重厚すぎる印象ではある。

弊社サービスには現時点では採用不可

覚えてもらうのにかかるコスト以上のメリットが返ってくるイメージがわかなかった。

教えるコストがそれほどかからないなら採用可能かもしれないが、今すぐ採用すると全員にペアプロで教えるレベルの手間がかかるはず…。

今後の計画

実践編のReact/NextとExpressを読み飛ばした&それ以外の箇所も写経していないので、まずVue/Nuxtのチャプターの写経をする。

その後、導入編を読み直しての気になった章と理解が浅い章の写経をする。

おそらく、1ヶ月強はかかるのではなかろうか。

それが済んだらフロントエンドはSPAをNuxt.jsとTypeScript(とVuetify)で、バックエンドはRESTful APIをKotlinとSpring Boot 2.1.xで、という構成で何かアプリでも書いてみよう。

ちょっと前に社内で少しだけ話題になってたThanks Cardのアプリのプロトタイプ1でも作ってみようかな?

最後に

まだまだ理解が浅いのでもっと学ぶと意見が変わると思う。

少なくともすぐに自社サービスに採用できないからといって、途中で学習を辞めようとは思わないくらいには可能性を感じている。

キャッチアップが終わったときに印象がどうかわっているか、楽しみ。


  1. これのパクリアプリ

brewで古いバージョンをインストールする

微妙に分かりづらかったので、メモを残しておきます。

Formulaのディレクトリに移動
$ cd /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/
古いコミットをフェッチ
$ git fetch --unshallow
インストールしたいバージョンを確認する
$ git log {application}
インストールしたいバージョンをチェックアウトする
$ git checkout {hash} {application}.rb
インストールする
$ brew install {application}
masterに戻す
$ git reset --hard
以下のブログを参考にしました。

qiita.com

Kotlinの好きなところ

tl;dr

  • kotlinを勉強していて特に気に入った言語仕様と例をひたすら紹介します
  • 紹介するコードは実際に書いてgithubにあげています
  • 2019/08/04に改訂しました。

気に入った言語仕様

  1. 演算子オーバーロード
  2. ifとwhen(switch)が式
  3. null安全
  4. イミュータブルにしやすい
  5. 拡張関数

紹介する言語仕様です。 他にも気に入っているものはあるのですが、サンプルコードや例を思いついたものだけ今回は紹介していきます。

1. 演算子オーバーロード

  • 独自定義した型で算術演算子を使える
    • + - * / % > < >= <= など
  • 例)
    • 有効期限日型と現在日付型の比較
      • 有効期限 < 現在日付
    • 有効期限の延長を足し算っぽく書ける
      • 有効期限 + 7L

言語固有の演算子の一部をメソッドとして利用できます。 +-といった記号ですね。

例に書いてあるとおり、プログラムをより直感的に表現することができるようになる仕様です。

コード例

data class ExpirationDate(val value: LocalDate) {
    operator fun compareTo(value: CurrentDate): Int { // <,>,<=,>=
        return this.value.compareTo(value.value)
    }

    operator fun plus(value: Long): ExpirationDate { // +
        return ExpirationDate(this.value.plusDays(value))
    }
}

class CurrentDate(val value: LocalDate) {
    operator fun compareTo(value: ExpirationDate): Int { // <,>,<=,>=
        return this.value.compareTo(value.value)
    }
}

実際の例です。 特定のシグニチャの条件を守ることで、メソッドが演算子として書けるようになります。

不等号の例でいくと、operatorとcompareToとIntの返り値といったシグニチャがそれです。 プラスの例で行くと、operatorとplusが定義されてるのがそれですね。

ちなみに引数にとれる変数の型は自分自身のクラスじゃなくても良かったりするところが使い勝手が良いポイントです。

実際にこのサンプルコードでも自分と違うクラスを受け取ってます。

使い方

// set up
val currentDate = CurrentDate(LocalDate.of(2019, 8, 4))
val expirationDate = ExpirationDate(LocalDate.of(2019, 8, 5))

// exercise
val actual = currentDate < expirationDate

// verify
assertTrue(actual) // 有効期限が切れてない

使い方の例です。 現在日付も有効期限も人が頭で考えるときは日付として捉えてますよね。

この2つの比較は日付の比較なので、どっちが未来でどっちが過去かって捉え方をしているはずです。 それって不等号を使えれば数式として自然に表現できませんか?

それをプログラムで表現するとこうなるわけです。

// set up
val expected = ExpirationDate(LocalDate.of(2019, 8, 12))
val expirationDate = ExpirationDate(LocalDate.of(2019, 8, 5))

// exercise
val actual = expirationDate + 7L

// verify
assertThat(actual, equalTo(expected))

次の例は有効期限の延長です。

有効期限を1週間伸ばすときはこんな感じですね。

有効期限 + 7と書けています。

自然言語や数式って慣れ親しんでいるものなので、こうかけるととても自然にプログラムを読むことが出来るようになります。

ただ、演算子オーバーロードという概念を知らない人には意味不明であり、混乱の元になるかもしれません。

しかし、みなさんはこの説明を読んだので、もうこういうコードを見てもすぐに理解できるはずです。 1upおめでとうございます。

何が嬉しいのか

  • 算術演算子は直感的に使える
    • 算数は誰でもできるので、数式でかけると素直に理解できる
  • 直感的な表現で数値計算や期間比較のコードを表現できるようになる

ifとwhen(switch)が式

kotlinではifやwhenは句ではなく、式です。 たぶん、言葉で表現してもわからないと思うのでコードの例を紹介します。

コード例

fun loveIf(language: String): String { // 関数名は適当
    val result = if (language.equals("KOTLIN", ignoreCase = true)) {
        "❤️" // return "❤️"と同じ意味
    } else {
        "💔" // javaならここで result = "💔"; とする必要がある
    }
    return "$language $result" // language + " " result と同じ意味
}
fun loveWhen(language: String): String {
    val result = when (language.toUpperCase()) {
        "KOTLIN" -> "❤️"
        else -> "💔"
    }
    return "$language $result"
}

まずifです。resultというローカル変数を定義していますが、ifのブロックの中でアサインするのではなくてifからreturnした値をresultにアサインしています。 これがifが句ではなくて式と言われている所以です。

whenでも全く同じことがおきています。

動作確認

// set up
val expected = "Kotlin ❤️"

// exercise
val actual = loveIf("Kotlin")

// verify
assertThat(actual, equalTo(expected))

returnされた値がちゃんと使われているかテストしています。

kotlin loveが期待値です。 loveIf関数にkotlinを与えて、その返り値がkotlin loveになっているか検証しています。

何が嬉しいのか

  • 特定の値の場合に既定値を与えるような式が簡潔になる
// Javaの例
String name = "Anonymous";
if (user.isLoggedIn) {
    name = user.name;
}
// Kotlinの例、この程度なら三項演算子でもできるが…
val name: String = if (user.isLoggedIn) user.name else "Anonymous"
  • Javaだとifやswitchで判定した結果を変数に代入する場合は見通しが悪くなりがちだし、変数をfinalにできなかった
    • final(イミュータブル)にするためにprivateメソッドなどでカプセル化して返り値を代入してた

既定値の割当でちょっと楽になります。

ただし注意なのは説明的メソッドや説明的変数の手法を使ったほうが可読性が高まるケースのほうが多いということです。

例の程度であれば十分短いので、有効なシーンといって良いかと思います。

ちなみにこのサンプルコードはネットに転がってたのをコピペしてます😅

2. null安全

  • null許容型とnull非許容型がある

Java何かと違って、nullableな変数かどうかが言語的に明確に表現されます。実際にコードの例を見てみましょう。

コード例

val notNullable: String = null // コンパイルエラー
var nullable: String? = null // null代入可

if (nullable != null) { // null check必須
    println(nullable.toUpperCase())
}

null非許容型は代入すらできません。コンパイルエラーになります。

null許容型はクエスチョンでnullかもしれないことを明示的に表現する必要があります。 さらにいうとnull許容型はnullではないことを検証するコードを書いたあとでないと、その変数を利用できない徹底っぷりです。

何が嬉しいのか

  • null非許容型にはnullが入らないので冗長なnullチェックがなくなる
  • null許容型はnullチェックしないと使えないので、うかつにメソッドを呼び出してNPEがおきにくい
  • nullかもしれない箇所ではnullを強く意識させられるのでnullの取り扱いを間違えにくい

ちなみにJavaにはOptionalがあるが、Optional自体がnullになりえるし、そもそも冗長なので私は仲良く慣れませんでした。

まだ良くわかってないこと

  • Null Object Patternとの明確な違い
    • 利用されるときの文脈によってはNOPは使いづらい
      • null許容型だとそれがマシになりそうな予感
  • Nullのときどう振る舞わせるか
    • 文脈によってnullのときどうするかが違うのが悩みどころではある
      • NOPのときに悩むことと同じ
      • これは業務で利用しながら考えるしかなさそう

3. イミュータブルにしやすい

  • data class x copy関数 x 名前付き引数の組み合わせで簡単にイミュータブルなオブジェクトを作れる
    • data classは後で単体でもう一度でてきます
  • List/MutableListとMap/MutableMapがある

みなさん、イミュータブルって言葉の意味わかります?反対語はミュータブルです。

状態を持たない、という意味ですね。プログラムならクラスのプロパティの値を書き換えられないとか、変数に再代入できないとかそんなことを意味します。

イミュータブルだとバグが入り込みにくくなるとされているのですが、そのイミュータブルを実現しやすい仕様が揃っています。イミュータブルだとなぜバグを生み出しにくくなるのかについては、別途ググってください。

data classってこんなやつ

// varにするとミュータブルになるので注意
data class ValueObject(val mainValue: String, val otherValue: String)

val valueObject = ValueObject("value object")
valueObject.mainValue = "other" // 代入不可、コンパイルエラーになる

classのprefixにdataとはいっているところと、プライマリコンストラクタの変数がvalで宣言されているところがポイントです。これで完全にイミュータブルなクラスが宣言できています。

この例では、mainValueはvalで宣言されており、javaで言うところのfinalがついている状態になるので代入ができません。

data class x copy関数 x 名前付き引数の組み合わせで簡単にイミュータブルなオブジェクトを作れる

  • Javaだとイミュータブルにしようとすると、とあるインスタンスの一部のプロパティだけ更新するときが面倒くさい
    • その他のプロパティはオリジナルを維持しつつ、更新対象プロパティのみ変わった新しいインスタンスを生成する必要がある
    • kotlinならそれを簡単に実現できるcopy関数がある

data classはイミュータブルなので、何かしらの状態を更新したい場合は更新された状態の新しいインスタンスを生成する必要がありますが、kotlinにはそれを簡単に実現できるcopy関数が存在します。

コード例
// set up
val valueObject = ValueObject(mainValue = "value object",
                              otherValue = "other value")

// exercise
// otherValueのみ書き換える
val otherValueObject = valueObject.copy(otherValue = "copied object")

// verify
assertThat(valueObject.mainValue, equalTo(otherValueObject.mainValue))
assertThat(valueObject.otherValue,
           not(equalTo(otherValueObject.otherValue)))
assertThat(otherValueObject.toString(),
           equalTo("ValueObject(mainValue=value object,
                                otherValue=copied object)"))

例だとother valueのみ書き換えています。

verifyの箇所で、copy前後でmainValueが同じこと、copy前後でother valueが変わっていること、copy前後でtoStringの結果がちゃんと変わっていること、などを検証しています。

List/MutableListとMap/MutableMapがある

// listOfだとイミュータブルなリストになる
val immutableList = listOf("a", "b", "c")
immutableList += "d" // コンパイルエラーになる
val added = immutableList + "d" // 既存Listにdをaddした新しいインスタンスを生成

// mutableListOfだとミュータブルなリストになる
val mutableList = mutableListOf("a", "b", "c")
mutableList += "d" // 内部的にはaddが呼び出される

あとは良い例だと思うので取り上げていますが、listやmapはイミュータブルとミュータブルを厳密に区別されています。

例えば、この例のimmutableListはimmutableだからaddできません。そのため、自身を更新するのではなくて、自身の要素と追加対象の要素をマージした新しいインスタンス+の結果として返しています

mutableは普通のArrayListですね

ちなみにlistの操作に+とか+=とかでてきてますがいったん気にしないでください。すぐ後で簡単に説明します

集合の操作はインスタンスが生成されてから実際に利用されるまでの間に足したり引いたりといった操作がされがちで、バグり易い気がします。

immutable操作させないことを強制できるので、安定したコードを書きやすいでしょう。逆にmutableListであれば操作されている前提を持つことができるので利用時にバグを埋め込みにくいのではないかと思います。

4. 拡張関数

  • 継承をせずとも既存のクラスを拡張できる言語機能
  • 基本型にすら新しい関数を生やすことができる

intだろうがStringだろうが関数を新しく生やすことができます。実際に少し後でコードをおみせします。

Listのコード例

  • 実は先程のlistの+もこれで実現されています
// Collection<T>.plusでCollectionに独自関数を追加している
public operator fun <T> Collection<T>.plus(element: T): List<T> {
    val result = ArrayList<T>(size + 1)
    result.addAll(this)
    result.add(element)
    return result
}

Collectionにplusが生えているところがポイントです。ちなみにoperatorという修飾子がついていますが、演算子オーバーロードになっていて、+演算子でも呼び出せるようになってます。

何が嬉しいのか

  • このクラスにこんなメソッドあったらなーってときに使えます
例えば

JavaならStringのnull or emptyなチェックってGuavaでこう書きますよね?

Strings.isNullOrEmpty(target)

これ、こう書けたほうが英語っぽいです

target.isNullOrEmpty()

まんまこんな感じで書けます。そうkotlinならね。

こう書いて

fun String?.isNullOrEmpty(): Boolean {
    return this == null || this.isEmpty()
}

こう使います

// set up
val sut:String? = null

// exercise
val actual = sut.isNullOrEmpty();

// verify
assertTrue(actual)

すごいでしょ?

5. 値オブジェクトに適した仕様がある

  • data classのことです

有効期限を例にとる

  • data classはたぶんこんな感じ
data class ExpirationDate(val value: LocalDate)
  • 以下が自動生成される
    • equals/hashCode()
    • toString
  • 文字列表現を手軽に得られるし、面倒なequals/hashCodeの実装がない
    • 値オブジェクトをequalsで比較するときは同値のチェックが目的なので、自動生成で良い

実証

equals()
// set up
val mockDate = LocalDate.of(2018, 8, 5).plusDays(1)
val expirationDate1 = ExpirationDate(mockDate)
val expirationDate2 = ExpirationDate(mockDate)

// exercise & verify
assertEquals(expirationDate1, expirationDate2) // 同値なのでtrue

equalsが自動で生成されていることを検証するコードです。Javaなんかだと、equalsのデフォルト実装は同一性の検証となるため、このコードは失敗するはずですが、kotlinではdata classのequalsは同値性を検証するコードを自動生成するので、このテストは成功します。

hashCode()
val mockDate = LocalDate.now().plusDays(1)
for (i in 0..10) {
    // set up
    val expected = 4135425
    val sut = ExpirationDate(mockDate)

    // exercise
    val actual = sut.hashCode()

    // verify
    assertEquals(actual, expected) // 何度実行しても同じHash値
}

hashCode()もequalsと同様です。検証コードをお見せしましたが解説は飛ばします。

toString()
// set up
val expected = "ExpirationDate(value=2019-08-05)"

// exercise
val actual = ExpirationDate(LocalDate.of(2019, 8, 5)).toString()

// verify
assertEquals(actual, expected)

JavaでもtoStringは自動生成されますが、ハッシュ値がでてくる実装になっています。kotlinではdata classは人間が見て意味がわかる文字列表現を自動生成してくれます。

何が嬉しいのか

  • 値オブジェクトのときのボイラープレートなコードを自動生成してくれること
  • 前のスライドで示した通り、イミュータブルであること

ちなみに...

Javaで値オブジェクトを実装するときは値の妥当性をコンストラクタで検証しますが、Kotlinでも同じことができます。initializerブロックがそうです。

JavaでもできることをKotlinでも出来ますという紹介にしかならないので、今回は割愛します。

5. その他の気になってる言語仕様

  • スマートキャスト
    • javaでいうinstanceOfをしたら、もうその変数がキャスト済みになる
  • エルビス演算子 ?:こんなやつ
  • エイリアス
  • in句
    • list#containsよりこっちのほうがわかりやすい
  • tryが式
    • if/whenと同様
  • Stringをregexに変換するtoRegexメソッド
    • regexをより直感的に使いやすくなる
  • トリプルクォート
    • 面倒なエスケープを入れなくてよくなるらへんが気に入りそう

AWS EKS上のアプリからSESでメールが飛ばせない不具合にどう対処したか

現象

Spring Boot x AWS EKS x Istio x AWS SESって組み合わせでアプリを動かしていますが、SESにメールを送ろうとしても以下のような感じのExceptionが出てメールが送れないという現象に遭遇しました。

Request processing failed; nested exception is org.springframework.mail.MailSendException: Failed messages: com.amazonaws.SdkClientException Unrecognized SSL message, plaintext connection?

ローカルで試す分には問題ないにも関わらず、です。

対処法

仕方がないので、AWS SESをSMTPサーバとしてapplication.yamlに設定してみたら普通に動き出しました。 ワークアラウンドでしか無い気がしますが、誰かの役に立つかもしれないのでメモを残しておきます。

こんな感じです。

spring:
  mail:
    host: email-smtp.{region}.amazonaws.com
    port: 465
    protocol: smtps
    properties:
      mail:
        smtp:
          auth: true
    username: {your username}
    password: {your password}

今回は、すぐにデモをしないといけない都合上時間がないので諦めました。

Spring Cloud AWSに乗っかる場合は、FWのコードを解析してどんなリクエストを送ろうとしているのか突き止めれば動かせるようになると思います。

追記[2019.07.26]

これだけじゃ足りないかもしれなくて、IstioのServiceEntryとVirtualServiceも追加しています。

さらに細かく検証できたらもう一度追記するかもしれません。

データサイエンティストに聞いてわかったデータ分析基盤アーキテクチャの勘所

データサイエンティストに聞いてわかったデータ分析基盤アーキテクチャの勘所

ログ収集&可視化のためのアーキテクチャについて、同僚のデータサイエンティストからお知恵を拝借しました。

お聞きした内容がかなり有益だったため、メモを残しておきます。

ログの保存場所

  • 生ログは必ず全件保存すること
  • 分析タイミングや分析ツールの都合で、適したDWHとなるデータストアに保存すること

とりあえず、S3に保存してさえおけば分析はできる

  • athena quicksight使えば良い

ElasticSearchのindexの作成単位

  • 細かく分けておくと分析しやすい
  • 細かく分けておくとライフサイクルを変えることができる
    • ログローテートをするかしないか、する場合の期間など
    • 例)
      • BIツールで使う分析用データは半永久的に保存しておく
      • OSやkubernetes関係の統計データは2週間程度で捨てる
  • アプリごとにindexをわけておくのが良い
  • ログの種類ごとのものもindexをわけておくこともある
    • warnとerrorログは抽出して別indexにしておく、とか
  • indexがわかれても、統合して横断的に検索する方法はある
    • 横断的に検索したい対象のindexは、前方一致でHITするように名前の付け方を工夫する
      • e.g.
        • our-awesome-application-2019.07.22
        • our-special-application-2019.07.22
          • our-*とすれば、awesomeとspecialの両方のindexが検索対象になる

S3に保存した過去の生データを分析する必要が出てきたら?

  • 選択肢は3つ
    1. athena quicksightを使う
    2. S3をデータソースに使えるその他の可視化ツールを使う
    3. 分析しやすいDWHを用意してそこに突っ込む

生データを用途別に仕分ける方法

  • AWSならkinesisかlambdaが使えそう
    • リアルタイム性が必要な場合
      • fluentdからkinesisに流してそこから分岐させる
    • リアルタイム性が不要
      • S3にログをアップロードする。そのイベントを拾ってlambdaを発火して分岐させる