Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

JShellでフォルダ出力するワンライナーを書いてみた。

概要

こんにちは、最近JShellに嵌っているuedaです。
この記事はワンライナー自慢大会 Advent Calendar 2018 - Qiitaの19日目です。
JavaのJShellを使ってテキストに書かれたとおりの構成のフォルダを出力する
ワンライナーを紹介します。

昔はJavaワンライナーなんて考えもしませんでしたが、
Java9から使えるようになったJShell(REPL)で対話的に実行する事で、
ワンライナーっぽく書ける様になりました。
中々使う機会が無いので、勉強がてらワンライナーを作成しました。

f:id:acro-engineer:20181218044827j:plain:w200
duke

環境

Java: openjdk version "11.0.1" 2018-10-16
OS: Windows10 Home

※本稿のワンライナーはJava11のAPIを使用しているため、Java10以前では動きません。

JShellを使ったJavaコマンド実行への道

Java11のダウンロード

JDKはいくつか種類がありますが、今回はAdoptOpenJDKを使いました。
adoptopenjdk.net

パスを通す

展開したAdoptOpenJDKのbinにパスを通し、「JShell」をコマンドプロンプトから実行できるようにします。

コマンドプロンプトからJShellを経由してのJava実行

> echo System.out.plintln("Hello.") | JShell -

このように「echo」+「生のJavaコード」+パイプ~JShell~ハイフンでJavaコードの単発実行ができます。
クラスやmainメソッドを書かなくて済む事を考えると、大分楽です。

注意点として、コマンドプロンプト実行ではechoコマンドが「>」を評価してしまうため、「^」(キャレット)でエスケープする必要があります。
例えば以下のような記述になります。

例 (i -> i * 10) → (i -^^^> i * 10)

本題(フォルダ出力ワンライナー)

さて、本題のワンライナーについて。
大量のディレクトリを作んなきゃ、て事はたまにありますよね。
そんな時、さくっと生成したいと考え、
テキストに書いたとおりにフォルダを出力するワンライナーを書きました。

input.txt

Excelからぺっと貼り付けたタブインデント付のテキストです。
このテキストの構成どおりのフォルダ作成を考えます。

test
	test1
		1-1
		1-2
	test2
		1-1
		1-2
		2-1
		3-1
			images
			procedure
		3-2
			images
			procedure
		3-3
			images
			procedure

本稿の主役のワンライナー

上記input.txtと同じフォルダで下記ワンライナーを実行すると、テキストに記載された構成通りのフォルダが生成されます。

>echo Stream.of(new LinkedHashMap^^^<Integer, String^^^>()).forEach(map -^^^> {try { Files.readAllLines(Path.of("input.txt")).stream().peek(k -^^^> map.put(k.length() - k.stripLeading().length(), k.strip())).map(s -^^^> s.length() - s.stripLeading().length() == 0 ? s : String.join("/", map.values().stream().limit(s.length() - s.stripLeading().length()).collect(Collectors.toList())) + "/" + s.strip()).peek(System.out::println).forEach(r -^^^> {try {Files.createDirectory(Path.of(r));} catch (IOException e) {throw new UncheckedIOException(e);}});} catch (IOException e) {throw new UncheckedIOException(e);}}); | JShell -

解説

ワンライナーのままだと読みづらいので、展開してみます。

Stream.of(new LinkedHashMap<Integer, String>()).forEach(map -> {
	try {
		Files.readAllLines(Path.of("input.txt")).stream()
				.peek(k -> map.put(k.length() - k.stripLeading().length(), k.strip()))
				.map(s -> s.length() - s.stripLeading().length() == 0 ? s
						: String.join("/", map.values().stream().limit(s.length() - s.stripLeading().length())
								.collect(Collectors.toList())) + "/" + s.strip())
				.peek(System.out::println)
				.forEach(r -> {
					try {
						Files.createDirectory(Path.of(r));
					} catch (IOException e) {
						throw new UncheckedIOException(e);
					}
				});
	} catch (IOException e) {
		throw new UncheckedIOException(e);
	}
});
最初のStreamは、処理中で使用するLinkedHashMap生成のためだけに行います。
Stream.of(new LinkedHashMap<Integer, String>()).forEach(map -> 以降実際の出力処理
ファイルを読み込み、ファイル1行ごとの文字列に対しストリーム処理を行います。
Files.readAllLines(Path.of("input.txt")).stream()
peekはストリームに影響を与えない処理を行うため、ここでMapにファイル内容を格納しています。

 Mapはキーがタブインデントの数、バリューがフォルダ名になるよう加工しています。

.peek(k -> map.put(k.length() - k.stripLeading().length(), k.strip()))
相対パスの生成を行っています。

 タブ数が0ならそのまま、0以外なら全親フォルダのパス+自分のフォルダ名がストリームに流れます。

.map(s -> s.length() - s.stripLeading().length() == 0 ? s : String.join("/", map.values().stream().limit(s.length() - s.stripLeading().length()).collect(Collectors.toList())) + "/" + s.strip()).
生成された相対パスのDEBUG出力です。
.peek(System.out::println)
生成された相対パスに対し順次フォルダ生成を行います。
forEach(r -> {try {Files.createDirectory(Path.of(r));}


書いてみて、今回の処理は流れをさかのぼる処理があったことから、
最初のmap生成など強引な処理になっているところがあります。
この辺りワンライナーに向いていなかったのではないかと後になって気がつきました。

ちなみに

Streamを使わずに、展開して記述すると以下のようになります。

	var map = new LinkedHashMap<Integer, String>();
	var resultPathList = new ArrayList<String>();
	List<String> lines = null;
	lines = Files.readAllLines(Path.of("input.txt"));

	for (String line : lines) {
		String pathName = "";
		int tabCount = line.length() - line.stripLeading().length();
		line = line.strip();
		map.put(tabCount, line);
		if (tabCount == 0) {
			pathName = line;
		} else {
			for (int index = 0; index < tabCount; index++) {
				pathName += map.get(index) + "/";
			}
			pathName += line;
		}
		resultPathList.add(pathName);
	}

	for (String resultPath : resultPathList) {
		System.out.println(resultPath);
		Files.createDirectory(Path.of(resultPath));
	}

24Lineぐらいありますね。是に比べるとStreamを使ったコードはスッキリしています。
こちらのほうが読みやすいですが。。

まとめ

以上、JShellで書いたワンライナー、いかがでしたでしょうか。
Javaはあまり向いていないとは思いますが、
ストリームを書く練習にはちょうどよいのではないかと思います。

ではでは。

Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

世界初のElastic認定エンジニアと一緒に働きたい人Wanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップの求人 - Wantedlywww.wantedly.com

JJUG CCC 2018 Fall で発表しました

皆さんこんにちは、しんどーです。

この記事はJava Advent Calendar 2018の17日目のエントリーです。
16日目はdo-m-gatoruさんの「JJUG CCC 2018 Fall」に参加してきた。でした。

はじめに

先日の12/15(土)に行われたJJUG CCC 2018 Fallに参加し、登壇させていただきました。
JJUG CCCは日本Javaユーザグループ主催のコミュニティイベントです。
参加者は登録数で1000人超と、ユーザカンファレンスとしては最大級の規模です。
Javaのイベントではありますが、ほとんどJavaに関係ないセッションもあったりでJavaユーザ以外でも十分に楽しめる内容だと思います。

発表について

朝イチで『ふつうのJavaアプリ開発のための自動テスト戦略』というタイトルで発表しました。

ふつうのJavaアプリ開発のための自動テスト戦略 / JJUG CCC 2018 Fall - Speaker Deck



前回のJJUG CCC 2018 SpringではJUnit 5について発表しており、2回連続でテストネタと、特に意識はしてなかったのですが続編的なものになってしまいました。
まだまだ間に合う!JUnit 5入門 / JJUG CCC 2018 Spring - Speaker Deck

ありがたいことに、登壇初心者枠のセッションにも関わらず多くの方にご参加いただき満席御礼となりました。僕に何をそこまで期待していたのでしょうか!?

参加してくださった方々、あとから資料を見てくださった方々には大変感謝感激です。
はてブもたくさんついてるようで、何やら恥ずかしさがこみ上げてきました。

f:id:acro-engineer:20181217003124j:plain
発表の様子

裏話的なこと

テストネタで発表しておきながら、実は僕はあまりテストが好きではありませんw
ただ嫌いなりにテスト(特にユニットテスト)のことに向き合って、いかに楽にできるか、いかに楽しく開発できるかを考えながら発表させていただきました。

CFPでは「実際のコードも交えながらお話します」とか書きましたが、結果コードは1行も出てきていませんww
「話が違うじゃねーか!」と発表中に靴を投げ込まれるのではないかと内心ビクビクしていました。

ノウハウや知識よりも、考え方や自分の想いを伝えるセッションにしたつもりですので、パッション的なものを感じていただければ何よりです。

さいごに

1年前はJJUG CCCに登壇するなんて考えもしなかったのですが、なんと今年は2回もしてしまいました。裏で多くの方に背中を押していただいて、こういう経験をできたのはエンジニアとしてとても貴重なことだと思います。

ちなみにですが、こんな超個人的な内容を会社ブログに上げさせてくれる弊社は自由だなーと思いました。


明日18日目のJava Advent Calendarの担当はnabedgeさんです。
登壇上級者からのJJUG CCCの感想が楽しみです!



Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 

少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

モノリシックなアプリケーションをマイクロサービス化したいエンジニア募集! - Acroquest Technology株式会社のWeb エンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com



社内勉強会でリアクティブについて発表した

こんにちは!こんばんは!Javaエンジニアの しんどー です。腹筋ローラーで今日も腹が痛い!

さて、AcroquestではLTxRTという社内勉強会があり、最近勉強した技術やプロジェクトのフィードバックなどが頻繁に行われています。

私も先日、「Spring WebFluxで学ぶReactive Application」というタイトルで発表しました。

"リアクティブ"とだけ書くと文脈によってイメージが全然違ってしまうのですが、今回はサーバサイドでのリアクティブについて解説しました。

JavaでリアクティブというとRxJavaを思い浮かべる方は多いと思います。国内ではLINEさんがRxJavaをサーバサイドで利用して、APIを非同期化する取り組みを行っていますね。

そして2017年10月にリリースされたSpring Framework 5.0で、Spring WebFluxというリアクティブなWebフレームワークがついにサポートされるようになったのです! (興奮気味なのは単に私がSpring好きなせいですw)

発表ではリアクティブの背景的な部分から入り、リアクティブのメリット/デメリット、ユースケースなどを説明しました。Spring WebFlux自体の説明が薄くなってしまったのはご愛嬌です。

スライドの最後にも書きましたが、リアクティブは正直難しくまだまだ勉強が足りていません・・・
ですが今後のサーバサイド開発で重要性が増すことは間違いないので、くじけずにやっていきます!!!

f:id:acro-engineer:20180802185855j:plain
こんな感じでやってます

Acroquestに興味を持った方はこちら↓↓からご連絡ください!

Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
モノリシックなアプリケーションをマイクロサービス化したいエンジニア募集! - Acroquest Technology株式会社のWeb エンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com

JUnit 4で消耗しているあなたに贈るJUnit 5入門

こんにちは、しんどーです。 気づいたら入社8ヶ月くらい経ってました。

さて、待望のJUnit 5のGA版が今年9月にリリースされました! この記事ではJUnit 5の概要と新機能の一部をご紹介したいと思います。 全部User Guideに書いてあるとか言わない

JUnit 5とは

JUnitとは、言わずと知れたJavaのテスティングフレームワークであり、 デファクトスタンダードの地位にあります。ですが現行のJUnit 4系の最初の メジャーバージョンリリースはすでに10年ほど前であり、保守性の低下が問題に なっていました。

そこでJUnitの刷新を目指すべく、JUnit 5プロジェクトが立ち上げられました。 Junit 5の特徴を簡単に述べると、

といったことが挙げられます。

JUnit 5のAPIは、JUnit 4のAPIとは互換性がありません。 したがって全く新しいフレームワークとして理解したほうがいいかもしれません。

で、JUnit 5って使えるの?

結論から言うと、一から再設計を行ったことによって、 JUnit 4のイマイチな部分が軒並み改善された印象です。

これまで設計上の制約からか、謎のお決まりごと(@Enclosedなクラスはstaticでないといけない等)が 多かったですが、JUnit 5はより自然で直感的なインターフェースデザインになっています。 お決まりコードも減りますので、多少のテストコード量の削減も期待できます。

ただし、すでに述べたようにJUnit 4とは互換性がありませんので、 既存のテストを書き換えるのはおすすめできません。 また新規のプロジェクトで使う場合も、IDE・ビルドツール・フレームワーク等の サポートにはご注意ください。 例えば、後述しますがJUnit 5.0系とmaven-surefire-pluginの2.20系の連携はバグがあり動作しません。

Springユーザであれば、Spring 5からJUnit 5のサポートがあるので、一緒に使い始めるのがいいかもしれません。 (逆にSpring 4でJUnit 5を使うのは大変かと思います。。)

JUnit 5のモジュール構成

JUnit 5は3つのサブプロジェクトから構成されています。

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit Platform

JUnitテストを実行するための基盤を提供します。Maven用、Gradle用のプラグイン等が用意されていますので、 各プロジェクトの環境に合わせたモジュールを選択します。

JUnit Jupiter

テストを記述するAPI(@Test etc...)とテストエンジンを提供します。 基本的に、「JUnit 5」と言った場合はJUnit JupiterのAPIを指すことが多いと思います。

JUnit Vintage

JUnit Platform上でJUnit 3 or 4を動かすためのテストエンジン等を提供します。 この記事では特に触れません。

Getting Started

さて、早速JUnit 5を動かしてみましょう。JUnit 4はたった一つのjarにまとまっていましたが、JUnit 5は複数の モジュールの組み合わせで動作します。(おかげで最初の1時間を無駄にしました。。)

シンプルなMavenプロジェクトを作成して、JUnit 5を動かします。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.github.rshindo</groupId>
  <artifactId>junit5-sample</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>
    <junit.jupiter.version>5.0.2</junit.jupiter.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>${junit.jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>${junit.jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-launcher</artifactId>
      <version>1.0.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.7.0</version>
          <configuration>
            <source>${java.version}</source>
            <target>${java.version}</target>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.19.1</version>
          <dependencies>
            <dependency>
              <groupId>org.junit.platform</groupId>
              <artifactId>junit-platform-surefire-provider</artifactId>
              <version>1.0.2</version>
            </dependency>
          </dependencies>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

実はJUnit 5.0系 は現在 maven-surefire-plugin の最新版(2.20.x)に対応していません。(5.1でバグフィックスが入るようです)
https://github.com/junit-team/junit5/issues/809
ここでは 2.19.1 を利用しています。

pom.xmlを作成したら、次にテストクラスです。

import org.junit.jupiter.api.Test;
public class AppTest {

  @Test
  void testApp() {
    assertTrue(true);
  }
}

テストクラスを書いたら、最後にmvn testでテストを実行しましょう!

基本のテスト

JUnit 5のAPIJUnit 4とは互換性がありませんが、 基本的なアノテーションは パッケージと名前を変えるだけでほぼ同じように動きます。

JUnit4 JUnit5
@Test @Test
@Before @BeforeEach
@BeforeClass @BeforeAll
@After @AfterEach
@AfterClass @AfterAll
@Ignore @Disabled

前処理・後処理

前処理・後処理は @BeforeEach @BeforeAll @AfterEach @AfterAll を使います。

@BeforeAll
static void setUpAll() {
    System.out.println("before all tests");
}

@BeforeEach
void setUp() {
    System.out.println("before each test");
}

@Test
void test1() {
    System.out.println("test 1");
}

@Test
void test2() {
    System.out.println("test 2");
}

@AfterEach
void tearDown() {
    System.out.println("after each test");
}

@AfterAll
static void tearDownAll() {
    System.out.println("after all tests");
}

このときの出力は次のようになります。

before all tests
before each test
test 1
after each test
before each test
test 2
after each test
after all tests

テストケース名

テストの名前は @DisplayName で指定可能です。これまではテストメソッド名を日本語で書く人も多かったと思いますが、 @DisplayName であれば文字種の縛りもなく自由にテスト名を変えられます。

@Test
@DisplayName("1 + 1 = 2になるテスト")
void plusTest() {
    assertEquals(1 + 1, 2);
}

@Test
@DisplayName("😁") //絵文字も可能
void emojiTest() {
}

Eclipseで実行すると以下のように表示されます。

f:id:acro-engineer:20171207003848p:plain

注)mavenでレポート出力したら@DisplayNameが効いていませんでした。。

アサーション

ラムダ式に対応した、便利なアサートAPIが追加されています。 もちろん、assertTrueassertEquals もあります。

アサーションのグループ化

あるオブジェクトのプロパティを全て検証したいとき、これまでは以下のように書いていたと思います。

@Test
public void employeeTest() {
    Employee employee = employeeService.findById(1);

    assertEquals("Aoi", employee.getFirstName());
    assertEquals("Miyamori", employee.getLastName());
}

仮に1つ目のアサーションが失敗したとします。 その時点で例外が投げられ、その次のアサーションは実行されません。 1つ目のアサーションが通るように修正したら、 今度は2つ目のアサーションも失敗していることに気づいてまた修正に戻って・・・という経験をした方も多いと思います。

そんなときに役に立つのが、アサーションのグループ化です。

@Test
void employeeTest() {
    Employee employee = employeeService.findById(1);

    assertAll("employee",
        () -> assertEquals("Aoi", employee.getFirstName()),
        () -> assertEquals("Miyamori", employee.getLastName())
    );
}

assertAll の中にラムダ式で複数のアサーションを渡しています。 このように記述することで、片方のアサーションが失敗しても、 もう片方のアサーションを実行することができるのです!

例外のアサーション

JUnit 4での例外のアサーションはこうでした。

@Test(exception = NumberFormatException.class)
public void exceptionTest() {
    Long.valueOf(null); // throws NumberFormatException
    fail("Exception not thrown");
}

これが assertThrows によってこう変わります。

@Test
void succeedingTest() {
    NumberFormatException ex =
        assertThrows(NumberFormatException.class,
            () -> Long.valueOf(null));
    assertEquals(ex.getMessage(), "null");
}

assertThrows は、例外がthrowされなかった場合には アサーションが失敗となります。@Testには何も書きません。 assertThrows は例外オブジェクトを返しますので、 そこからメッセージの検証などもできます。

また、JUnit 4で例外が発生しなかったケースのために書いていた fail("Exception not thrown") という呪文も必要なくなるのです!

assertThat

様々なアサーションAPIが追加された一方、 assertThatJUnit 5では提供されていません。 JUnit 5の方針として、アサーションライブラリは開発者が好きなもの使ってね、というスタンスのようです。

assertThatを使いたい場合は、Hamcrestなどのサードパーティのライブラリを導入すればOKです!

構造化テスト

JUnit 4の @Enclosed の代わりに @Nested が導入されました。

public class EmployeeServiceTest {
    EmployeeService service;
    @BeforeEach
    void setUp() {
        this.service = new EmployeeService();
    }
    @Test
    @DisplayName("IDで検索する")
    void testFindById() {
        Employee employee = service.findById(1);
        assertAll("employee",
                () -> assertEquals("Midori", employee.getFirstName()),
                () -> assertEquals("Imai", employee.getLastName())
        );
    }

  //非static
    @Nested
    @DisplayName("名前で検索する")
    class FindByFirstNameStartingWithTest {
        @Test
        @DisplayName("firstNameが「E」から始まる")
        void startsWithE() {
      // アウタークラスのフィールドにアクセスできる
            List<Employee> employees = service.findByFirstNameStartingWith("E");
            assertEquals(employees.size(), 1);
            Employee employee = employees.get(0);
            assertAll("employee",
                    () -> assertEquals("Ema", employee.getFirstName()),
                    () -> assertEquals("Yasuhara", employee.getLastName())
            );
        }
    }
}

その違いは何と言っても、インナークラスが非staticになったことです! これによって、アウタークラスのプロパティを参照したり、前処理・後処理を共通化することができます。

まとめ

ここまでJUnit 5の新機能の一部を紹介してきました。 この他にも、パラメータ化テスト、タグ、Extensionなど 注目すべき機能がたくさんあります。 この機会にJUnit 5を是非触ってみてください! (できれば続き書きたいなあ。。)

Acroquest Technologyでは、キャリア採用を行っています。

  • ビッグデータHadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
  • マイクロサービスDevOpsなどの技術を使ったり、データ分析機械学習などのスキルを活かしたい。
  • 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。

  少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。 世の中に誇れるサービスを作りたいエンジニアwanted! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com

#Java本格入門 ついに発売! 日本よ、これが #アクロ本 だ!

最近まで寒い寒いと思っていたら急に暑くなってきましたが、お風邪など召さず健やかにお過ごしでしょうか。
ただ季節の話をしているだけの @ です☺️


さて、
Acroquestの同僚たちと執筆したJava本格入門が、ついに、ついに発売されました!!
gihyo.jp
電子書籍も同日発売で、Amazon Kindle楽天Koboのほか、GihyoのサイトでPDF/EPUB版を購入することもできます。

また、正誤表などを掲載しているサポートサイトも用意しました。
github.com
issueはどなたでも作成いただけるようしてあります。・・・既に柴田先生からのありがたいissueが!😇


またこの本は、入門書の次に読む「2冊目」を目指して書いたものであり、編集者の傳さんも書かれている通り、裏テーマが「35歳からのJava再入門」だったりします。


たとえば、
1〜2年ぐらいJavaを書いてきたので、もう少し次のことを学ぶとか。
Javaの勉強が少し古いバージョンまでで止まってしまっているので、改めて学び直すとか。
概要を知ってはいるけど、知ったかぶりになってしまっているところを補強するとか。
そういうような所で使ってもらえる本にしたい、という想いで執筆を進めました。


・・・なんていう綺麗な話はさておき、裏話を少しだけ。

実は、この本が企画されたのは、Java8がリリースされるよりも前でした。
当時は、これからJava8が出るにもかかわらず、世の中にはまだまだJava7の使い方が広まっていないと感じており、そういう想いから from old Java to modern Java なんてスライドを作って発表したりもしました。
このスライドが編集者である傳さんの目に留まったのが、この本のきっかけでした。

Java8のリリースより前から書き始めて、発売になったのがJava9リリースを数ヶ月後に控えた今というのはもう、多方面の皆様にご迷惑をおかけしました感が凄いわけですが。。。


そんなわけで苦しんで生まれた一冊ですが、もし本屋などで見かけた際には、ぜひ手に取っていただいて、そのままレジに持っていただいて、会計待ちの間にスマホAmazonから配る用にもう一冊買っていただいて、おまけに便利な電子書籍版も購入いただければ、幸いです。


それでは!
See you!

Apache Flink の耐障害性はどんなもの? ~Exactly once の振る舞い~

こんにちは、阪本です。

IoTやらデータ分析やらに関わる機会が増えて、大量のストリームデータに埋もれる(もとい、処理する^^;)ことが増えてきました。
しかも、関わっている業務的に、大概において、データ欠損は許されないという。。

そんなストリームデータを、高速かつ障害に強いアーキテクチャで処理しれくれるのが、Apache Flinkです。
アーキテクチャはこんな感じになっています↓
Apache Flink 1.2.0 Documentation: Distributed Runtime Environment

Flinkの動きをざっくり書くと、

  • JobManagerはジョブを受け付け、1つ以上のTaskManagerに処理の実行を指示する。
  • JobManagerは処理中、各TaskManagerにチェックポイントトリガーを発行する。
  • TaskManagerはチェックポイントトリガーを受信すると、状態のスナップショットを保存し、JobManagerにAckを返す。
  • 障害が発生すると、チェックポイントに戻り、再度実行を行う。

となっていて、このチェックポイントの仕組みが、耐障害性を高めています。

でも、障害に強いと言っても、実際に障害が発生したらどのような挙動になるの?
Exactly onceと書いてあるけど、本当にそうなの?
そんな疑問を解消すべく、こんな条件で試してみました。

f:id:acro-engineer:20170227171700p:plain

2台のKafka Brokerを立て、2台のFlink TaskManagerがConsumerとして並列処理でデータを取得し、Elasticsearchに書き込みます。
動作中に、TaskManagerのうちの1台を強制停止したらどうなるか?をやってみました。

まずは何も気にせずやってみる

Flinkアプリケーションのコードは次のような感じです。文字列をJSONとして解析し、Mapに変換してElasticsearchに流しています。

public class FlinkExecutor {
    public static void main(String... args) {
        new FlinkExecutor().start();
    }

    public void start() {
        // 5秒おきにCheckPointを発行、並行処理数を2とする
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.enableCheckpointing(5000L);
        env.setParallelism(2);

        // Kafkaから受け取った文字列をJSON解析する
        Properties kafkaProps = new Properties();
        kafkaProps.setProperty("bootstrap.servers", "192.168.0.1");
        kafkaProps.setProperty("group.id", "test");
        DataStream<String> stream = env.addSource(new FlinkKafkaConsumer010<>(
                "topic", new SimpleStringSchema(), kafkaProps));
        SingleOutputStreamOperator<Map<String, Object>> outputStream = stream
                .map(FlinkExecutor::parseJson);

        // Elasticsearchに書き込む
        Map<String, String> elasticsearchConfig = new HashMap<>();
        elasticsearchConfig.put("bulk.flush.max.actions", "1");
        elasticsearchConfig.put("cluster.name", "elasticsearch");
        List<InetSocketAddress> transports = Arrays.asList(new InetSocketAddress("192.168.0.4", 9300));
        outputStream.addSink(new ElasticsearchSink<>(elasticsearchConfig, transports,
                new MyElasticsearchSinkFunction<>()));

        env.execute()
    }

    @SuppressWarnings("unchecked")
    private static Map<String, Object> parseJson(String arg) {
        try {
            return mapper.readValue(arg, Map.class);
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
    }

    static class MyElasticsearchSinkFunction<T extends Map<String, Object>> implements ElasticsearchSinkFunction<T> {
        private static final long serialVersionUID = -3596543908572672509L;

        @Override
        public void process(T element, RuntimeContext ctx, RequestIndexer indexer) {
            IndexRequest request = Requests.indexRequest()
                    .index("flink-sample-index").type("sample-type").source(element);
            indexer.add(request);
        }
    }
}

Kafkaに対して20000件のデータを連続して送信中に、2つあるFlink TaskManagerの1つをkillしたところ、TaskManagerと通信できなくなった旨のメッセージがJobManagerのログに出ていました。

2017-03-15 18:56:50,648 WARN  flink-akka.actor.default-dispatcher-3 akka.remote.ReliableDeliverySupervisor - Association with remote system [akka.tcp://flink@flink-taskmanager1:36911] has failed, address is now gated for [5000] ms. Reason: [Association failed with [akka.tcp://flink@flink-taskmanager1:36911]] Caused by: [ホストへの経路がありません

何回か上記のWARNログが出た後、タスクとジョブの状態が「FAILED」となりました。

2017-03-15 18:57:33,615 WARN  flink-akka.actor.default-dispatcher-15 akka.remote.RemoteWatcher                                     - Detected unreachable: [akka.tcp://flink@flink-taskmanager1:36911]
2017-03-15 18:57:33,620 INFO  flink-akka.actor.default-dispatcher-15 org.apache.flink.runtime.jobmanager.JobManager                - Task manager akka.tcp://flink@flink-taskmanager1:36911/user/taskmanager terminated.
2017-03-15 18:57:33,621 INFO  flink-akka.actor.default-dispatcher-15 org.apache.flink.runtime.executiongraph.ExecutionGraph        - Source: Custom Source -> Map -> Filter -> Sink: Unnamed (2/2) (103335fecb10487207ede8049ef5771c) switched from RUNNING to FAILED.java.lang.Exception: TaskManager was lost/killed: ResourceID{resourceId='953329bfa928e188c95f623efe90bd2f'} @ flink-taskmanager1 (dataPort=43427)
...
2017-03-15 18:57:33,626 INFO  flink-akka.actor.default-dispatcher-15 org.apache.flink.runtime.executiongraph.ExecutionGraph        - Job Flink Streaming Job (888c1c6b9e910fddad08615b82bd0782) switched from state RUNNING to FAILING.

ジョブが失敗しても、生きているTaskManagerが残っていれば、自動的にジョブが再開されました(状態がRUNNINGになっています)。

2017-03-15 18:57:33,692 INFO  flink-akka.actor.default-dispatcher-15 org.apache.flink.runtime.executiongraph.ExecutionGraph        - Job Flink Streaming Job (888c1c6b9e910fddad08615b82bd0782) switched from state FAILING to RESTARTING
...
2017-03-15 18:57:43,694 INFO  jobmanager-future-1-thread-1 org.apache.flink.runtime.executiongraph.ExecutionGraph        - Job Flink Streaming Job (888c1c6b9e910fddad08615b82bd0782) switched from state RESTARTING to CREATED.
...
2017-03-15 18:57:43,700 INFO  jobmanager-future-1-thread-1 org.apache.flink.runtime.executiongraph.ExecutionGraph        - Job Flink Streaming Job (888c1c6b9e910fddad08615b82bd0782) switched from state CREATED to RUNNING

ジョブはめでたく再開されました。が、Elasticsearchには、欠損はなかったものの、一部重複するレコードが格納されてしまいました。。
それはある意味自明で、最後のチェックポイント(次の図のチェックポイント2)以降の状態は失われ、再実行されるからです。
f:id:acro-engineer:20170317003026p:plain

Elasticsearchは、トランザクションやスナップショットの仕組みがないため、重複して実行された部分はレコードも重複してしまうことになります。

重複しないようにする

じゃあ、どうすれば重複せずにレコードをElasticsearchに書き込めるでしょうか?
答えは、冪等性を保つような書き込み方にしてあげればOKです。

MyElasticsearchSinkFunctionクラスでElasticsearchに書き込むデータを作っていましたが、
ここにidを指定して、同じデータは同じ一意のidになるようにすることで、重複した(同じidの)レコードがElasticsearchに格納されなくなります。
(厳密には、こっそり見えないところに保存されているようですが。)

@Override
public void process(T element, RuntimeContext ctx, RequestIndexer indexer) {
    // timestampとcategoryの組み合わせで一意となる場合
    String id = element.get("timestamp") + "_" + element.get("category");
    IndexRequest request = Requests.indexRequest()
            .index("flink-sample-index").type("sample-type").id(id).source(element);
    indexer.add(request);
}

今度こそめでたく、重複せずにElasticsearchに格納することができました。

まとめ

Exactly onceといっても、挙動はSink(実はSourceも)の特性に影響されます。
ほとんどのSinkは冪等性を持たず、ゆえにExactly onceではないので、上記のように自分で工夫することが必要です。

とはいいつつ、障害が発生しても途中から自動で勝手にやり直してくれるのは、便利ですね!

今回は単純に各イベントをElasticsearchに保存する処理について調べてみました。
次回は、複数イベント間の関係を考慮して処理するCEP(Complex Event Processing)について調べてみます。

ではでは。

Acroquest Technologyでは、キャリア採用を行っています。

  • ビッグデータHadoop/Spark、NoSQL)、データ分析(Elasticsearch、Python関連)、Web開発(SpringCloud/SpringBoot、AngularJS)といった最新のOSSを利用する開発プロジェクトに関わりたい。
  • マイクロサービスDevOpsなどの技術を使ったり、データ分析機械学習などのスキルを活かしたい。
  • 社会貢献性の高いプロジェクトや、顧客の価値を創造するようなプロジェクトで、提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や、対外的な勉強会の開催・参加を通した技術の発信、社内勉強会での技術情報共有により、エンジニアとして成長したい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。

Elasticsearchを仕事で使いこみたいデータ分析エンジニア募集中! - Acroquest Technology株式会社のエンジニア中途・インターンシップ・契約・委託の求人 - Wantedlywww.wantedly.com

Java Day Tokyoでマイクロサービスとかの話をしてきました #JavaDayTokyo

Hello world, タニモトです!
っていうと、誰か分からない感じになりますが @ です!

なんだか怒濤のようにJavaな日々が過ぎていきました。
5/21(土)のJJUG CCCではイベントスタッフをこなしながら、後輩たちによるElasticsearchハンズオンを見守り、とにかく忙しくしていたら1日が過ぎ去っていきました。CCCの参加者は800人越えですってよ😳

そして、5/24(火)のJava Day Tokyoでは、自分自身の発表と、また英語でインタビューを受けるという貴重な機会をいただき、こちらも準備でバタバタしていたら、あっという間にイベントが終わっていました!😷
Java Day Tokyoは、1300人を越える参加があったそうです!

これらのイベントに参加してくださった皆様、ありがとうございます。
日本のJavaコミュニティが少しでも盛り上がるよう力を尽くしている中で、このような大きなイベントが運営されたこと自体、JJUG幹事の一人として嬉しく思っています。・・・ぐらいの真面目なコメントも、たまにはしないとね😆

NightHackingの英語インタビューで好き放題!

さて、Java Day Tokyoでは、2つ、お話してきました。

一つ目が、NightHackingの英語インタビューです。
NightHackingは、Oracle社のStephen Chinさんが中心になって世界中のJavaコミュニティやJavaイベントを巡り、現地のJavaエンジニアにインタビューをするというものです。今回のJava Day Tokyoのタイミングでも、何組かのインタビューが行われました。

昨年に引き続き、僕もインタビューの機会を得たので、好きなことを好きなだけ話してきました。
www.youtube.com

なんかもうプレビューからしてアレですが、BABYMETALと、格ゲーのマクロと、マイクロサービスについて話しました。
好きなことを好きなだけにも程があるって感じですね!

マイクロサービス開発の追体験をしてもらいたい!

もう一つは、マイクロサービスについてのセッションです。
今回は、マイクロサービスについて、とにかく「僕自身がやったこと」を中心にお話をしました。
speakerdeck.com

マイクロサービスに関する情報って、理論的なものや概念的なもの、あるいは原理主義的な説明や、教科書的な説明が多い印象で、なかなか実際的なところが話されていないように感じています。
また、世の中で謳われているマイクロサービスの構成要素についても、組織にフィットするよう取捨選択やカスタマイズなども行うべきですが、やはりその辺りもあまり情報はありません。

そんな状況でしたので、僕自身が辿ったマイクロサービス開発の経験を、皆さんにも追体験して頂ければと思って話しました。

もし可能だったら、セッション後にマイクロサービスについてのディスカッションなどできれば良かったかも知れませんね。また別の機会に、どこかでやりましょう!

商用障害!?

ところで、セッション中に「商用障害」という言葉を使ったのですが、終わったあとに知人から「あれはどういう意味だったの?」という質問を受けました。どうやらこれ、一般用語ではなかったみたいですね。

商用環境で起きる障害だから、商用障害・・・なんですが、そもそも商用環境という言葉すら、一般用語でない様子。

圧倒的に「本番環境」という言葉が一般的なようです。
商用環境というのは、どうもテレコム系でよく使われる用語のようですね・・・?


なんて、いずれにせよ、何かと学びの多い数日間でした。
次は半年後、JJUG CCC Fallでお待ちしています!

f:id:acro-engineer:20160526011929j:plain:w640

それではまた、
See you!

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、Elasticsearch、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 
データ分析で国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の新卒・インターンシップ - Wantedlywww.wantedly.com