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を発火して分岐させる

障害調査のコツ(ログの調査方法など)

新卒とかに何度も説明しているので、再共有時用のメモです

とにかくまずはアプリケーションが出力しているログを見ることです。

大抵の場合は、答えか大きなヒントがそこに書いてあります。

特にフレームワークの出力しているエラーメッセージとスタックトレースをよく見ましょう。

自分たちが書いたコードでも、異常データが入力された場合やデータ不整合を検知した箇所ではわかりやすいメッセージと共にエラーログを出力しておきましょう。半年か1年後の自分が救われます。(もっと早いかも?)

ログにヒントが無いときはどうしましょうか。

  • 現象が無応答状態ならThread dumpの各Threadの状態を観察する
  • OutOfMemoryによる突然死の場合はheap dump(発生時に出力するように設定するのを忘れないこと)を、Eclipse Memory Analyzerなどで解析する
  • Stop The Worldっぽい場合は、gc.logでFull GCが起きてないか確認する

といった感じのことをすれば大抵の場合は原因を突き止められると思います。

上記でも特定できない突然死の場合はOSのOOM Killerを疑いましょう。OOM Killerでないにしても、/var/log/messagesなどの/var/log配下のログを見ると、OSがプロセスを落としている痕跡が残っているかもしれません。

他にも症状によって疑うところはたくさんあると思いますので、上記は一例だと思ってください。

ちなみにネットワーク周りの調査だとパケットキャプチャするっていう手もあります。

例えばkeep aliveの設定が効いているかとかハーフコネクションが起きてないかとかですね。

意図した電文や正常な電文がやり取りされているか確認する手段はあるので、知っておくといつか役に立つかもしれません

私もパケットキャプチャの勉強をちゃんとやろうと思ってすべく、パケットキャプチャの教科書という書籍を買いました。

パラパラめくった感じでは、なかなか良さそうです。

まだ読んでませんが^w^

DAO(Repository)の単体テストについて

DbSetUpとAssertj-DBの組み合わせがとても良さそうです。

さらに組み込みDBのH2と組み合わせてSpringのTest系の機能を組み合わせるとほぼ理想通りのDAOのテストがかけるようになりました。

具体的には以下が実現出来ています。

  1. h2でテスト実行するからMySQLなどの本物のDBが要らない
  2. テスト実行後のデータが残るのでテスト失敗時のデータが残せる
  3. テストクラスごとにh2のデータベースが起動するので、テストの並列実行が可能(=高速化)
  4. テストデータを簡単に投入できる
  5. データを簡単に検証できる

コードの完全版は用意できませんでしたが、DbSetUpとAssertj-DBの簡単なサンプルを示しておきます。

DBのセットアップはこんな感じで、

@Before
public void setUp() {
    Operation ops = sequenceOf(DELETE_ALL, INSERT_ROLE);
    DbSetup dbSetup = new DbSetup(this.destination, ops);
    dbSetup.launch();
}

テーブルの中身の検証はこんな感じです。

Assertions.assertThat(someTable)
          .hasNumberOfRows(5)
          .row(0)
          .value("some_column").isEqualTo("awesomeValue")
          .value("other_column").isEqualTo("fantastic");

量が多くなると流石に見づらくなりますが、JavaでDBのテストを表現できると書きやすくていいですね。

この組み合わせは実際に業務で利用しています。

分散トレーシングをテーマにして勉強会に登壇してきた

だいぶ前ですが、とある企業様との合同勉強会で、分散トレーシングの説明とデモを行ってきました。

そのサンプルコードのURLを共有します。

https://github.com/kogayushi/ist-distributed-tracing-demo

スライドの元となるmarkdownファイルもこのリポジトリに入っています。

https://github.com/kogayushi/ist-distributed-tracing-demo/blob/master/What_is_distributed_tracing.md

分散トレーシングなにそれ?って人が多かったですが、ネタを仕込んでいたこともあり笑いが生まれていました。

勉強会の質としてどうだったかは…わかりませんw