mike-neckのブログ

Java or Groovy or Swift or Golang

Swift Package Manager のメモ

とある理由により、 Swift Package Manager を使っているけど、どこに書いてある方法を真似しても(Swift のドキュメントですら)エラーが出てしまうので、自分用にメモを取ることにした


プロジェクトの作成

プロジェクトを作成するには swift package init コマンドを使う.

今回はアプリケーションを作りたいので、オプションに --type executable を指定する.

$ mkdir Hello-Nio
$ cd Hello-Nio/
$ swift package init --type executable
Creating executable package: Hello-Nio
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/Hello-Nio/main.swift
Creating Tests/
$ tree ./
./
├── Package.swift
├── README.md
├── Sources
│   └── Hello-Nio
│       └── main.swift
└── Tests

3 directories, 3 files

このときに出来上がる main.swift の中身はこんな感じ.

print("Hello, world!")

また、プロジェクトの依存関係を記述する Package.swift は次のようになっている.

// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Hello-Nio",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "Hello-Nio",
            dependencies: []),
    ]
)

ではビルドしてみる

$ swift build
Compile Swift Module 'Hello_Nio' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/Hello-Nio

これでビルドできたので、試しに実行してみる.

$ ./.build/x86_64-apple-macosx10.10/debug/Hello-Nio
Hello, world!

参考URL

swift.org


AppCode に取り込み

コードを追加したいので、AppCode で取り込むために、 xcodeproj を作る

$ swift package generate-xcodeproj
generated: ./Hello-Nio.xcodeproj
$ tree ./
./
├── Hello-Nio.xcodeproj
│   ├── project.pbxproj
│   └── xcshareddata
│       └── xcschemes
│           ├── Hello-Nio-Package.xcscheme
│           └── xcschememanagement.plist
├── Package.swift
├── README.md
├── Sources
│   └── Hello-Nio
│       └── main.swift
└── Tests

6 directories, 6 files

依存ライブラリーを取り込む

依存ライブラリーを取り込んでいく.

Swift-NIO を使いたいので、 Package.swift を以下のように書き換える

// swift-tools-version:4.0
import PackageDescription

let package = Package(
    name: "Hello-Nio",
    products: [
        .executable(name: "Hello-Nio", targets: ["Hello-Nio"])
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-nio.git", from: "1.6.1")
    ],
    targets: [
        .target(name: "Hello-Nio", dependencies: ["NIO"]),
        .testTarget(name: "Hello-Nio-Tests", dependencies: ["Hello-Nio"]),
    ]
)

ポイントは次の4つ

  • // swift-tools-version:4.0 を先頭に書く. これを書かないと version が 3 と認識されてコンパイルできない
  • products: に成果物の記述をする.
    • .executable は実行可能なバイナリーを作る場合に指定する.
    • .library はライブラリーを作る場合に指定する.
    • .executable / .library の中にある targets: は後続する targets:name: から選択して記入する
  • dependencies: には依存するライブラリーのgit レポジトリーのURLとバージョンを記述する.
  • targets: にはビルドのモジュールの単位を記述する(っぽい)
    • プロダクション用には .target を指定し、中にある dependencies: には dependencies: で指定した依存パッケージの products:(.library) の名前を指定する
    • テスト用には .testTarget を使う
    • なお、上記の記述の場合、テスト用の "Hello-Nio-Tests" というモジュール用のディレクトリーがないので、 Tests ディレクトリーの下に作っておく必要がある

AppCode に外部ライブラリーを読み込ませるために、 xcodeproj を作り直す

$ swift package generate-xcodeproj

ここまでを取り込んだ状態の AppCode は次のような感じ

f:id:mike_neck:20180529082459p:plain


適当にプログラムを書く

適当に書いた

import NIO

var buffer: ByteBuffer = ByteBufferAllocator().buffer(capacity: 64)
buffer.write(string: "foo-bar")

let string: String = buffer.readString(length: buffer.readableBytes) ?? ""

print("\(string)")

ビルドする

ビルドは先程と同じ swift build で行う.

$ swift build
warning: error while trying to use pkgConfig flags for swift-nio-zlib-support: couldNotFindConfigFile
Compile CNIOLinux shim.c
Compile CNIOSHA1 c_nio_sha1.c
Compile CNIOAtomics src/c-atomics.c
Compile CNIODarwin shim.c
Compile Swift Module 'NIOPriorityQueue' (2 sources)
Compile Swift Module 'NIOConcurrencyHelpers' (2 sources)
Compile Swift Module 'NIO' (51 sources)
Compile Swift Module 'Hello_Nio' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/Hello-Nio
warning: target 'Hello-Nio-Tests' in package 'Hello-Nio' contains no valid source files

そして実行してみる.

$ ./.build/x86_64-apple-macosx10.10/debug/Hello-Nio
foo-bar

というわけで、一通りビルドまでできました.


TODO

テストについて書く

JJUG CCC 2018 Spring にて発表しました #jjug_ccc

表題の通り 2018/05/26 開催された JJUG CCC にて発表してきました。

www.java-users.jp


セッションが1つとLTが1つです。

セッションの方は 「(俺が)はじめての Netty」 で、LT のタイトルは「Spring Boot アプリケーションの起動を速くする 108 の Tips」です。

資料はこちらです。

  • セッション資料

www.slideshare.net

  • LT 資料

www.slideshare.net


題材についてですが、なぜいまさら Netty を選んだか?ですが、僕はこれまで Netty と聞くと「速いサービスで使われている I/O のライブラリーね、なるほどね(理解していない)速いよね(理解していない)」という感じで、技術者としては思考停止に陥っていたので一発奮起して、かつ勉強がてら発表してみようと思って CFP を書きました。

CFP を書く頃はまだチュートリアルを一通りやって、少しだけ Channel とか ChannelHandlerByteBufjavadocを軽く読んだだけの状態で、このあたりを押さえればよいだろうと言う感じで CFP を書いていました。

ただ、実際にその程度の理解では何も発表資料が書けないことが見えてたので、 Netty In Action を購入して、 Comprehensive な知識を習得することにしました。そして、 2週間程度で Netty In Action は読み終わったのですが、その結果として僕はまだ java.nio.channels(NIO) 周りのAPI および内部実装をちゃんと理解できていないことに気づきました。さらには NIO が解決している課題をちゃんと説明できないのではないかという思いも生じていました(C10K 含めて知識としてはあっても、それを初級者がわかるように説明できないのではないか)。そこでもう少し低レイヤーのシステムコールも含めて勉強しました。そのうえで、OIO/NIO の知識を構築し直すと、Netty が何を解決しようとしているのかがわかるようになってきました。また、 NIO が抱える諸問題(といっても大したあれではない)を Netty はどのように解決するか描き出すのが良さそうなことがわかったので、これが最終的な資料の原型になったと思います。

また Netty の内容の説明を全面に押し出そうとおも思っていたのですが、レベル設定を初心者向けにしていたこともあるので、NIO の仕組みを確実に抑えた方が理解に役立つ(むしろ Netty の説明だけしてたらポカーンされる)と考えたので、半分ほどを NIO の話になっています。本当はもっと応用っぽいこともやってみたかったし、その準備もしてました(WebFlux 〜 reactor-netty のあたりも調べてた)。

以上がセッション資料を書くときに考えてたことです。

発表前に まーや さんが面白いことを言ってくれたので会場の雰囲気が軽くなりました。非常に有難かったです。

発表終了後にいくつか質問をいただきました。

  • サーバーでコネクション(ペイロード長)の設定できるのか?
    • NIO の場合、 ServerSocketChannel#<T>setOption(SocketOption<T>, T) で設定します。 SocketOption<Integer> として StandardSocketOptions#SO_RCVBUF を使います。
    • Netty の場合、 ServerBootstrap#<T>childOption(ChannelOption<T>, T) で設定します。 ChannelOption<Integer> として ChannelOption#SO_RCVBUF を使います。
  • Http で Multipart で大きなファイルを受け取る場合の実装方法
    • netty example の http あたりが参考になります -> github.com
  • Netty の http モジュールでサーブレット相当のことができるか?
    • 残念ながら、そこまではできないので、別途 reactor-netty あるいは Spring WebFlux などの上位のフレームワークを使うのがよいです

LT 発表ですが、発表待ちの最中に セバスチャン が突然僕の前でLTすることになって、若干プレッシャーがかかりました。しかし、彼のおかげで LT を聞こうとする人が増えたのでラッキーな発表順だと思いもしました。

LT の方は以前 jsug で発表した内容の焼き直しです。jsug は Spring の勉強ということもあるので、技術的にどうしてこれをやると速くなるのかという説明を入れていましたが、CCC の LT ではそのあたりは省きました。懇親会なのでお酒入っていて理解できないだろうし、ここ数回ほどJJUG CCCでLTをして得ている印象では、そのような説明よりもっとわかりやすい部分を長く強く説明したほうが笑いを取れるだろうと考えたからです。なお、結果的に笑いがとれたので、僕的には満足です。

ちなみに、このようなお言葉をいただきました。

@SpringBootApplication になった人」、いい表現ですね。 じゅくちょー ばりにそれで売っていこうかな…でも、 「俺自身が @SpringBootApplication になる」の Spring Boot アプリケーションを書くのが非常に面倒なのでどうしようかな…


その他

  • 1本しか持っていないコネクターを会社で固定で使っている状態なので ゆうすけ さんと 速水 さんにコネクターをお借りしました。大変助かりました。
  • 某お嬢さんにお会いしました。ここ1年とちょっとほどツイートしていなかったので、某有名企業やめて、Javaもやめて、何かすごいよい条件の会社で働いているのかな?ひょっとしたらドラム缶にコンクリートで埋められて東京湾に投げ込まれてないかなとか心配しましたが、特に変わらず活躍されているご様子で安心しました。
  • 職業が自身のお名前でもある 戸倉彩 さんにご挨拶できました。

Java Day Tokyo 2018 のセッションメモ

Java Day Tokyo 2018 に行ってきたので、そのメモ。


キーノート

目黒駅で東京駅南口行きバスのりばを探すのに時間がかかってしまったため、20分遅刻してしまった。

僕が参加したときには Java の新しいリリースモデルの説明。

OpenJDK は 半年間しかサポートしないので、LTSが欲しい場合は Oracle または他のベンダーに頼る感じと思われる。


Updates from the JCP Program

トラブルで最初の10分くらい何を言っているかわからなかった(通訳レシーバーが故障してた)。

JCP/JSR がどういったもので、 Java に貢献するにはどうしていくかという話だった


Project Vallhala

Value Type というプリミティブをフィールドとするデータ型を定義するようにして、その配列が効率的になるようにメモリの持ち方を工夫するというプロジェクト。

これをさらに進めると、ジェネリクスにプリミティブ型を指定できるようになり、 Stream<Integer> とか IntStream ではなく、 Stream<int> のように扱えるようになる。ただ、型の変位については問題があるようで、 int が継承しているクラスは一体何なのかなどの整理が必要である様子。

また、 Unsafe を置き換えるもので VarHandles があるが、それらの概要を示した上で、 JJUG CCC でそのセッションをやるとのことだった。


Curing you Domain Model Anemia with Effective & Clean Tips from the Real World

どういったモデルを書いていけば、集約度の高いクラスが書けるか的な話。DDD好きにはたまらないやつ。

JPAのエンティティを例にサンプルを書いていってたが、僕が普段意識して書いているコードに近いものになってた。

  • コンストラクターではなく、スタティックファクトリーメソッドを提供する
  • エンティティクラスにはデフォルトコンストラクターだけではなく、フィールドに値を突っ込めるコンストラクターも提供する(そのコンストラクターはファクトリーメソッドからのみアクセスする)
  • エンティティクラスの中で特定の値域をもつようなフィールドは、プリミティブな値をそのまま持つのではなく、 Embeddedable クラスを作ってそちらで検証する
  • コレクションフィールドのゲッターは不変(Collections.unmodifiableList() など使って)にしたコレクションを返す。値の追加はこのクラスの中でのみおこなう

Java SE10/11 移行ガイド

すごい混んでたセッション。引っ込み思案なため、席がとれなくて、床に座ってセッションを聞いてた。

雑なまとめ

  • @Deprecated になっている API は使うな。@Deprecated(forRemoval = true) となっているものはまじでなくなるので使うな
  • --add-exports + ALL-UNNAMED/--add-opens などコンパイルオプションが多い
    • モジュール周りはドキュメント自分で読まないと詳しくなれなそう
  • 一つのパッケージを複数のモジュールに組み込めないので、 現在 Java SE にある JAXB/JAX-WSは取り除かれる。使いたい場合は Java EE から提供されているものを使うこと
    • ただ、 javax.activationmaven central にもないので、自分でパッケージングが必要になりそう…
  • ライブラリーを作っている人は META-INF.MFAutomatic-Module をつけてくれ。でないと、jar ファイルの名前がモジュール(Unnamed-Module)になるのでバージョンが変わるたびにビルドが壊れる

たぶん、このメモ読んでも移行できないので、資料を見たほうがよいかもしれない


Project Loom

軽量プロセス Fiber についてのセッション。今回もっとも面白かったセッション。

  • FiberJava の runtime やユーザーコードに支配されるスレッドで、 Thread よりもコストが低く、 OS のスレッドの 1000倍の個数を扱える
  • サーバーなどのリソースの活用の仕方をモニターすると、 セッションの生存期間が長くて数も増えるが、殆どの時間が I/O 待ちでリソースが有効活用されていない
  • 簡単だが性能がよくない同期モデルのプログラミングにするか、複雑になるが高性能の非同期プログラミングモデルにするかの選択しかなかったが、 Fiber を用いると高性能で簡単なモデルのプログラミングが出来るようになる
  • Fiber をこれまでの Thread の上に作るか、 Thread の parent インターフェースとして Strand を作り、 Fiber インターフェースを Strand を継承したものにするか、どちらかがAPI の候補となっている
  • Fiber を使うと、JAX-RS のコードのスループットが5〜10倍になる
  • もしすでに非同期で書いていたら…それを同期に戻すような修正が必要になるかもしれない
  • Fiber を使うと、golang のチャネルや Python のジェネレーターのようなものが作れるようになる
  • Fiberシリアライズして別のJVMに送ってそちらで計算させることができる(coherence のようなデータがある所で処理させるような用途が可能/超長時間のトランザクションなども実現できる)
  • 課題: Fiber に対応させるために既存の java.netjava.nio 周りを書き直さないといけない

あまり興奮を伝えられていないが、本当に Fiber 最強やんけという感想しかなかった

CompletableFuture の thenApplyAsync メソッドは何度も呼び出せるのか試してみた

最近、自分専用のライブラリーを書いていて、 CompletableFuture#thenApply および CompletableFuture#thenApplyAsync などを複数回呼び出せるのかがわかっていなかったし、 Javadoc にも書いていないようなので実験してみることにしました。


書いたのは次のようなコードです。

@Test
void handlers2() throws InterruptedException {
  final CountDownLatch latch = new CountDownLatch(1);
  final CompletableFuture<String> future =
      CompletableFuture.supplyAsync(
          () -> {
            sleep();
            // ランダムで結果を返す
            // 成功の場合は foo を返す
            // 失敗の場合はRuntimeExceptionが発生
            return Result.random().apply("foo");
          },
          executor);

  // CompletableFuture#thenApplyAsync を2回呼び出す
  final CompletableFuture<String> f1 =
      future.thenApplyAsync(str -> String.format("result -> %s", str));
  final CompletableFuture<String> f2 =
      future.thenApplyAsync(
          str -> {
            throw new RuntimeException(str);
          });

  // 結果を文字列で表示するためのハンドラー
  final CompletableFuture<String> hf1 =
      f1.whenCompleteAsync(
          (str, th) ->
              handle(str, th)
                  .onSuccess(s -> System.out.println(String.format("handler 1 -> %s", s)))
                  .onError(
                      e ->
                          System.out.println(
                              String.format(
                                  "handler 1 error -> %s", e.getClass().getSimpleName()))),
          executor);
  final CompletableFuture<String> hf2 =
      f2.whenCompleteAsync(
          (str, th) ->
              handle(str, th)
                  .onSuccess(s -> System.out.println(String.format("handler 2 -> %s", s)))
                  .onError(
                      e ->
                          System.out.println(
                              String.format(
                                  "handler 2 error -> %s", e.getClass().getSimpleName()))),
          executor);
  CompletableFuture.allOf(hf1, hf2).whenComplete((s, t) -> latch.countDown());
  latch.await();
}

結果は次のようになりました

handler 1 -> result -> foo
handler 2 error -> CompletionException

というわけで、同じ CompletableFutureインスタンスから複数の CompletableFuture を派生させることが可能とわかった


すると次に気になるのは、 CompletableFuture の結果が返ってきてから、 thenApplyAsync を呼び出せるのかという問題

先ほどのメソッドを次のように書き足してみた

@Test
void handlers2() throws InterruptedException {
  // CountDownLatch を追加
  final CountDownLatch finalLatch = new CountDownLatch(1);

  // ... 先ほどのメソッド

  latch.await(); // 先ほどのメソッドの最後の行

  final CompletableFuture<String> f3 =
      future.thenApplyAsync(str -> String.format("after completed: %s", str));
  final CompletableFuture<String> hf3 =
      f3.whenCompleteAsync(
          (str, th) ->
              handle(str, th)
                  .onSuccess(s -> System.out.println(String.format("handler 3 -> %s", s)))
                  .onError(
                      e ->
                          System.out.println(
                              String.format(
                                  "handler 3 error -> %s", e.getClass().getSimpleName()))),
          executor);

  hf3.whenCompleteAsync((s,t) -> finalLatch.countDown());
  finalLatch.await();
}

実行結果はこちら

handler 2 error -> CompletionException
handler 1 -> result -> foo - foo
handler 3 -> after completed: foo - foo

で、 complete した状態の CompletableFuture に対して、関数を適用することも可能だということがわかった。