JavaFX で Flux アーキテクチャを実装する


この記事は JavaFX Advent Calendar 2016 の6日目です。昨日は @makoke さんの Gluon Mobile(無料)を試してみる でした。あすは @Ziphil さんです。

概要

JavaOne 2016 で聞いてきた CON3927: Flux Architecture with JavaFX の内容をお送りします。

CON3927: Flux Architecture with JavaFX

Speaker Manuel Mauky @manuel_mauky
Slide https://static.rainfocus.com/oracle/oow16/sess/1462804682452001cMeJ/ppt/Flux%20with%20JavaFX%20(JavaOne%202016).pdf

Abstract

With React.JS and the flux architecture, Facebook brought new ideas into the world of front-end development. The core idea of flux is to restrict unidirectional data flow between all components and to explicitly model user interactions. The goal is a better understanding of what is happening inside the application. But JavaScript developers aren’t the only ones who can profit from this architecture pattern. With some modifications, even JavaFX developers can use flux to structure their applications. This session gives an introduction to the flux architecture in general and shows how developers can implement flux with JavaFX by using some reactive programming techniques such as the data binding capabilities of JavaFX and reactive streams.

感想

まず Flux アーキテクチャというのがどんなものかを解説してから、それを JavaFX でどうやって実装していくのかを解説していました。Flux アーキテクチャ自体は Web アプリケーションのために考え出されたものですが、ユーザ入力を受け付けるフロントエンドを持つアプリケーションという点では JavaFX (やスマートフォンアプリ)でも共通していて、同じデザインパターンを適用できる可能性があるという気づきがありました。今後は その辺もより見ていく必要があると感じました。


まず、 Flux アーキテクチャとは何か?

  • Facebook 発、Front-End 用のアーキテクチャパターン
  • データの流れを一方通行にすることで、アプリケーションの構造をシンプルにできる
  • 特定の Framework に依存するものではない

Flux アーキテクチャの利点

  • バグがどこにあるのか探すのが容易
  • フロントエンドのコードのテストが容易
  • Undo/Redo の実装が容易
  • イベントソースの実装が容易
  • より関数指向な開発スタイルが可能

日本語の解説は「Fluxデザインパターンの勉強」をご参照ください。

Core idea: unidirectional data flow

データの流れを一方通行にすることでシンプルにする

Store

  • アプリケーションの状態/ロジック/ビジネスユニットの表現 を持つ
  • Setter は持たない(=外側から直接値を操作させない)
  • 「受け取ったデータから、どう振る舞うか」はここで実装する

View

  • Store オブジェクトと Controll を持つ
  • Store のデータを表示
  • Store のデータが更新されたら表示内容を更新

Action

  • View で受け付けたユーザ入力に応じて生成される
  • Type (Actionの種類) と Payload (Actionの値)を持つ
  • GoF の Command Pattern に類似
  • 実際のコードを見ると、「どう振る舞わせるか?」を実装するのではなく、「何の値でどのActionを実行させるか」を保持させる模様
Action's sample
{
  "Type": "CREATE_USER",
  "Payload": {
    "name": "John",
    "age": 12
  }
}

Dispatcher

  • Reactive Streams を持つ
  • すべての Action をすべての Stores に渡す役目をする
  • 同期的に動作する

Flux アーキテクチャを JavaFX でどのように実装するのか?

JavaFX に不足しているもの

  • JavaFX の Scenegraph には Virtual-DOM に対応する仕組みも API もない
  • not fully declarative …… ListView を定義するにも FXML だけでは完結せず、冗長なクラス定義が必要

しかしながら、 JavaFX は Reactive programming 対応できる

  • Data binding……FXML
  • Reactive Streams……ReactFX or RxJavaFX ※ ReactFX は前述の React.JS とは何の関係もない

実装に向けてのアイデア

  • Store は内部プロパティを利用
  • Stores は外部に read-only properties を提供
  • View は Store の read-only properties をバインド
  • Action は Command Pattern でクラスを実装
  • Dispatcher は Reactive Stream
  • Stores は Dispatcher stream に対し subscribe


Example Code

クリックすると数値がカウントアップするカウンターのアプリケーションで考える。

Store
public class CounterStore {

    private IntegerProperty counter = new SimpleIntegerProperty();

    public CounterStore() {
        Dispatcher.getInstance()
                  .getActionStream()
                  .filter(a -> a.getClass().equals(IncreaseAction.class))
                  .map(a -> (IncreaseAction) a)  // cast is needed
                  .subscribe(this::increase);
    }

    private void increase(IncreaseAction action) {
        counter.set(counter.get() + action.getAmount());
    }

    public ReadOnlyIntegerProperty counterValue() {
        return counter;
    }

}
View
public class CounterView {

    @FXML
    private Lable valueLabel;

    @Inject
    private CounterStore store;

    public void initialize() {
        valueLabel.textProperty().bind(store.counterValue());
    }

    @FXML
    public void increaseButtonPressed () {
        Dispatcher.getInstance().dispatch(new IncreaseAction(1));
    }

}
Action
public class IncreaseAction {
    private final int amount;

    public IncreaseAction(int amount) {
        this.amount = amount;
    }

    public int getAmount() {
        return amount;
    }

    // equals & hashcode
}
Dispatcher
import org.reactfx.EventSource;
import org.reactfx.EventStream;

public class Dispatcher {
    private EventSource<Action> actionStream = new EventSource<>();

    public void dispatch(Action action) {
        actionStream.push(action);
    }

    public EventStream<Action> getActionStream() {
        return actionStream;
    }

}

Demo

All check のチェックボックス実装で役立つ……1つでもチェックが外れたら All のチェックを外す

マウスオーバー時だけ出る delete button はどう実装するか?

JavaFX では1行で書けてしまう。が、Functional Programming じゃない。

deleteButton.visibleProperty().bind(root.hoverProperty);

アイデア

類似するアイデア

  • CQRS(Command Query Responsibility Segmentation) + Event Sourcing
  • Redux

Virtual SceneGraph のようなもの?

JavaFX 標準には Virtual-DOM に当たる Virtual SceneGraph がないとのことで今回のような実装となったが、 templateFXというものがあるとのこと

関数指向の言語を使う?

  • Groovy
  • Kotlin
  • Frege

Links

  • TodoMvcFX
  • Example Code……今回実装した Flux Architecture の Framework(お試し実装)、 Reactive Streams には ReactFX を活用している

MVVM Pattern

JavaOne 2016 では JavaFX で MVVM(Model-View-ViewModel) パターンを実装する方法についてもセッションがありました(CON2592: Creating JavaFX Applications with mvvmFX)。こちらはすでに mvvmFX としてフレームワークが公開されています。新規の JavaFX プロジェクトで採用したいフレームワークを探している場合は、こちらを調査してみてもよいかもしれません。