JJUG CCC 2017 Fall に行ってきた #jjug_ccc

簡単に所感をまとめます。セッション資料はどこかで公開されるはず。

www.java-users.jp

Selenide or Geb? あなたはそのときどちらを使う?

  • Selenide
    • Java製WebDriverラッパー
    • DSLによるjQueryライクなセレクタ
    • IDEの補完を活用できる
    • Ajaxなどの非同期処理に対する記述が書きやすい
  • Geb

    • じぇぶ
    • Groovy製WebDriverラッパー
    • DSLによるjQueryライクなセレクタ
    • waitが書きやすい
  • Selenideのメリ/デメ

  • Gepのメリ/デメ

    • Selenideより細かい制御ができる
    • Groovy/Gradleの知識が必要
    • IDEの補完は効きづらい
  • ローカルでは動くのにJenkins上では落ちることがある

    • おそらく環境依存などが原因 (jsのロードが遅れるなど)
    • Selenideは失敗して落ちるとスクリーンショットとHTMLが残る
  • マルチブラウザテスト

    • GebConfig上でWebDriverの生成処理を変更することで対応
    • Selenideも設定ファイルでWebDriverの生成処理を変更することができる
  • PhantomJSやChromeのヘッドレスモードを使ってテストする

    • E2Eのテストなので実際は本物のブラウザでテストするべきという考えがある
  • 読むのを楽にしたいか/書くのを楽にしたいか

    • 書きやすさならSelenide
    • 読みやすさならGeb
  • SelenideはIDEのサポートがあるので取っ付きやすい
  • Gebは学習コストがやや高め
  • Selenideはテスティングフレームワーク (簡単なアサーションはある)
  • GebはSpockなどと組み合わせる

最近、Selenide を試してみたところだったので話が聴けてよかったです。両方ともIDEのサポートはあるものの、Intellij IDEA との親和性が高そう。書くのをラクにしたいか、読むのをラクにしたいかで選べばよい、とのこと。確かに、Selenide は書きやすそうな印象。非同期処理に対する記述のところは調べておきたい。

Apache Camel + hawtio + Spring Boot によるモダンなインテグレーション・マイクロサービス

  • インテグレーションマイクロサービス
  • Smart Endpoints and Dump Pipes

    • マイクロサービス自体に他のサービスと連携する機能を持たせる
    • ルーティングロジックがエンドポイント(各マイクロサービス)の中にある
    • パイプはシンプルにする
  • マイクロサービス間が連携したらスパゲッティにならないか

    • そのために Enterprise Service Bus がある
  • コンウェイの法則

    • サービスと開発チームが小さくなる
    • インテグレーションはスパゲッティになるが、個々の開発チームはシンプルになる
  • Apache Camel

  • ルーティングはXMLでも書ける (Spring XML DSL)

    • IDEGUIを使う場合はXMLを使う必要あり
  • Content-Based Router

    • コンテンツの内容に応じてルーティング先を決める
    • choice / when / to
  • hawtio

    • Angular 1.x + Jolokia ベースの監視ツール
    • HTTP/JSONで通信
    • Spring Boot 連携 (監視用のポートで起動する)
    • Camelサポートが豊富
      • ルーティングがグラフィカルに見れる
      • やり取りしているメッセージが見れる
  • Camelのテストフレームワークでルーティングのテストができる

  • コンポーネントが多いので選定などは大変っぽい

マイクロサービスの開発自体あまりなく名前程度しか知らなかったんですが、Apache Camel はよさそうです。機会があれば使ってみたい。hawtio は初めて聞いた。

DDD × CQRS - コマンドとクエリでORMを使い分けた話

  • CQRS
    • コマンドクエリ責務分離
    • 単独でも適用可能
    • イベントソーシングと組み合わせる必要はない
  • 書き込みと読み込みのモデルを分離する

  • 従来は単一のデータストアで同じモデルが読み書きを担う

    • 1モデルの処理が複雑になる
  • そもそも読み込みと書き込みの要件は異なる

    • パフォーマンスを求められるのは参照系
  • 1モデルに詰めるとどこかで無理がでてくる

  • モデルを読み書きで分離する

  • データストアを分離する

    • 参照系は read-only なレプリカにするなど
  • イベントソーシング

    • すべてのアクションをイベントとして記録する
    • 既存のデータはupdateしない
    • Writeモデルはイベントとして記録される
    • Readモデルはイベントから変形される
  • 読み込み書き込みそれぞれの要件に合わせてORMを選定する
  • JOOQ
    • SQLに近い記述でタイプセーフにSQLが書ける
  • Writeモデルの制約はコンストラクタと公開メソッドで実現する

CQRS についてはよく分かってないマンですが、読み込みと書き込みでモデルを分けるというのは参考になりました。特に要件に合わせてORMも分けるというのは思い付かなかった...。

劇的改善 CI4時間から5分へ〜私がやった10のこと〜

  • R-SET
  • CIは10分で終わらせるのがいい (遅くとも20分以内が望ましい)

  • レイヤごとにテストの責務を閉じる

    • RepositoryとRDBはまとめてテスト
  • Controllerのテストは @WebMvcTest にする
    • @SpringBootTest はすべてのBeanを登録してアプリケーションを起動するので遅い
  • ServiceレイヤのテストではDIは使わない
    • Springの機能は使わない (RepositoryはMock化する)
  • RepositoryはFilterでBeanを選択する
    • @MyBatis or @MyBatisTest で必要なBeanのみを登録する
    • MyBatisに限らず必要なBeanをフィルタリングするのは大事
  • テストサイズ導入, テストサイズによる実行タイミングの分割
    • Googleが提唱している
    • Small, Medium, Large
    • 実行タイミングはプルリクエストやブランチのマージなど
    • LargeテストはいわゆるE2Eテスト
    • JUnit@Category, maven-surefire-plugin の groups で実現する
    • mvn の Profile で実行するテストサイズを切り替える

たぶん、無駄なテストを改善する/除去するというのが最も効果ありそう。テストサイズについては初めて知りました。あとで調べてみよう。

Java SE 9の紹介: モジュール・システムを中心に

Java SE 9の紹介: モジュール・システムを中心に

  • Maven, Gradle を使っても複数バージョン混在地獄は解決できない

    • jarファイルの名前しか見ないため (中身までは見ない)
  • モジュールシステム

    • モジュール単位で依存関係を整理する
    • 内部パッケージの使用を制限する
  • 複数のモジュールで同一パッケージが含まれる場合、JVM起動時点で検知される
  • module-info.java

    • 外部に公開するパッケージは exports 命令で指定する
    • 自分が使うモジュールは requires 命令で指定する
    • requires したモジュールの exports されたパッケージの public なクラスが使える
    • それ以外を使うとコンパイルエラー/実行時例外
  • 標準ライブラリである java.base モジュールは暗黙的に requires される

  • モジュールシステムはリフレクションにも適用される

    • opens 命令で外部からリフレクションでアクセスできるようになる
    • モジュール宣言に open を修飾するとすべてのパッケージが opens 指定される
  • モジュールパスとクラスパスは別

  • 無名モジュール/自動モジュール

  • ライブラリ提供者はすぐにモジュール化すること

    • 使う側が requires 命令を書けない
  • Properties クラスは Latin-1 のまま

この日、最も聴きたかったセッション。モジュールシステムの概要が分かってよかった。要復習。なんとなく、ちゃんと作ってるライブラリであれば、モジュール化はそこまで難しくなさそうな印象。

その他

今年春のCCCは参加できなかったのですが、参加人数が多くてかなり混雑してた模様。今回は、前回より参加人数が少なかったのか、それほど混雑は感じられませんでした。というか、通路が一方通行になってたりいろいろ工夫されてたのがよかったのかも。移動がいつもよりずっとラクでした。

Vue.js でウィジェットっぽいもの (仮) その2

前回の続き

Vue.js でウィジェットっぽいものを作ってみる (仮) - kntmr-blog

続編として今回は以下をやってみようかと。

  • Webpack で出力するファイルをまとめる
  • ウィジェットにパラメータを渡せるようにする
  • 任意のタグ/キーワードで表示できるようにする

Webpack で出力するファイルをまとめる

vue-cli の Webpack テンプレートでは CommonsChunkPlugin プラグインがデフォルトで設定されている。普通はこのままでよいが、今回はウィジェットとして作成するので、HTMLから読み込むファイルをまとめたい。

出力ファイルの設定は webpack.prod.conf.js に記載されている。で、plugins の中に記載されている webpack.optimize.CommonsChunkPlugin の箇所をコメントアウトする。尚、デフォルトで出力される vendor と manifest についてはまだよく分かっていません...。

あと、CommonsChunkPlugin プラグインについては、以下が参考になりました。

qiita.com

ウィジェットにパラメータを渡せるようにする

以下が参考になりました。Vue インスタンス生成時に render 関数の中で App.vue の props に渡すというもの。

qiita.com

今回は、HTML に data 属性でパラメータを設定する。

<div id="app" data-tag="vue.js"></div>

で、main.js の中で取得して store に格納する。

new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App },
  render: function (createElement) {
    const dataset = this.$el.dataset
    this.$store.dispatch('setQuery', dataset.tag)
    return createElement('App')
  }
})

その他、Vue インスタンス生成前にやるなら以下でも可。

import store from './store'

const element = document.getElementById('app')
const dataset = element.dataset
store.dispatch('setQuery', dataset.tag)

任意のタグ/キーワードで表示できるようにする

上記、store に格納したパラメータを API アクセス時にクエリで渡す。とりあえず、これで任意のタグでウィジェットを表示できる。キーワードも同様にできると思うので今回は省略。

import axios from 'axios'

const api = {
  get: (url) => return axios.get(url)
}
export default {
  getItems: (query) => api.get(`https://qiita.com/api/v2/items?query=${query}`).then(resp => {
    return Promise.resolve(resp.data)
  })
}

あと、store にデータとクエリを一緒に持たせるのはどうなんだろうか。個人的には悪くない気もするけど...。

Vue.js でウィジェットっぽいものを作ってみる (仮)

Qiita Widget : Qiitaの投稿を表示できるブログパーツ - Qiita

こんな感じのウィジェットっぽいものを、Vue.js と Veux の学習を兼ねて作ってみようかと。今回は Qiita の Vue.js タグを表示するもの。

github.com


以下、備忘録。

vue-cli

vue-cli の Webpack テンプレートを使用してプロジェクトを作成する。

npm install -g vue-cli

最小構成でやるのでテストとかはないです。

vue init webpack vue-widget-sample
cd vue-widget-sample
npm install

es6-promise

IE 対応のため、es6-promise をインストール。IE では Promise が使えない。

npm install --save es6-promise

src/main.js の先頭に以下を追加。

import 'es6-promise/auto'

Vuex

本家サイト(ja)GitHub の examples を参考に実装。当初、下図が全然ピンと来ていなかったのですが、コードを書いてみてようやく理解できてきました。(たぶん)

https://vuex.vuejs.org/ja/images/vuex.png

ドキュメントに書いてありますが、小規模なアプリであればイベントバスで十分かもしれません。もしくは、props だけでイケるかもしれません。が、ある程度は規模に関係なく Vuex は導入してよいかと思います。このアーキテクチャに沿うことで全体的にシンプルに書けるようになると感じています。

あと、以下が参考になりました。

chibinowa.net

ウィジェット

こんな感じで読み込む。このあたりはまだ改善の余地あり...。

<!DOCTYPE html>
<html>
<head>
  <meta charset=utf-8>
  <title>vue-widget-sample</title>
  <style type=text/css>
    .my-widget {
      width: 360px;
      height: 400px;
    }
  </style>
  <link href="/css/app.css" rel="stylesheet">
</head>
<body>
  <div id=app></div>
  <script type="text/javascript" src="/js/manifest.js"></script>
  <script type="text/javascript" src="/js/vendor.js"></script>
  <script type="text/javascript" src="/js/app.js"></script>
</body>
</html>

これからやりたいこと

  • Webpack で出力するファイルをまとめる
  • ウィジェットにパラメータを渡せるようにする
  • 任意のタグ/キーワードで表示できるようにする

今さらながら Selenide を使ってみる

簡易なテストページを作って試してみました。

サンプルコード


以下、備忘録。

各ブラウザの WebDriver をダウンロードして配置する。

Third Party Browser Drivers - Downloads

テスト対象のブラウザを設定する。

{
    Configuration.browser = WebDriverRunner.CHROME;
    System.setProperty("webdriver.chrome.driver", "driver/chromedriver.exe"); // WebDriver のパス
}

テストコードからは jQuery のようにセレクタを使って要素を指定する。

open("http://localhost:8080/");
$(By.name("input")).val("foo").pressEnter();
$("#output").shouldBe(text("foo"));

テストコードに同じセレクタが重複するのを避けるため、Page Object パターンを使う。

Page Objects - Documentation

ページ要素を Page Object クラスに隠蔽する。

@FindBy(name = "input")
public SelenideElement input;
@FindBy(id = "output")
public SelenideElement output;

テストコードからセレクタを排除できる。

IndexPage page = open("http://localhost:8080/", IndexPage.class);
page.input.val("bar").pressEnter();
page.output.shouldBe(text("bar"));

Selenide としては、ページ要素のフィールドを private にして、ページ要素に対するロジックをメソッドで提供することを推奨している模様。

IndexPage echo(String val) {
    $(By.name("input")).val(val).pressEnter();
    return this;
}
String output() {
    return $("#output").text();
}

テストコードにはページ要素を操作するメソッド呼び出しとアサーションを記述する。

IndexPage page = open("http://localhost:8080/", IndexPage.class);
page.echo("buz");
assertEquals("buz", page.output());

JJUGナイトセミナー「メッセージングミドルウェア特集」に行ってきた #jjug

先日、JJUGナイトセミナー「メッセージングミドルウェア特集」に行ってきました。簡単に所感をまとめます。

jjug.doorkeeper.jp

メモから抜粋。(資料が公開されたら貼っておきます)

実運用して分かった Rabbit MQ の良いところ・気をつけること

  • オープンソースのメッセージブローカー
  • 複数のメッセージプロトコルに対応している
  • スタンドアローンでもクラスタ構成でも構築が可能
  • 多言語サポート
  • プラグインが豊富
    • AMQP以外のプロトコルを追加できる
    • 認証機能, 管理画面
  • Web API でリソースやトラフィックの監視が可能

  • RabbitMQ 導入前はブローカーのないキュー管理の仕組みを利用していた

    • 接続設定の変更などが面倒
    • 設定ファイルをすべてのファイルに持たせる必要がある
    • Producer, Consumer が簡単に追加できない
  • キュー管理から RabbitMQ へ

    • AMQPプロトコルをサポート
    • クライアントライブラリがある (Java, PHP)
    • クラスタ構築が簡単
    • Producer, Consumer の追加が簡単
    • トピックとキーでルーティングが可能
  • バックエンドサーバの前段に RabbitMQ のクラスタを配置
  • Java から PHP へのメッセージの受け渡しができる
  • クラスタ構成で耐障害性が高い
  • クライアントは RabbitMQ クラスタを向けるだけでよい
    • Producer, Consumer の追加が簡単
  • 現在は1クラスタで 1000万msg/day を Consumer が処理している

  • RabbitMQ の前にロードバランサーを配置してみた

    • メッセージがなくなる現象が発生
    • LBの設定でセッションが維持されずコネクションが途中で切れることが原因
    • RabbitMQ の Java クライアントにロードバランスする機能が実装されている
  • RabbitMQ はデフォルトでメッセージをディスクに書き込む設定になっている

    • 大量のメッセージが処理しきれずクラスタが応答しない現象が発生
    • メッセージをメモリで扱うように設定を変更
    • マスタ1台はディスク, スレーブ2台はメモリ
    • RabbitMQ はディスクを使うことを推奨している (メモリは特殊ケース)
  • 管理プラグインの Message Rates の設定がデフォルトで Basic モード (メッセージの流量をモニタする)

    • このオプションを無効にしたらパフォーマンスが向上した
    • メトリック監視とパフォーマンスはトレードオフ
    • スループットが要求される場合はプロビジョニングに注意
  • 無停止バージョンアップ

    1. Producer を新しい RabbitMQ クラスタに向ける
    2. クラスタに残っているメッセージを捌き切ったら旧クラスタを停止
  • ネットワーク障害時
    • クラスタを落として再起動 (ドキュメントにも書いてあるので問題なし)

40分弱でわかる Apache Kafka

  • スケーラブルな分散pub/sub型のメッセージングシステムを実現するためのミドルウェア
  • ストリーミングプラットフォーム
  • pub -> 仲介者 (ブローカー) -> sub

    • pubとsubを非同期に分離して疎結合
  • 小規模なシステムでも使える (大規模なシステムに限らない)

  • オートスケールではない
  • プロトコルは独自バイナリ
  • パーティション単位で順序を保証する

  • 複雑なデータパイプラインをシンプルにする

  • ストリーミングデータを処理する (リアルタイム処理)

  • Kafka はディスクにメッセージを書き込む

  • オフセット (Consumerが次にどこを読むか) を柔軟にコントロールできる
  • 指定時間内はデータが損失なく読み直すことができる安心感

  • ZooKeeper (分散コーディネーションシステム)

    • 高い可用性と信頼性
    • クラスタマネジメント
    • 死活監視
    • ACL情報のストア
  • Kafka で扱うメッセージは独自フォーマットのバイナリ

  • トピックはメッセージストリームのラベル
    • ラベルの名前は任意
    • Producer は1〜複数のトピックにメッセージを投げる
    • トピックは負荷分散のためパーティションに分割される
  • パーティション
  • Consumer Group
    • 複数の Consumer を論理的にグルーピングできる
    • オフセットによって Consumer Group ごとにどこまで読み込んだかを Consumer が覚えている
  • Extract -> Transform -> Load (ETL)
    • Kafka からデータを取得して加工して Kafka に戻す

メッセージキュー「Pulsar」の紹介

  • Yahoo!で開発されたpub/sub型メッセージングシステム
  • マルチテナント
    • 1つのMQに複数のサービスが同居できる
    • 他のサービスのトピックへのアクセスは認証/認可機構でブロックできる
  • トピックが階層化されている (ネームスペース単位で設定変更が可能)
  • 地理的に離れたデータセンターのクラスタ間でレプリケーション

    • すべてのデータセンターにメッセージを Publish するのは非効率
    • MQ内部でデータセンターをまたいでレプリケーションしてくれることが望ましい
    • Producer は自分のデータセンターの Pulsar にメッセージを送るだけでよい
    • あとは Pulsar がレプリケーションしてくれる
  • クライアントライブラリは Java, C++, Python

    • 多言語からは WebSocket API で利用可能
  • pub/sub はトピックURIで Pulsar に接続する

  • Subscription Type

    • Exclusive : 1つの Subscription に対して、1つの Consumer
    • Shared : 1つの Subscription に対して、複数の Consumer (Consumer Group)
    • Failover : Exclusive + Consumer が落ちたら別の Consumer に failover する
  • Consumer から Broker に ACK を返すとキューからメッセージが削除される

  • BookKeeper

    • 先行書き込みログ (SSD) と 永続化ストレージ (HDD)
    • 速度と永続性の両立を実現する

素人目ですが、後発の Pulsar はやはりいい感じに見えました。ただ、他のミドルウェアにも特徴がいろいろあって適材適所だと思うので、システムの特性に応じて選定できるようになるとよいのかなと思います。これを書いてる時点では資料は公開されてないのですが、最後にミドルウェアの比較があったので、それはぜひ読み返してみたいです。

今回はミドルウェアの話がメインでしたが、そのうちメッセージングシステムの設計の話とか聞いてみたいです。

JSUG勉強会 2017年その7 〜 俺たちのマイクロサービス に行ってきた #jsug

JSUG勉強会 2017年その7 に行ってきました。簡単に所感をまとめます。

jsug.doorkeeper.jp

今回のテーマはマイクロサービスです。メモから抜粋します。

無理をしないマイクロサービス

  • マイクロサービスアーキテクチャは手段
  • 組織や体制に合うやり方でこれまで実現できなかったことを実現する

    • やり方はそれぞれ違う
  • 各チームの機能追加を独立でデプロイできるようにする

  • リリーススピードを上げることが目的

  • よくあるアーキテクチャ

    • フロントエンドUI <–> サービスA, B, C, …
    • フロントエンドUI <–> API Gateway <–> サービスA, B, C, …
  • フロントエンドUIに変更が集中する

  • 各サービスがUIを持ち、各チームで開発する

  • ユーザ情報をどうやって連携するか

    • ユーザサービスを立ててSSOにする
    • 認証情報は Cookie に保存する
    • 各サービスはサブドメインで分ける
    • 昔からあるSSOのやり方
  • Cookie には JWT (JSON Web Token) で保存する
    • ..
    • Base64エンコードしてCookieに保存する
    • クライアント側はデコードするだけで使える
    • なりすまし防止のため、Header と Claims を秘密鍵で署名する (Signature)
    • 各サービスに公開鍵を渡しておく
  • Spring Security

    • JWT を公開鍵で検証、Base64 でデコードして Spring Security の UserDetails を生成する
    • Cookieがなければユーザサービスのログイン画面を表示する
  • 各サービスの画面レイアウトを統一したい

    • レイアウトを管理するチームを用意する
    • 共通レイアウトを jar でパッケージングして配布する
    • src/resources/templates/layout.html
  • aタグのURIはプロパティから取得する

    • 全サービスでプロパティのコピペが発生する
    • 共通化する (共通化し過ぎると独立してデプロイし辛くなるため、独立性を妨げない程度に共通化する)
    • Spring Cloud Config
    • Config Server で管理する
  • 他のサービスとの連携はAPI化する

    • どこかのAPIで障害が発生したときに呼び出し元に障害が伝搬する
  • Circuit Breaker
    • 障害の伝搬を防ぐ (fail fast)
      • アクセスできないようにする
    • 自動で復旧する
    • fallback が書ける
  • Spring Cloud Netflix (Hystrix)
  • それぞれのAPI呼び出しに Circuit Breaker を入れる
    • それぞれで HystrixCommand を書くのは煩雑
  • 中間に API Gateway を入れる
    • 各サービスは API Gateway を経由して各APIを利用する
    • fallback をここに書く
  • Zuul / Zuul2

  • Service Discovery

  • Envoy

    • サイドカーパターン
    • C++ベースのリバースプロキシ
    • サービスの代わりに Envoy 同士が連携して Service Mesh を構成する
    • Service Mesh の管制塔の役割として Istio がある
  • API呼び出しで必要となる入出力クラスをどうするか

    • 共通ライブラリにはしない (他チームの変更の影響を受けやすい)
    • 各サービスで必要なフィールドだけを定義したクラスを用意する
    • JSONをそのまま使う
    • API提供側がクライアントライブラリを配布する
  • プラットフォーム

    • k9s, Cloud Foundry
    • Spring Boot なら Docker コンテナを使う必要性はあまりないかも
      • jar自体がjavaコマンドで起動できるしポータブルなので
  • CI/CD

    • ビルド/デプロイの自動化
    • Concourseのパイプライン

俺のマイクロサービス -マイクロサービスに関する経験と考察-

  • 問題意識をベースにすることが大事
  • 誰かの真似ではない

  • マイクロサービスは、設計、実装、デプロイ、運用など多岐に亘る概念である

  • 結果論
  • グッドプラクティスに名前を付けたものがマイクロサービス
  • マイクロサービスの目的はスケーラビリティとアジリティを向上すること

    • それが必要な企業は決して多くはない
    • 多くの企業にとってはマイクロサービスは必要ではないかも
  • HTML5 + API

    • APIが再利用できない問題
    • 画面デザイン > API の順番で作ると、特定画面向けのAPIになってしまう
  • 深夜リリース

  • Start simple, not small

  • Spring Boot

    • Executable JAR == マイクロサービス
    • デプロイの局所化、Spring Cloud との連携が可能
  • Service Discovery + Load Balancing
    • Eureka
    • Spring Boot と組み合わせやすい
  • Queuing
  • Session Replication
    • スティッキーセッション
    • Spring Session + Hazelcast (ライトスルーができる)
  • Versioned Platform
    • サービスごとに言語やフレームワークが選べる
    • ノウハウの分散が懸念
    • 親pomでフレームワークのバージョンを統一する
    • Spring IO Platform の親pomを使う
      • 今は使っていない
      • バージョンアップ待ちがボトルネックになることも
    • 管理されたマイクロサービス
  • Shared Interface
    • サービス側の変更によってコンシューマ側の実装を変更しないといけない
    • サービス側とコンシューマ側で同じBeanを実装するのは無駄
    • 共通のインタフェース
      • サービス側とコンシューマ側が使う
      • 同じようなコンシューマの実装があちこちに
    • 実際はクライアントライブラリのアプローチをとった
      • 共通インタフェースやBeanもクライアントライブラリに入れる
  • Service Unit Test
    • 各サービスの Controller 以降を JUnit でテスト
      • DBアクセスを含めたテスト
    • 他のマイクロサービスの呼び出しはモックにする
      • Mockito などは使わず、オーバーライドでモックする
    • Spring Boot の機能で end-to-end を JUnit でテスト
  • Service Integration Test
    • マイクロサービスごとを結合するテストは必要
    • 各サービスを手動で立ち上げて JUnit から HTTP 通信でテストする
    • 正常系と異常系の数パターンをテストする
    • エラーの発生を正しく伝搬できるか確認する
    • インタフェースの齟齬は意外と多い
  • CI/CD
    • デプロイ数/対象が多すぎて手動だと大変、サーバの準備も大変
    • 環境構築を自動化する
    • 環境構築は Ansible で自動化
    • ビルドは Jenkins で自動化
    • 将来的にはブルーグリーンデプロイを目指したい
    • APIのバージョニングも大事 (初期段階から考慮する)
  • Monitoring
    • 監視は大事
    • Elasticsearchを中心としたモニタリングの仕組み
    • メトリクスやログの収集
    • Metricbeat, Filebeat, …
  • Front Service & Backend Service
  • Serverless
  • Shared Data Store
    • マイクロサービスごとに Data Store を用意する
    • マイクロサービス間で Data Store を共有する
    • KVS
  • Event Sourcing
    • 例) 買い物かご (商品追加、削除、空にする、など)
    • 買い物かごにイベントを発生させる
    • イベントの種類に商品追加、削除、空にする、などのイベントがある
    • 発生したイベントはすべて記録する
    • イベントの積み重ねによって買い物かごの状態を判断する
    • 状態のソーシングではなくイベントのソーシング
    • 処理の追い越しや不正な画面遷移が発生た場合にどう対処するか

これが正直な感想ではあります。あとマイクロサービスの起原は以下のブログです。(たぶん)

martinfowler.com

日本語訳はこれ。

kimitok.hateblo.jp

これを読むとマイクロサービスはあくまでも「結果論」であることが分かります。まずは、自分たちが解決したい問題や課題はなにか、これを明確にする必要があります。マイクロサービスは手段であり目的ではないわけです。

初期段階からマイクロサービスアーキテクチャを見据えることは大事だと思います。が、マイクロサービスは最先端の開発手法だから取り入れていくべきだ、みたいなひとたちはまだまだたくさんいると思います。先日、某大企業のセミナー資料にもそんなニュアンスのことが書かれていて、ちょっと残念な気持ちになりました…。

最後に Spring Fest 2017 の告知がありました。2017/11/24 開催です。

springfest2017.springframework.jp

java.lang.IllegalArgumentException: Comparison method violates its general contract!

自作の Comparator でリストをソートしたら初めて見るエラーメッセージが。

java.lang.IllegalArgumentException: Comparison method violates its general contract!

再現コード

Java 1.8.0_92 です。

なかなか再現できずいろいろ試した結果、以下のようになりました…。

public class Foo {
    private String name;
    public Foo(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
public class Sample {
    void execute(List<Foo> list) {
        list.stream()
                .map(o -> map(o.getName()))
                .sorted((o1, o2) -> compare(o1.getName(), o2.getName()))
                .count();
    }
    static Foo map(String name) {
        return new Foo(name.startsWith("a") ? name.toUpperCase() : null);
    }
    static int compare(String str, String other) {
        if (str == null) {
            return 1;
        }
        if (other == null) {
            return -1;
        }
        return str.compareTo(other);
    }
}

実行コード。

// リストの要素は32個以上
List<Foo> list = Arrays.asList(
        new Foo("xyz"), new Foo("abc"), new Foo("xyz"), ... , new Foo("xyz"), new Foo("abc"), new Foo("xyz"));

new Sample().execute(list);

解決 (とりあえず)

compare を以下のように変更するとエラーが発生しなくなります。

static int compare(String str, String other) {
    // ここを追加
    if (str == null && other == null) {
        return 0;
    }
    // ここまで
    if (str == null) {
        return 1;
    }
    if (other == null) {
        return -1;
    }
    return str.compareTo(other);
}

TimSort

すみません、詳しくは理解していないのですが、sort の内部では TimSort というアルゴリズムが使われており、Comparator のロジックに矛盾がある場合に今回のエラーが発生するようです。

上の再現コードでは、Comparator の o1, o2 が同値になるケースがあり、これらの比較結果を返すロジックを含める必要があります。

その他

尚、リストの要素が32個未満の場合、別のアルゴリズム (mini-TimSort) に切り替わるようで、この場合には今回のエラーは発生しません。