この記事ではsp2というJavaScript/TypeScriptの状態更新ライブラリを紹介していきます!
Immutable.jsとかImmerとかを使ってる人が見たら楽しいかもしれない。
(ちなみに私は今フロントエンド界隈ちょっと離れちゃってるので、今の主流がなんなのか、とか分かっていないです、許して..)
sp2とは
ちょっと期待してこのQiita記事にたどり着いた皆さん。
sp2のstar数などをみてがっかりしただろうか。
そしてこのライブラリ作者が私であるということを知ったらもっとがっかりするかもしれない。
あ、でも離れないで。もうちょっと読んで。
fully-testedで、fully-documented、そしてproduction実績多数ございます。
私の会社でも採用してくれてます(うちの会社のエンジニアのみんなありがとう)。
TypeScriptの型もバリバリ利いてくれて心地よく使えます。
で、そいつは一体どんなものなのか、見ていこう。
使い方のイメージ
const obj = { foo: { bar: "xx" } };
const operation = { $set: { "foo.bar": "baz" } };
const newObj = update(obj, operation);
newObj
は、
{ foo: { bar: "baz" } }
になります。
だからなんなのでしょう?
そうですね。
そう思いますね。
代入すればいいですよね?
obj.foo.bar = "baz";
これで目的のものが手に入る人もいるでしょう。
が、Fluxアーキテクチャだとそうはいかないっすよね。
Immutableが必要だったよね
あ、そうか。単に代入すると、objが書き換わってしまい、新しいobjができないか。
Fluxアーキテクチャだと、新しい状態だと検知させるために、新しいobjectのアドレスを与える必要があるんでしたね。
これを解決するのがImmutableという概念なわけですね。
ちなみにメモリ効率を考えてshallow copyをするのが常套手段なわけですが、sp2もそれを実施してくれます。ご安心を。
Immutable.jsで良いんでは
Immutable.jsでは、独自のオブジェクトを作ってそこにメソッドを生やすことにより、mutableっぽい操作なのに戻り値が新しいアドレスのobjectであるという状況を実現してくれます。
const obj = Map({
foo: Map({ bar: 'xx' })
});
const newObj = Map.setIn(["foo", "bar"], "baz");
ただこれを使うと、シンプルな、ネイティブなobjectを使いたい、という欲求には応えられなくなります。
全部Immutable.jsな世界、を受け入れられればそれはいいのかもしれないが、もっとシンプルに解決したい。
そこでImmerなのでは
その点Immerは、シンプルなobjectを扱うことができて快適ですね。
値を更新する関数を定義することでそれを解決できます。
const obj = { foo: { bar: "xx" } };
const newObj = produce(obj, draftState => {
draftState.foo.bar = "baz";
});
ただこの値を更新する関数というのは、JSONにしたらもちろん消えちゃいますよね。
(Function.toString()
? 驚き最小の原則に反しませんか?)
つまりこの関数は、環境を超えて渡せないんです。
環境を超えて渡す必要がないんですが。
わかります。あんまりないです。だからImmerでいいんです。でも待ってください。
たとえば、皆Gitを使っていますよね。
要はJSONのGit
Gitは、差分(=commit)をデータとして管理しているから、
差分だけを送りあえば、環境を超えてリポジトリを同期できます。
比較的大きなデータをサーバーとクライアントで同期するとき、
更新差分をJSONメッセージにしたら、通信量を抑えて同期できますよね。
たとえば個人だけが情報を管理する日記帳のようなスマートフォンアプリで、
サーバーがそのデータの永続化だけを担うような場合ってありますよね。
他人とのinteractionが発生しないデータ。
そういう場合、そのアプリの構造化されたJSONデータをサーバーに永続化する際、
差分だけを送り続ければいいですね。
Gitの1人プロジェクトで、commitしてpushしてる状態と一緒ですね。
だからデータで差分を表現したかった
だから、データで差分を表現したかったのです。Gitの1commitがデータで表現されているのと同じように。
Immutability Helpers??
と、語っていたら、同じようなものがあるみたいです。
僕がこのsp2を作ったのは2018年で、たぶんそのとき↑コイツはなかったんです。
今調べたら、こんなのができてたんですね。
- simple objectを扱う
- 更新差分がJSONで表せる
そして公式だし最強ですね。
あ、でもまだ解散しないでいいんですよ。
sp2の真価はここからですから。
MongoDB準拠
MongoDBというドキュメント指向DBがあります。
誤解を恐れずにいうとJSONをそのまま永続化するタイプのDBなのですが、
このMongoDBもデータの更新のためにJSONを指定することができるんです。
sp2は、これに準拠して作りました。
Immutability Helpersよりもぐんと表現力があります。
MongoDB準拠だとどう嬉しいのか
そうですよね。MongoDB使ってないと嬉しくないかもですね。
それはごめんなさい。
けど、原理的には、MongoDBに同じ操作を直投げすれば、
常にクライアントとMongoDBで値が同期できるってわけなんです。
いちいちデータ更新のREST APIを生やしまくる必要ないんです。
(あ、GraphQLですか?あれいいよね)
このsp2を利用しつつ、MongoDBと通信してサーバークライアント同期を達成してくれる
phenylというライブラリもあり、
特に日記みたいな、ユーザーだけがデータを更新するようなしくみで楽しく使えます。
型サポートがすごくいい感じ
import { $bind, update } from "sp2";
// target object type
type Person = {
name: { first: string; last: string };
age: number;
};
const { $set, $path } = $bind<Person>(); // Inject the type and generate operation-creating functions.
const operation = $set($path("name", "first"), "John");
$bind()
という命令を使えば、
Person
型が、name.first
という階層的なプロパティを持っていることもわかるので、
下のように補完してくれます。
これは本当に便利です。
Production-ready
ソフトウェアエンジニアリングの世界は、
理屈上の便利さだけではなくて、
どれだけ普及してるかが普及の鍵になるという厳しい世界で、
ネームバリューがないと残念なことになるわけですが、
でもテストカバレッジは100%で実践投入していいやつです。
テストケースをあげておきますね。ガチの仕様として参考になります。
https://github.com/phenyl/sp2/blob/master/modules/updater/test/updater.test.ts
あと、環境依存していない論理ライブラリなので、Universalに使えます(node.js上で使ったっていい)。
名前 なんでsp2なの?
The name "sp2" is derived from the name of orbital of chemical bond used in phenyl group. This was once a core function of Phenyl framework and extracted from the library. Another meaning of "sp2" is State-operating Procedures with Portability. Portability means that procedures are expressed by JSON data. This makes procedures portable and applicable over different environments.
sp2は、phenyl基を構成する化学結合、sp2混成軌道にちなんで名付けられています。
phenylというライブラリ(DDDのhexagonalアーキテクチャをシンプルに実現しよう、という目的で六角形のベンゼン環を持つフェニル基からつけられた)のコアとなる概念だからそういう名前にしてます。
(出典: 猫でもわかる有機化学 http://xn--u8jvc1drbs0514cvfm43vv1giwx.net/benzene-features-2/)
もう一つの意味が、
State-operating Procedures with Portabilityで頭文字がSPPとなることからsp2という意味です。
Portabilityというのは、差分更新データを異なる環境に持ち運ぶことができるという意味です。
phenyl??
sp2を使ってサーバーとクライアントでの差分同期を実現するためのライブラリ phenyl(フェニル、と発音)については、
また次回説明します。
読んでくれてありがとう。