Viewing entries in
技術

JSON vs MessagePack vs Protocol Buffers

JSON vs MessagePack vs Protocol Buffers

実業務で大きなサイズのデータをブラウザと交換する機会があったのですが、その際のデコード処理の負荷が課題となっており、効率的にエンコード・デコードできるデータフォーマットを探していました。候補としてProtocol BuffersとMessagePackが挙がり、詳しくパフォーマンスを知るために今回検証を行いました。この記事ではその調査結果をご紹介します。

主にブラウザでデータを受け取るという観点から デコードが早いかどうか を中心に測定を行いました。

なお、計測に使った環境は次のとおりです。

  • MacBook Pro 14インチ、2021 (Apple M1 Pro) / 32GB
  • Node.js 20.5.0 (V8 11.3.244.8-node.10)
Passmark PerformanceTest 11.0.1000

CPU Mark                      22,049
Integer Math                  46,634 MOps/Sec
Floating Point Math           65,615 MOps/Sec
Prime Numbers                    281 Million Primes/Sec
Sorting                       33,605 Thousand Strings/Sec
Encryption                    12,497 MB/Sec
Compression                  280,803 KB/Sec
CPU Single Thread              3,902 MOps/Sec
Physics                        2,957 Frames/Sec
Extended Instructions (NEON)  11,078 Million Matrices/Sec

Memory Mark                     3,927
Database Operations	            9,486 KOps/Sec
Memory Read Cached             24,395 MB/s
Memory Read Uncached           22,894 MB/s
Memory Write                   24,052 MB/s
Latency                            26 ns
Memory Threaded               118,100 MB/s

MessagePack

MessagePackはJSONと同じスキーマレスなデータ構造です。

MessagePackのためのJavaScriptライブラリとしては、次が人気のようです。

インターネット上でも少し探すとベンチマークが見つかります。この結果を見ると、JSONの方が10倍ほど高速のようです。

バイナリフォーマットと比べてJSONのパフォーマンスがこれほど高いのは意外でした。その理由は@msgpack/msgpackのREADMEのベンチマークで説明されていました。

Run-time performance is not the only reason to use MessagePack, but it's important to choose MessagePack libraries, so a benchmark suite is provided to monitor the performance of this library.

V8's built-in JSON has been improved for years, esp. JSON.parse() is significantly improved in V8/7.6, it is the fastest deserializer as of 2019, as the benchmark result bellow suggests.

(和訳) MessagePackを使う理由は実行時のパフォーマンスだけではありません。それでもどのMessagePackのライブラリを選ぶのかは重要です。そのため、このライブラリのパフォーマンスを観察するためのベンチマークスイートを用意してあります。 V8に組み込まれたJSONライブラリは何年も掛けて改善されており、特にJSON.parse()はV8/7.6で目を見張る改善がなされました。下のベンチマーク結果が示唆するように、2019時点では最も早いデシリアライザです。

ChromeのJavaScript実行エンジンのV8はC++で書かれており、パフォーマンスが高いのは納得できます。

とはいえ、2019年からもう4年が経過しており、多少状況が変わっている可能性はあります。

ベンチマーク

そこで現時点の実際のパフォーマンスを知るために、@msgpack/msgpackのリポジトリ内のベンチマークを実行してみます。

比較しやすいように出力結果は整形してあります。また、比較のために一部のテストケースにJSONを追加しています。

npx node -r ts-node/register benchmark/benchmark-from-msgpack-lite.ts

Benchmark on NodeJS/v20.5.0 (V8/11.3)

operation                                         |   op    |   ms  |  op/s
------------------------------------------------- | ------: | ----: | ------:
(エンコード)
buf = Buffer.from(JSON.stringify(obj));           | 1084100 |  5000 |  216820
buf = require("msgpack-lite").encode(obj);        | 1001900 |  5000 |  200380
buf = require("@msgpack/msgpack").encode(obj);    | 1260600 |  5000 |  252120
buf = /* @msgpack/msgpack */ encoder.encode(obj); | 1311300 |  5000 |  262260
buf = require("msgpackr").pack(obj);              | 2915100 |  5000 |  583020
(デコード)
obj = JSON.parse(buf.toString("utf-8"));          | 1737500 |  5000 |  347500
obj = require("msgpack-lite").decode(buf);        |  575700 |  5000 |  115140
obj = require("@msgpack/msgpack").decode(buf);    | 1536000 |  5000 |  307200
obj = /* @msgpack/msgpack */ decoder.decode(buf); | 1546600 |  5000 |  309320
obj = require("msgpackr").unpack(buf);            | 1424700 |  5000 |  284940

node benchmark/msgpack-benchmark.js

./sample-large.json: (7598 bytes in JSON)

(encode) json             x 82,388 ops/sec ±0.54% (96 runs sampled)
(encode) @msgpack/msgpack x 31,050 ops/sec ±0.16% (100 runs sampled)
(encode) msgpack-lite     x 29,034 ops/sec ±0.76% (96 runs sampled)
(encode) notepack.io      x 28,766 ops/sec ±0.13% (98 runs sampled)
(encode) msgpackr         x 66,822 ops/sec ±0.65% (97 runs sampled)
(decode) json             x 80,895 ops/sec ±0.73% (98 runs sampled)
(decode) @msgpack/msgpack x 32,898 ops/sec ±0.12% (98 runs sampled)
(decode) msgpack-lite     x 15,715 ops/sec ±0.27% (98 runs sampled)
(decode) notepack.io      x 21,699 ops/sec ±0.90% (100 runs sampled)
(decode) msgpackr         x 67,720 ops/sec ±0.21% (99 runs sampled)

結果を見ると、デコーディングに関してはJSONが依然として早いことがわかります。デコーディングにおけるパフォーマンスが低い理由は、JSONと同じくスキーマレスで構造がわからないというのが大きな理由だと考えられます。

ライブラリの中でも msgpackrは早いことがわかります。MeasureThat.netにはmsgpackrの異なるテストケースが載っています。

数値や真理値等を中心としたデータの多次元配列をデコードする MsgPack Numbers Decode というテストケースではJSONと比べて2.6倍程度早いようです。データの読み取りが単純な数値であれば、決まったデータを読み取って変換(ビット演算等)するだけであり、早いのは納得できます。

一方、文字列のデコードを中心とした MsgPack String Decode はJSONに比べて遅いという結果が出ています。MessagePackでは、ヌル終端文字列ではなくPascal文字列を採用しています。つまり、文字列長を最初のバイトに含めるようになっています。これにより、読み取り速度を大幅に向上させられます。しかし、それでも遅いという結果が出ているということは、V8のC++で実装されたJSON.parse()の方がJavaScriptで書かれたmsgpackrよりも早いのだろうと推測しています。

文字列データが支配的でない単純な配列であれば、MessagePackの方が早いのではないかと考えられます。実際にそのようなデータを試してみたところ、JSONよりも1.5倍程度早いという結果が得られました。とはいえ、配列の何番目にデータが入っているという知識を事前に共有しておく必要があり、データ構造の変更に注意を払う必要が出てきます。実用性を考えると好ましいとはいえないでしょう。なお、ここでは Chrome/115.0.5790.114 (V8 11.5.150.16) を計測に利用しました。

エンコードしたデータ

[
  ["hello", 123, true],
  ["world", 456, false],
  ["java", 541, true],
  ["javascript", 231, false],
  ["c", 852, true],
  ["cpp", 142, false],
  ["basic", 431, true],
  ["python", 591, false],
  ["hello", 123, true],
  ["world", 456, false],
  ["java", 541, true],
  ["javascript", 231, false],
  ["c", 852, true],
  ["cpp", 142, false],
  ["basic", 431, true],
  ["python", 591, false],
]
operation              |   op
---------------------- | ------:
JSON Numbers Decode    |   968,421 ops/sec ±0.83% (68 runs sampled)
JSON String Decode     | 1,104,092 ops/sec ±0.54% (69 runs sampled)
JSON Simple Decode     |   923,841 ops/sec ±0.22% (69 runs sampled)
MsgPack Numbers Decode | 1,593,083 ops/sec ±0.17% (69 runs sampled)
MsgPack String Decode  |   994,357 ops/sec ±1.79% (65 runs sampled)
MsgPack Simple Decode  | 1,366,154 ops/sec ±0.13% (69 runs sampled)

MessagePack自体はスキーマレスなデータ形式ですが、亜種のmsgpackrではスキーマを定義するための仕組みもあるようです。msgpackrに掲載されているベンチマークを実行してみました。

operation                                                  |    op    |   ms  |   op/s  |    size   |
---------------------------------------------------------- | -------: | ----: | ------: | --------: |
(エンコード)
msgpackr w/ shared structures: packr.pack(obj);            | 15845200 |  5000 | 3169040 |           |
msgpackr w/ random access structures: packr.pack(obj);     | 16235200 |  5000 | 3247040 |           |
require("msgpackr").pack(obj);                             | 10741900 |  5000 | 2148380 |           |
bundled strings packr.pack(obj);                           |  9101900 |  5000 | 1820380 |           |
buf = Buffer(JSON.stringify(obj));                         |  6359300 |  5000 | 1271860 |           |
(デコード)
msgpackr w/ shared structures: packr.unpack(buf);          | 34135500 |  5000 | 6827100 |        63 |
msgpackr w/ random access structures: packr.unpack(buf);   | 21832500 |  5000 | 4366500 |        58 |
require("msgpackr").unpack(buf);                           |  6387800 |  5000 | 1277560 |       143 |
bundled strings packr.unpack(buf);                         | 15376400 |  5000 | 3075280 |        74 |
obj = JSON.parse(buf);                                     |  7323000 |  5000 | 1464600 |       180 |

require("msgpackr").unpack(buf) とJSONでは、JSONのほうが早いという結果が出ています。一方、スキーマを定義したmsgpackr w/ shared structuresでは、4.7倍という非常に早い結果が出ています。

上記のベンチマーク結果を見ると、JSONのデコードとMessagePackのデコードのパフォーマンスに大きな差がないため、JSONと比べてもおよそ4倍程度の差だと推測できます。これは、後述するスキーマベースのProtocol BuffersがJSONと比べて4倍程度早いこととも一致します。

Protocol Buffers

Protocol Buffersは、スキーマに基づくデータフォーマットです。スキーマはデータ構造の定義のことで、これを事前に定義して送信者と受信者で共有し、エンコードとデコード時に用います。JSONやMessagePackでは、実際にデータを読み取らなければ構造がわかりませんでしたが、Protocol Buffersでは事前に分かっているため、高いパフォーマンスが期待できます。

Protocol BuffersのためのJavaScriptライブラリとしては、protobuf.jsgoogle-protobufが人気のようです。

protobuf.jsのREADMEに載っているベンチマーク結果は、i7-2600KとNode.js 6.9 (2016年)のもので結果が古くなっています。

ベンチマーク

protobuf.jsのリポジトリ内のベンチマークを実行してみます。

ただし、以下の点で変更があります:

  • 静的コードの再生成
    • pbjs --target static data/bench.proto > data/static_pbjs.js
  • google-protobufの更新
    • 3.11.3 -> 3.21.2
> protobufjs@7.2.4 bench
> node bench

benchmarking encoding performance ...

protobuf.js (reflect) x 1,544,621 ops/sec ±0.21% (96 runs sampled)
protobuf.js (static)  x 1,580,495 ops/sec ±0.17% (97 runs sampled)
JSON (string)         x 1,165,063 ops/sec ±0.18% (95 runs sampled)
JSON (buffer)         x   872,308 ops/sec ±0.19% (97 runs sampled)
google-protobuf       x 1,015,041 ops/sec ±0.16% (99 runs sampled)

   protobuf.js (static) was fastest
  protobuf.js (reflect) was  2.3% ops/sec slower (factor 1.0)
          JSON (string) was 26.3% ops/sec slower (factor 1.4)
        google-protobuf was 35.8% ops/sec slower (factor 1.6)
          JSON (buffer) was 44.8% ops/sec slower (factor 1.8)

benchmarking decoding performance ...

protobuf.js (reflect) x 3,936,820 ops/sec ±0.14% (99 runs sampled)
protobuf.js (static)  x 4,269,989 ops/sec ±0.20% (97 runs sampled)
JSON (string)         x   949,356 ops/sec ±0.14% (99 runs sampled)
JSON (buffer)         x   868,264 ops/sec ±0.15% (100 runs sampled)
google-protobuf       x   909,006 ops/sec ±0.18% (95 runs sampled)

   protobuf.js (static) was fastest
  protobuf.js (reflect) was  7.7% ops/sec slower (factor 1.1)
          JSON (string) was 77.8% ops/sec slower (factor 4.5)
        google-protobuf was 78.7% ops/sec slower (factor 4.7)
          JSON (buffer) was 79.7% ops/sec slower (factor 4.9)

benchmarking combined performance ...

protobuf.js (reflect) x 1,057,638 ops/sec ±0.14% (96 runs sampled)
protobuf.js (static)  x 1,106,991 ops/sec ±0.19% (98 runs sampled)
JSON (string)         x   491,279 ops/sec ±0.16% (99 runs sampled)
JSON (buffer)         x   420,535 ops/sec ±0.21% (99 runs sampled)
google-protobuf       x   462,041 ops/sec ±0.31% (95 runs sampled)

   protobuf.js (static) was fastest
  protobuf.js (reflect) was  4.4% ops/sec slower (factor 1.0)
          JSON (string) was 55.6% ops/sec slower (factor 2.3)
        google-protobuf was 58.3% ops/sec slower (factor 2.4)
          JSON (buffer) was 62.0% ops/sec slower (factor 2.6)

公式サイトに掲載されているデータは、Node.js 6.9.1(V8 5.1系)であり、JSON.parse()の改善前のものでした。 今回のNode.js v20によるデコードの結果を見ると、差はやや縮まっているように見えますが、それでも4倍程度はprotobuf.jsのほうが早いことがわかります。

Protocol Buffersがなぜこんなにも早いのかを考察してみます。MessagePackでは文字列のデコードが遅いことが分かっていましたが、Protocol Buffersでも文字列の表現(Pascal文字列)には違いがありません。そのため、(検証は行っていないのですが)Protocol Buffersでも文字列が多いデータのデコードは早くないだろうと予想できます。MessagePackでオブジェクト(Map型=JSONのオブジェクトのような連想配列)を表現したい場合、フィールド名を文字列として表現しなければなりません。一方、Protocol Buffersではスキーマという形でフィールド名が事前に送信側と受信側で共有されており、フィールド名の代わりにフィールドのID(整数値)を使ってエンコードが行われます。Protocol Buffersは文字列のエンコードをしなくてよい分、早いのだろうと思います。他の要因としてデコード処理の効率化の程度の違いやデコード時の型チェックの省略による影響も考えられます。

まとめ

Protocol Buffers(特にprotobuf.js)は非常に早く、 JSONと比べて4倍以上高速 にデコードできることがわかりました。

MessagePackはJSONより遅くなる場合がありました。特に文字列を中心としたデータではパフォーマンスが出ないようです。一方、数値を中心とした単純な配列のようなデータであれば高いパフォーマンスを示しました。また、事前にスキーマを用意しておける亜種のmsgpackrを使えばProtocol Buffersに近い性能を発揮できるようです。

スキーマベースのデータフォーマットが高いパフォーマンスを発揮できる理由は、構造の情報を事前に共有してあることで、プロパティ名・フィールド名(JSONのキーに相当)のような構造の情報を実行時に読み取る必要がないためだろうと思われます。

ただ、桁が変わるほど、つまり10倍、100倍、1000倍といった大きな差があるわけではありませんでした。数KBのデータのデコードにかかる時間は数マイクロ秒程度であり、1000倍のデータ(数MB)がやってきたとしても単純計算で数ミリ秒しか掛かりません。ブラウザ上で更に大きなデータを大量に扱うという場合であれば役立つ可能性はありますが、パフォーマンスだけを考慮するならば、多くのケースでJSONでも十分だと考えられます。

一方、大量のリクエストやデータに応えなければならないサーバ側では、大量のデコード処理を行う必要が出てくるでしょう。サーバ側で使われるCやJavaのようなコンパイル型の言語であればバイナリフォーマットされたデータのデコードにおいてJavaScriptの実装よりも高いパフォーマンスを発揮できると予想されます。デコード処理を効率化できれば、その他のことにCPU時間を利用できるため、パフォーマンスは僅かながら向上するのではないかと期待できます。大規模システムではその小さなパフォーマンスの差が費用に大きな違いを生むかもしれません。

パフォーマンス以外にもスキーマベースのプロトコルには様々なメリットがあることを忘れてはなりません。スキーマによって送受両端でフォーマットの食い違いを予防すること、データフォーマットを安全に更新できること、型安全なプログラミングを実現できること、gRPC等のフレームワークに則った開発ができることなどといった利点があります。そういった他の効果も踏まえて検討をする必要があります。

今回は調べられなかったProtocol Buffersにおける文字列のデコード速度は、いつか測定できればなと考えています。また、今回はライブラリのテストケースをそのまま実行しましたが、条件の異なる様々なテストケースを作成して比較してみたいと考えています。

今回の結果はNode.jsで測定したもので、ブラウザによってはベンチマークの結果が大きく変わります。今回は記載していませんが、Safari(JavaScriptCore)や Firefox(SpiderMonkey)では JSON.parse() のパフォーマンスが異なります(その場合でもフォーマット間のパフォーマンスの差という意味ではあまり変わりません)。

かとじゅんさんを招いて「Big Picture Workshop」を開催しました

かとじゅんさんを招いて「Big Picture Workshop」を開催しました

かとじゅんさんをお招きし、2回にわたってEventStormingの一種である Big Picture Workshop を開催しました。

EventStormingは、近年注目されている、情報システムの概観を掴むための手法の一つです。ThoughtWorks社のTechnology Rader Vol.19では、当社の考える産業で採用していくべきテクニックの一つとして取り上げられました。

EventStormingでは、関係者が集まりシステムを取り巻く環境を大きな壁と付箋を使って互いに発表しあいながら、システム化の対象とシステムへの期待を共有していきます。

EventStormingの特徴は、システムを利用するにあたって発生するイベント(ドメインイベント)に着目している点です。イベントから始まり、それを時系列順に並べ、関連する利用者や外部システム、金銭の発生を洗い出していくことで、システムの全体像を掴むことができます。

今回は「かつてない図書館」を企画するというテーマで、10人程度の参加者が集まって、アイデアを出し合いました。

進めながら気づいたのは、議論を促進するような仕組みが随所に取り入れられていることです。 最初は発散させ後で収束させるようにしたり、全員が立った状態で全員と情報を共有しながら認識合わせをして、座って議論しないようにしたりと意見を言いやすい環境を整え議論の価値を高める効果を感じました。

event-stroming-1.jpg

タイムライン強化 〜 ウォークスルー

最初は参加者が各自の思い思いのイベントを付箋に書いて、タイムライン(時系列順)に並べていきます。 イベントは、動詞の過去形で書くように決められていて、動詞の過去形で書くことで、イベントでないものを出さないように誘導したり、後の工程で「□□した」のが誰なのかに視点を誘導する効果があります。

イベントが出揃ってきたら、タイムライン強化を行います。タイムライン強化では、システムを利用する上で必要不可欠で価値の高い、重要なイベントにフォーカスしてその前後のイベントや関係のあるアクター、外部システムなどを充実させていきます。

次のウォークスルーでは、イベントの流れを言語化しながら、考慮不足や抜け漏れを発見していきます。まずこのイベントが起こって、次はこのイベントが起こる、というように、流れに沿って説明することで、あのイベントが抜けているのではないかということに気づきやすくなるそうです。

実際にやってみると、タイムライン強化の最中は気にしていなかったイベント同士の前後関係の違和感や足りていないアクターが明確になり、タイムラインを充実させることができました。

こうした考慮不足に早い段階で気づけるのは、EventStormingの大きな利点だと感じました。

event-storming-2.jpg

逆方向ナラティブ 〜 クロージング

ウォークスルーで拡充したタイムラインについて今度は逆方向からそのイベントを起こし得るのに、その前のイベントは適切かという視点を持ちながら言語化しました。

逆方向から読んでいくとそのイベントを起こすためにはその前のイベントは重要だがそれだけでは足りないといった、依存の不足が見つかりました。

また、エンジニアはお金の話が抜けがちということであえてお金について考えるフェーズが用意されていて、実際この辺りの話はいくら素晴らしいビジネスモデルやアイデアがあったとしても無い袖は振れないので、ステークホルダーが一同に介して認識合わせができるタイミングで一緒にお金について考えるフェーズを設けるのは合理的に感じました。

一通り終わったところで今回のワークショップの中でリスクになりそうな問題やその解決策を洗い出し共有しました。

IMG_0092.jpg

終わりに

ステークホルダーが一堂に会して曖昧な部分を明らかにし合意を形成するため、お客さんやチーム内での認識合わせなど、実際のプロジェクトで有用なテクニックだと感じました。

関係者と顔を合わせながら合意を得るという点で、同様に情報システムに対する期待を取りまとめる技法である、リレーションシップ駆動要件分析(RDRA)と似ている点がありそうです。それぞれの違いを見定めて、各プロジェクトに適した手法を取り入れられるよう理解を深め活用していきたいです。

今回のワークショップで体験した Big Picture EventStorming は、プロジェクトを立ち上げる際に使うものでしたが、他にもプロジェクトの別の場面で使える手法があるそうです。 中でも、設計者や実装者の観点に寄った Design Level EventStorming という技法があるそうで興味を惹かれました。

最後になりましたが、かとじゅんさんに感謝の意を申し上げます。

この度は貴重な体験の機会を作ってくださり、ありがとうございました。

リチャード 伊真岡さんをお招きして社内勉強会を開催しました

リチャード 伊真岡さんをお招きして社内勉強会を開催しました

リチャード 伊真岡さんをお招きし、「OSSコントリビューションの始め方」「Akkaについて」という2テーマで社内勉強会を開催していただきました。

資料はこちら

Scala関連のカンファレンスやbuildersconなどで登壇されているリチャードさんにお越しいただいて話が聞ける大変ありがたい機会でした。

前半:OSSコントリビューションの始め方

前半は「OSSコントリビューションの始め方」というタイトルで、OSSへのコントリビューションの貢献のモチベーション・仕方・程度などは多様で、まずは「気軽に始めてみましょう」というお話でした。

普段お世話になっているOSSに恩返しの気持ちも込めて、簡単なところから始めてみようと考える機会になりました。

後半:Akkaについて

Akkaの歴史から始まり、akka-cluster, akka-persistenceの解説や、 弊社のバックグラウンドに合わせてAkka + SpringBootという切り口で、よく採用しているSpringBootを軸にした視点で比較・解説くださり、盛り沢山な内容でした。

中でも私が印象に残っているお話がAkkaで構築するクラスターとKubernetes等のコンテナオーケストレーションを使ったマイクロサービスという視点でのクラスターの関心の違いについてでした。

IMG_9942.jpeg

図のようにAkkaが意識しているのは1つのアプリケーションとしてのスケールで、Kubernetes等のコンテナオーケストレーションツールではマイクロサービスのようなサービス群としてのクラスターを意識しているということでした。

Akkaクラスターの得意/不得意について実際の利用シーンを交えて説明してくださり、使い分けのイメージが湧きました。

最後に

発表を通じてOSSコントリビューションやAkkaについて理解を深めることが出来ました。

今回は座学が中心でしたが、発表の中でAkka Clusteringのハンズオンを行いたいというお話もありましたのでそのような手を動かす機会や、社内勉強会を通じてAkkaへの理解をより深めていきたいです。

リチャード 伊真岡さん、素晴らしい発表をありがとうございました。

近日リチャード 伊真岡さん主催のEventStormingワークショップが開催されますのでご興味のある方は、是非参加してみてはいかがでしょうか。

ドメインモデリングワークショップ第2弾

ドメインモデリングワークショップ第2弾

前回に引き続きかとじゅんさんによる勉強会の第4弾で、ドメインモデリングワークショップの2回目を開催していただきました。

前回のワークショップでは、かとじゅんさんのドメインモデリングワークショップを参考にWalletアプリケーションで登場する概念のモデリングを行いました。

今回のワークショップではそのモデルを利用する側や保存する側などにもフォーカスして、アプリケーションの1つの機能として通して動作するよう実装していきました。

クリーンアーキテクチャ

始めに前回のワークショップのおさらいと、1つの機能を通して実装するに当たりクリーンアーキテクチャの解説をしていただきました。

その後JavaチームとScalaチームに分かれ、各チームはモブプログラミングの形式で実装を行っていきました。

IMG_2748.JPG

前回のワークショップで作成したアプリケーションを、まずはクリーンアーキテクチャの構成に変更していきました。

かとじゅんさんのクリーンアーキテクチャの記事で紹介されている構成を参考にinterface-adaptorcontract-interface-adaptoruse-casedomaininfrastructureというプロジェクトを作成し、依存関係を定義しました。

以下はその記述の一部です。(JavaチームではGradleを使用しています)

IMG_2753.JPG

build.gradle

project(':interface-adaptor') {
        dependencies {
            compile project(':contract-interface-adaptor')
            compile project(':use-case')
            compile project(':domain')
        }
    }

    project(':contract-interface-adaptor') {
        dependencies {
            compile project(':domain')
        }
    }

    project(':use-case') {
        dependencies {
            compile project(':contract-interface-adaptor')
            compile project(':domain')
            compile project(':infrastructure')
        }
    }

    project(':domain') {
    }

    project(':infrastructure'){
    }

各プロジェクトが上位のプロジェクトに依存しないことを実装者に強制するために、パッケージではなくプロジェクトで分けるという方法が印象的でした。

clean.png

Wallet作成のユースケース

今回取り組んだのはウォレットの作成というユースケースでした。

アドバイスをもらいながら、Walletの識別子をULIDを用いたものに変更したり、金額をint型からMoney型という値オブジェクトに変更するというリファクタリングを進めつつ、今回新たに作成したuse-caseinterface-adaptorについても実装していきました。

interface-adaptorに作成したWallet用のRepositoryはメモリ上に保存する実装をしたのですが、これはcontract-interface-adaptorにあるWalletRepositoryというinterfaceを実装しています。

後に保存先がDBになった場合でも、contract-interface-adaptorに定義したinterfaceを守った上で新しいRepositoryを実装することでuse-casedomainが隔離されinterface-adaptorの変更に左右されなくなるので、クリーンアーキテクチャのメリットを体験できる良い機会になりました。

最後に

今回のかとじゅんさんによるワークショップではDDDとクリーンアーキテクチャを用いて実際にアプリケーションの1つ機能を通して実装しました。

ドメインモデリングのワークショップを始めるにあたって、参加者から「Evans本やIDDD本を読んだが実際のプロジェクトにどう適用していけばいいかわからない」という意見をもらっていたのですが、この2回のドメインモデリングワークショップに参加して、「ずいぶん実装のイメージが掴めた」という言葉を貰え、非常に有意義な勉強会となりました。

今後は実装に対する理解を深めていきつつも、よりドメインを深く理解するという考え方や手法についてもフォーカスして知見を深めていきたいと考えています。

『Effective Java 第3版』研修を開催しています

『Effective Java 第3版』研修を開催しています

アットウェアでは、2018年12月より、柴田芳樹さんを講師に迎えて『Effective Java 第3版』研修を開催しています。

『Effective Java』は、かねてよりJavaプログラマにとって必読の書と言われてきました。その最新改訂版として昨年10月に『Effective Java 第3版』(Joshua Bloch著、柴田芳樹訳、丸善出版社)が出版されました。およそ10年振りとなる今回の改訂では、Java9 までの新しい構文・機能が網羅され、現代的な構文を Java ではどのように記述するべきか、示唆に富んだ内容になっています。社内では、普段から読書会が開催されており、当初『Effective Java 第3版』も有志を集めての読書会というかたちで開催する予定でした。しかし、翻訳者である柴田芳樹さんを講師に迎えるという幸運に恵まれたこともあり、研修という形での開催となりました。

柴田さんは、古くからJavaに関わられており、Javaの黎明期から業務としてJavaを利用してきた経験をお持ちです。また、『Effective Java』だけではなく『プログラミング言語Go』の翻訳や、『プログラマー"まだまだ"現役続行』の執筆など、多方面で活躍されています。そんな柴田さんのお話を伺おうと、研修には、入社1・2年目の新人から、開発経験10年以上のベテランまで、10名の社員が参加しています。

研修は、基本的には輪読・記載されている内容の解説・質疑応答という形で進められていきますが、解説される内容は書籍に記載されている内容だけにとどまらず、さらに掘り下げる形でJavaの言語仕様やJVMの仕組み、コンピューターサイエンスにまで踏み込む講義をされることがあります。とくに言語仕様やJVMの解説では、なぜJavaの文法がこのようになっているかという側面から話がすすめられるので、説得力をもったないようで語られます。また、折に触れ柴田さんの業務での実体験が話としてされることがあり、エンジニアとして業務を推進する姿勢や開発に対する意識など学ぶことが多くあります。経験に裏付けられた柴田さんの講義は、新人だけでなくベテランにまで良い刺激になっているのではないかと思います

研修も既に6回を数えており、和やかな雰囲気で進められています。研修の中で書籍のミスを発見する出来事もあれば、休憩時間には『Java PAZZLERS』の問題を解いたり、柴田さんから90年代のJava開発の話を聞くなど、講師と参加者の距離も縮まっており、楽しい時間を過ごしています。

研修は6月で終了予定ですが、この研修を通して、エンジニアとしてより大きく成長したいと思っています

ヌーラボの木村さんを招いてKubernetes勉強会を開催しました

ヌーラボの木村さんを招いてKubernetes勉強会を開催しました

ヌーラボ木村さんを招いて発表形式の勉強会を開催しました。

Kubernetes Meetup Tokyo #14 に弊社メンバーが参加した時に「イイ話だった。もっとお話を聞きたい」という声が出て、オファーをださせて頂き、快諾頂いて実現するに至りました。今回の為だけに業務を調整して、福岡から飛行機で駆けつけくださり感謝です。

最近、このような頻繁にゲストを招いて勉強会の開催が実現している様子をみると、普段の社内の声って大事だなとつくづく感じます。一歩踏み出し、アクションをすることができれば、色んなわくわくすることができたり、向かいたい方向へ前進できてイイ感じです。

そして、今回のKubernetes勉強会の内容の話題に移っていこうと思います。

開催にあたり、参加者の層やどんな話が聞きたがっているのかを事前に会話させてもらい、以下のようなテーマを候補を出していただきました。

  1. Kubernetes 基礎知識
  2. Cacoo の Kubernetes 環境
  3. Cacoo の Microservices 事例
  4. Kubernetes のつらみ
  5. Kubernetes Tips 、便利ツールなど

ついつい全部関心があると言ってしまいそうになるところですが、参加者をアットウェアエンジニア全員として案内をする予定なので、基礎や浅いところにも触れていただき、なぜKubernetesにしようと思ったか、調査や導入前段の思考をお話を聞けると、幅広い層にもリアル感が伝わっていいかなと考えています。

というようなご相談をさせていただきました。

また、Kubernetsに特に関心があったりKubernetes Meetup Tokyoに参加したメンバーの中で以下のような話も聞いてみたいというニーズがありました。

  • Kubernetes meetupで発表された内容をさらに深掘りした話
  • 導入後の苦労や運用周りの話
  • マイクロサービスの周りのアプローチを、どういう風にKubernetesのオブジェクト単位にまで落とし込んでいったかという話

これらのお話は「ぜひ懇親会でも更にお聞きできたら」ということを木村さんにお伝えし、当日の勉強会のテーマが決まっていったのでした。

開催された勉強会では、木村さん自身が関わっているCacooの開発を続ける中で、積み上げられた歴史を知る木村さんだからこそ伝えられる、オンリーワンなすごくいい発表を聞かせていただきました。

当日の勉強会で印象に残っていること

  • マイクロサービスと国際的なロケーションを組織にあわせた形でうまく融合できるようになってきたという話。
    • 最初うまくいかなかったところや工夫も聞けてよかったです。
    • 明確なオーナーシップを持つことでうまくいったそうです。
  • 使っているフレームワークや言語は多彩であった
    • その中でも独自のルールをなくして、標準的なルールに従うようにするアプローチをとった。
    • 新しいメンバーが入ってきても伝わりやすいというメリットを享受。
  • 本番環境とステージング(ベータバージョン)の使い方とアプローチ方法
  • コストの考え方
    • インフラコスト(メモリ使用量なども含め)は上がることが多い
      • AWSが儲かるアーキテクチャなんじゃないかな(笑)。だからみんなプッシュしているんですよ。(笑)と談話
    • ↑は半分冗談まじりな言い方ですが、メリットがあるからやっている
      • システムのスケールに加えて、複数拠点などの働き方・開発チーム自体もスケールできた。
      • 仕様を変えて進めていく時のやりやすさ
    • こういう話を弊社(アットウェア)のエンジニアが聞いたことを会話し、トレードオフや共通理解を深めていけると感じた。
  • 懇親会でも刺激を受けた
    • 木村さん自身が関わったKubernetes周り以外のナレッジもチームメンバーから得て、勉強会で展開したり、自身の学び・仕事に活かしているとのこと
      • チーム内で各々が素晴らしい関係が築けていることがわかるというエピソード。
    • 実は私と数名が横浜で行なっている個人活動のコミュニティの勉強会が、木村さんにとって勉強会デビューだったという出会いのミラクルな事実が発覚。

最後に

書きたいことは他にもいっぱいありますが、控えめに言って最高すぎたということで、この辺りで筆をおきたいと思います。 今回の内容とは全く同じではありませんが、木村さんがKubernetes Meetup Tokyoで発表されていた動画が公開されています。ご興味ある方は、ぜひ一度見てみてはどうでしょうか。

素晴らしい発表どうもありがとうございました!

かとじゅんさんを招いて「ドメインモデリングワークショップ」を開催していただきました

かとじゅんさんを招いて「ドメインモデリングワークショップ」を開催していただきました

前回に引き続きかとじゅんさんによる勉強会の第2弾です。

ドメインモデリングワークショップ

今回はドメインモデリングについてのワークショップを開催していただきました。 前回の勉強会の際に社内で、「Evans本やIDDD本を読んだが実際のプロジェクトにどう適用していけばいいかわからない」という声が上がっていました。 今回のワークショップではその辺りの疑問に応える形で、実際のユースケースからどうドメインモデルに落とし込んでいくか、という部分に焦点を当てたワークショップを開催していただきました。

お題はかとじゅんさんがScala福岡 2019でご講演なさったウォレットサービスのドメインについてでした。 想定するユースケースも講演内容のユースケースと同様のものが予め展開され、そこからどのようにモデルを見つけていくかという内容でした。

ドメインモデルの分析

以下がドメインモデルの分析で行ったモデリングの一例です。 付箋の色で表すものが違っていて、緑がドメインモデル、青が振る舞い、赤が属性、黄色が気になることとなっています。 支払いについてはウォレット集約の配下でまとまりましたが、請求をウォレット集約に含めるか否かで活発な議論がなされていて、モデルについてより深掘りして考える良い機会となりました。

IMG_20190122_185533.jpg
スクリーンショット 2019-01-23 19.06.40.png

ドメインモデルの実装

分析したモデルをモブプロ形式で実際のコードに落とし込んでいく演習も行いました。 言語の指定はなかったのですが、チームの共通言語がJavaだったので今回の演習ではJavaで記述しました。 頂いたレビューの中で、集約の与り知らないところでコレクションの操作をされるのを防ぐために、コンストラクタや値の返却時にはコレクションのコピーを渡すべきというのが印象的でした。

インスタンス生成時

// List<WalletEvent>は本来ファーストクラスコレクションにするのが望ましい
public Wallet(long id, List<WalletEvent> eventHistory) {
    this.id = id;
    // 外から渡された参照は外部で任意の操作をされることがあるので中身のコピーを渡す
    this.eventHistory = new ArrayList<>(eventHistory); 
}

値の返却時

public List<WalletEvent> getHistory() {
    // 渡した参照に外部で変更を加えられる恐れがあるのでコピーを返却する
    return new ArrayList<>(eventHistory); 
}
%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88+2019-01-23+19.05.49.jpg

最後に

かとじゅんさんにワークショップを開催していただき、実践的なドメインモデリングの手法を体験することができ非常に有意義で楽しい時間でした。 とても良い題材を頂いたので今後もウォレットサービスをベースにDDDについて取り組み、知見を深めていきたいと考えています。

増田さんを招いて「ドメイン駆動設計 考え方と技法」をテーマとして社内勉強会を開催しました

増田さんを招いて「ドメイン駆動設計 考え方と技法」をテーマとして社内勉強会を開催しました

あけましておめでとうございます。 昨年末に『現場で役立つシステム設計の原則』の著者としてもよく知られている増田さんをお招きして、ドメイン駆動の考え方や技法についての勉強会を開催しました。

開催のきっかけ

よこはまクラウド勉強会に参加しているメンバーがDDD Aliance高崎さんと弊社で『現場で役立つシステム設計の原則』が話題になっているというお話をした際に、お繋ぎしましょうかというお話を頂き今回の勉強会が実現しました。 著者の方から直接お話を聞けるとは思いもしなかったので、今回の機会を実現していただいた高崎さんには心より感謝致します。

内容

まずはソフトウェアの諸問題は変更容易性に依存していているというお話からはじまり、モジュール構造の評価指標、設計スタイルの話からドメインロジックに焦点を当てる開発手法の話へとわかりやすく順序立てて話を進められていました。 ドメインロジックに焦点を当てる開発において要求を分析するための技法のお話では、Fact-Rule-Goalアプローチ、VETRO分析などのキーワードでお話しいただき、実際の業務においてどうビジネスポリシーをどう噛み砕いていくかというを説明していただきました。 コードに近づいたところの話としてはドメインオブジェクトの設計パターンも説明していただきました。 クラスの単位や計算の種類、区分オブジェクト、コレクションの操作や契約による設計などのキーワードで説明していただき、よりドメインロジックに焦点を当てたモデリング手法を理解できました。

強く印象に残っているのが区分オブジェクトのお話のところで、区分、種別を見つけたらEnumに寄せてみようというものでした。 例としてレンタルビデオの料金種別を考えたときにEnumに{ 新作, 一般, 子供 }に列挙すると新作の子ども向けの場合はどうなるのか等の違和感が出てくる。 その違和感を見つけることでより本質的なドメインに焦点を当てることができるというお話をしていただき非常に参考になりました。

もう1つ強く印象に残っているのが契約による設計で、事前条件/事後条件の担保をassertでするのではなく引数や戻り値の型を用いて行うという手法です。 値オブジェクトとして定義した型を利用することで、その型のインスタンスが生成されているということは、存在すべき値の範囲で生成されているということができていると見なすことができるというお話でした。 増田さんのこちらの資料にもある事前条件/事後条件の担保についてのお話です。 利用する側としても渡すべき関心事が明示的でわかりやすいです。

私の普段の業務でドメイン駆動設計のことで疑問に思っている事柄について増田さんに質問をしたところ、増田さんご自身の中での答えとともに、日々実験的に色々なアプローチでより良い答えを探し続けているというお話をいただきました。 私も日々の業務の中で色々なアプローチを試しより良い答えを探し続けていく姿勢を学びました。深く感謝致します。

最後に

atWareでは技術スタックの1つとしてDDDをあげられるよう日々学習しています。 その1つの活動として今回の増田さんや前回の記事のかとじゅんさんのようなDDDの先駆者の方々をお招きして社内ナレッジを蓄積しております。 その過程で私どもとしても提供できるアウトプットがあればぜひして展開していきたいと考えております。 今後とも宜しくお願いします。