Almin 0.13リリース - アプリケーションレイヤーのトランザクション
Almin 0.13.xのStable版をリリースしました。 今後に向けて下地となる基盤部分をかなり書き換えたのと、Strict modeや試験的にTransactionを追加しました。
まとめ
0.12xからの0.13.10の変更をまとめると次の通りです。
- 🔥 Breaking Changes
- IE9/IE10のサポート終了
- CIから落としただけなのでまだ動くかもしれないですが
- 一部Deprecatedはありますが基本的にコードは変更しなくても動くはずです
- IE9/IE10のサポート終了
- ⚠️ Deprecated
- リネーム
context.onWillExecuteEachUseCase
->context.events.onWillExecuteEachUseCase
- almin/migration-toolsを使うことで自動的にマイグレーションできます。
- リネーム
- 🆕 Features
- Strict mode
- Transaction(strict modeでのみ有効)
-
executor
-execute
のType Safe版です
-
Internal
- Unit of Work(作業単位)が内部的な実装として追加され、データフローなどがコードとしてきちんと管理されるように(Transactionなどもこれを利用)
詳しくはRelease Almin 0.13.10 · almin/alminをみてください。
Breaking Change 🔥
Almin 0.13は幾つか破壊的な変更がありますが、殆どの場合コードは変更しなくても動くと思います。
Drop IE9 and IE10
IE 9/10のサポートは終了しました。
Store#onDispatch receive only dispatched the Payload by UseCase#dispatch #224 #254 #255
Store#onDispatch
は今までUseCase#dispatch
以外の内部的なpayloadを受け取っていました。
今回からはStore#onDispatch
はUseCase#dispatch
のみ、つまりユーザー自身がdispatch
したpayloadのみを受け取ります。
他のAlminのライフサイクルイベントを受け取り対場合はcontext.events
(LifeCycleEventHub
)を参照してください。
Recommened: 基本的にはStoreの更新はStore#receivePayload
で行うことを推奨しています。(Strict modeはこれをチェックできます)
Store#receivePayload
でのStore更新のパターン
class YourStore extends Store {
constructor(){
super();
// Initialize state
this.state = {
foo : "bar"
};
}
// Update code here
receivePayload(payload){
this.setState(this.state.reduce(payload));
}
getState(){
return this.state;
}
}
Automatically catch throwing Error from UseCase#execute()
#193 #194
0.12まではUseCase#execute
で同期的なエラーが発生すると突き抜けて、window.onerror
まで到達していました。
0.13からは、同期的なエラーであってもcatch
できるようになっています。
class ThrowUseCase extends UseCase {
execute() {
throw new Error("error");
}
}
context.useCase(new ThrowUseCase).catch(error => {
console.log("It can catch")
});
new Store()
throw Error
Store
はabstract classなので継承してください。
new Store()
は単純にgetState
時に例外を投げます。
Notable Changes
Renaming: context.on*
to context.events.on*
#239
context.on*
などのライフサイクルイベントを検知するイベントをcontext.events.on*
に移動しました。
ただし、Context#onChange
はライフサイクルイベントという扱いではなく、UI更新のためのハンドラを登録するAPIなのでそのままContext
に残っています。
他のAPIは非推奨となっています。
今はまだ@deprecated
タグがついただけですが、もうちょっとしたらコンソールにも警告が出るようになります。そして次のメジャーアップデートで削除します。
これを移行するマイグレーションツールも用意してあるのでご利用ください。
Migration
almin/migration-toolsはつぎのようにnpmでインストールできます。 後は移行したファイルを指定して対話に従って選択すれば完了です。
Notes 先にバージョン管理システムにコミットしてバックアップしてから実行してください。
npm install -g @almin/migration-tools
almin-migration-tools [<file|glob> ...]
Before:
context.onWillExecuteEachUseCase((payload, meta) => {});
context.onDispatch((payload, meta) => {});
context.onDidExecuteEachUseCase((payload, meta) => {});
context.onCompleteEachUseCase((payload, meta) => {});
context.onErrorDispatch((payload, meta) => {});
After:
context.events.onWillExecuteEachUseCase((payload, meta) => {});
context.events.onDispatch((payload, meta) => {});
context.events.onDidExecuteEachUseCase((payload, meta) => {});
context.events.onCompleteEachUseCase((payload, meta) => {});
context.events.onErrorDispatch((payload, meta) => {});
UndocumentなAPIであるUseCaseExecutor#on*
が削除 #243
Remove
UseCaseExecutor#onWillExecuteEachUseCase
UseCaseExecutor#onDidExecuteEachUseCase
UseCaseExecutor#onCompleteExecuteEachUseCase
Feature 🆕
Strict mode
詳しくはドキュメントを見てください。
ドキュメント: https://almin.js.org/docs/tips/strict-mode.html
Strict modeはStore#receivePayload
以外のタイミングで、Store
を更新シていると警告を出すようにするモードです。
// enable strict mode
const context = new Context({
dispatcher,
store,
options: {
strict: true
}
});
関連:
Context#transaction
#226
- Stability: Experimental
- この機能はstrict modeではないと警告がでます
Context#transaction
はUseCaseをトランザクション的に実行するAPIです。
Context#useCase
は一つのUseCaseを一個ずつ実行して、そのUseCaseの実行終了毎にViewの更新が行われます。(具体的にはStoreが一つでも変更されているならContext#onChange
が呼び出されます)
一方、Context#transaction
はTransactionContext
というトランザクション用のコンテキストを作り、transactionContext.useCase
でUseCaseを実行してもすぐにはViewの更新は行われません。
幾つかのUseCaseをtransactionContext.useCase
で実行した後に、確定したいタイミングで、transactionContext.commit()
を実行するとそれまでに実行していたUseCaseからのdispatch
やライフサイクルなどはまとめてStoreに伝わります。
Context#transaction
の中でUseCaseを何度実行しても結果として起きるViewの更新はcommit()
したタイミングの一回だけになります。
サンプルコード:
const context = new Context({
dispatcher: new Dispatcher(),
store: storeGroup,
options: {
strict: true
}
});
// then - called change handler a one-time
let onChangeCount = 0;
context.onChange(stores => {
onChangeCount++;
});
// when
context
.transaction("transaction name", transactionContext => {
return transactionContext
.useCase(new ChangeAUseCase())
.execute(1)
.then(() => transactionContext.useCase(new ChangeAUseCase()).execute(2))
.then(() => transactionContext.useCase(new ChangeAUseCase()).execute(3))
.then(() => transactionContext.useCase(new ChangeAUseCase()).execute(4))
.then(() => transactionContext.useCase(new ChangeAUseCase()).execute(5))
.then(() => {
// commit the result to StoreGroup
transactionContext.commit();
});
})
.then(() => {
// finish the transation
console.log(onChangeCount); // => 1
});
名前の通り大量のUseCaseを連続して実行する必要がある際に利用できます。 初期画面に必要なUseCaseが複数があるが、その途中で何度もViewを更新する必要がないといった際に、一つのトランザクションとしてまとめることができます。
例えばReactにもReactDOM.unstable_batchedUpdatesという隠しAPIみたいなものがありますが、そういうのをState管理側でやるための仕組みです。
Reduxにも似たようなBatch updatingの仕組みを持ったmiddlewareがあります。
- Idea: Batching actions · Issue #542 · reactjs/redux
- acdlite/redux-batched-updates: Batch React updates that occur as a result of Redux dispatches, to prevent cascading renders. See https://github.com/gaearon/redux/issues/125 for more details.
AlminのContext#transaction
はstrict modeじゃないと正しく動きません。
簡単にいうと、Alminの範囲外でStoreが更新されているとそのトランザクションが正しくても結果が正しくないことがおこるのでそういうケースを防止するためです。
現在の実装は1つのトランザクションで1回のcommit()
またはexit()
のみができます。exit()
した場合はそのトランザクションで実行したUseCaseのイベントを破棄します。
また、今はあるUseCaseの中からcommit()
したい!やサブトランザクションのような概念はありません。Stability: Experimentalなのでその辺に意見ある人は意見ください。
Add Fluent style executor
#193
- Stability: Experimental
- This feature is subject to change. It may change or be removed in future versions.
- See #193
TypeScriptでUseCase#execute
をする際に型チェックがちゃんとできるバージョンです。
context.useCase(new MyUseCase())
.executor(useCase => useCase.execute("test"))
.then(value => {
console.log(value);
});
context.useCase(useCase).execute()
はcontext.useCase(useCase).executor(useCase => useCase.execute())
の糖衣構文です。
もっとこうした方がよさそうという意見がある場合は次のIssueに意見をください。
Documentation
APIドキュメントが更新されているので https://almin.js.org/ をみてください。
Almin-logger
- [email protected]は Almin 0.13.x をサポートしています。
Internals
JavaScriptのライブラリを徐々にTypeScriptに移行する | Web Scratchで書いたようにAlminのテストをTypeScriptに少しづつ移行しています。
まだ全部は移行できてないので、Pull Request待ってます!
Notes 📝
Unit of Work
Almin 0.13からUnitOfWorkという内部的なクラスが追加されました。 簡単にいうと通常はUseCaseの実行をそのままStoreに流してくれますが、トランザクション時はUseCaseの実行を止めたり進めたりできるものです。
Context#transaction
はこれの上に作られています。
サンプルコード
サンプルコードにTypeScript + AlminでのTodoMVCの実装を追加しました。
JavaScript版やFlowType版などもあります。
個人的にはshopping-cartの方がらしく書けているかなと思います。
触ってみて何かおかしなサンプルがある可能性はあるので、そのときはIssueを立ててください。
もっと実際のアプリケーション的なコードを見たい場合はFaaoなどを見てみてください。 これはプロダクションのレベル感で書いているので、そこそこ複雑です。
- azu/faao: Faao is a GitHub issue/pull-request client on Electron.
- Faao - ドメイン駆動設計で作るGitHub Issue Client -
おわり
Alminは大抵の人が読んで分かるコードを書けるようにデザインしています。
ありふれたクラスベースにしているのもそうですが、
react-reduxのconnect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
みたいにいきなり厳しい感じになったしないように気をつけています。
Reduxはドキュメントも充実してるし、やっぱり基盤的な部分はよくできるてるのでReduxもちゃんと理解した方がいいと思います。ミドルウェアや拡張部分が複雑になったりしてるだけでコアはしっかりしてます。
AlminはReduxやFluxのようなこともできますが、ドメイン層をクライアントサイドどう扱うかという部分に焦点を置いています。 逆をいえば、Alminが扱わない領域(ドメイン/インフラ)と扱う領域(アプリケーションレイヤー/Viewの連携)をはっきり区別するということにつながります。
たとえば、Fluxだとドメイン(ロジック)をどこに置くかを考えると、Storeの中を構造化していくことになることが多いです。
Storeの中だとドメイン側がライブラリの都合に引っ張られてしまいがちです。(強い意志が必要) なので、Alminは扱う領域をアプリケーションレイヤーのみにして、その他のドメインレイヤーなどはユーザー側で扱う領域としています。
この構造によってドメインのロジックはPureなJavaScriptとして書けることを期待しています。 この辺については次のスライドで書いています。
- 複雑なJavaScriptアプリケーションを考えながら作る話
- azu/large-scale-javascript: 複雑なJavaScriptアプリケーションを作るために考えること
- Faao - ドメイン駆動設計で作るGitHub Issue Client -
実際にある程度の規模感のものもちゃんと書けるようです。
今後の予定としては、トランザクション周りをもう少し考えたいのと、結局ライブラリの中として上手くできていてもプロジェクトの中で上手く動くかは別なのでその辺を上手くナビゲーションできる仕組みを考えたい気がします。
例えば、AlminのUseCase実装からユースケース図を自動生成とかは現実的にできます。このようなプロジェクトが目指すべき構造からずれていないかを可視化できたりチェックできるような仕組みを提供できると、ユーザーは書くことに集中できてモチベーションが維持しやすいのではないかなと思ったりします。
そういう視点のContributeも待ってますし、それとは別にPRが送りやすそうなIssueはGood for beginners
ラベルが付けてあります。
お知らせ欄
JavaScript Primerの書籍版がAmazonで購入できます。
JavaScriptに関する最新情報は週一でJSer.infoを更新しています。
GitHub Sponsorsでの支援を募集しています。