今さらながら 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) に切り替わるようで、この場合には今回のエラーは発生しません。

Spring の Bean Validation でエラーメッセージにフィールド名を埋め込む

Bean Validation と書いたが、メッセージにフィールド名を埋め込むのは、正確には Spring が提供する機能らしい。

サンプルコード


アノテーションは以下の通り。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy = { SampleValidator.class })
public @interface SampleValidation {

    String value();

    String message() default "{com.example.demo.constraint.SampleValidation.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

今回は Controller で @Validated を利用してフォームバリデーションをするパターン。

@RestController
public class DemoController {

    @Autowired
    MessageSource messageSource;

    @PostMapping("index")
    public String index(@RequestBody @Validated FooForm form, Errors errors) {
        if (errors.hasErrors()) {
            errors.getAllErrors().stream()
                    .map(e -> messageSource.getMessage(e, Locale.getDefault()))
                    .forEach(System.out::println);
            return "NG";
        }
        return "OK";
    }

    static class FooForm {
        @SampleValidation("foo")
        private String name;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }

}

プロパティが未定義の場合、フォームクラスのフィールド名がメッセージの {0} にそのまま出力される。

com.example.demo.constraint.SampleValidation.message={0} is invalid.
name is invalid.

フィールド名をキーにしてプロパティを定義した場合、定義したフィールド名がメッセージの {0} に埋め込まれて出力される。

com.example.demo.constraint.SampleValidation.message={0} is invalid.
name=Name
Name is invalid.

<フィールド名> 形式と <フォームクラス>.<フィールド名> 形式を一緒に定義した場合、<フォームクラス.フィールド名> 形式のプロパティが優先される。

com.example.demo.constraint.SampleValidation.message={0} is invalid.
name=Name
fooForm.name=NAME
NAME is invalid.

Bootstrap と Vue.js で簡単なモックアップを作る

以前、Bootstrap ベースのモックアップについて書きました。

Bootstrap を利用して簡単なモックアップを作る - kntmr-blog

で、今回は Bootstrap + Vue.js 版のモックアップを作ってみました。モックアップとしての内容は Bootstrap 版と同じです。Vue.js と Webpack の初学習を兼ねているので、いろいろとあやしいところがあると思います。特に、Webpack の機能はまだよく理解できていない…。

github.com


以下、備忘録。

package.json を作る。

npm init

開発時に使うライブラリは --save-dev を付けてインストールする。実行時に必要なライブラリは --save を付けてインストールする。(たぶん)

npm install --save-dev webpack-dev-server webpack vue-template-compiler vue-style-loader vue-loader url-loader style-loader file-loader extract-text-webpack-plugin css-loader
npm install --save vue-router vue jquery bootstrap

package.json に scripts を定義して、npm run build で webpack コマンドを叩く。webpack.config.js の設定に従ってモジュールをビルドする。

{
  "scripts": {
    "dev": "webpack-dev-server",
    "build": "webpack"
  }
}

ローカルの動作環境には webpack-dev-server を使う。npm run dev でサーバーを立ち上げて、http://localhost:8080 にアクセスするとページが表示される。webpack-dev-server は、ファイルの変更を検知して自動でリビルドしてライブリロードする機能を持つ。

一応、vue ファイルでコンポーネント化しているが、試行錯誤したものの、いろいろと中途半端なところはありそう。

ご参考まで。

Oracle 認定資格デジタルバッジ

8月から Oracle 認定資格を保有していることを証明する電子証明書デジタルバッジ』の提供が開始されたようです。保有資格をオンラインで公開できるものらしい。

education.oracle.com

というわけで、Oracle Certified Java Programmer のデジタルバッジを入手。

Oracle Certified Java Programmer, Silver SE 8
Oracle Certified Java Programmer, Gold SE 8

その他

Silver SE 8 を受験したときのメモはこちら。

kntmr.hatenablog.com

Gold SE 8 を受験したときのメモはこちら。

kntmr.hatenablog.com