コンテンツにスキップ

2. 状態を持つ: Container と ContainerProcess

第 1 章の Process は、同じ引数を渡せばいつも同じ結果を返す冪等な関数でした。 しかし、ロボットや IoT の部品を作っていると、カウンタの値やモータの現在位置、カメラの接続のように「覚えておきたいもの」が必ず出てきます。 この章では、整数を 1 つ覚えておくカウンタを題材に、juiz で状態を扱う方法を学びます。

第 1 章でクローンしてビルドしたサンプル集をそのまま使います。Container のサンプルも、第 1 章のビルドで一緒にできあがっています。 まだの場合は、第 1 章の「サンプルを用意する」 の手順を済ませてください。

この章で使うファイルは次の 3 つです。

役割 名前
Container カウンタの値を 1 つ持つ
ContainerProcess increment Container の値に arg1 を足して、足した後の値を返す
ContainerProcess get Container の値をそのまま返す

Container は、状態そのものです。値を入れておく入れ物と、それを作る関数だけを書きます。

container/example_container/src/lib.rs

use juiz_sdk::prelude::*;
#[repr(Rust)]
pub struct ExampleContainer {
pub value: i64,
}
#[juiz_container]
fn example_container(initial_value: i64) -> JuizResult<Box<ExampleContainer>> {
Ok(Box::new(ExampleContainer {
value: initial_value,
}))
}

状態を入れておく構造体 ExampleContainer と、それを作る関数を書きます。 #[juiz_container] を付けた関数が Container の作り方になり、関数名 example_container が Container の型名になります。

ContainerProcess も ExampleContainer の定義を使うので、このクレートは Cargo.toml で crate-type = ["cdylib", "rlib"] として、共有ライブラリとしても Rust のライブラリとしても使えるようにしています。

Container には、値を変えたり読んだりする処理は書いていません。Container は「何を覚えておくか」だけを表します。

状態を変えたり読んだりする処理は、ContainerProcess に書きます。 ContainerProcess は、最初の引数に Container を受け取る Process です。どの Container と組になるかは、Container の型名で指定します。

container/example_container_increment/src/lib.rs

use example_container::ExampleContainer;
use juiz_sdk::prelude::*;
#[juiz_container_process(
container_type = "example_container"
description = "Container Process for example_container. This process will add given value to container."
arguments = {
default = {
arg1 = 1
}
}
)]
fn example_container_increment(
container: &mut ContainerImpl<ExampleContainer>,
arg1: i64,
) -> JuizResult<Capsule> {
container.value = container.value + arg1;
return Ok(jvalue!(container.value).into());
}

#[juiz_container_process] の container_type に、結びつける Container の型名 example_container を書きます。 最初の引数 container で Container を受け取り、container.value のように中の値を読み書きできます。2 番目以降の引数(arg1)は、第 1 章の Process と同じように呼び出すときに渡します。

container/example_container_get/src/lib.rs

use example_container::ExampleContainer;
use juiz_sdk::prelude::*;
#[juiz_container_process(container_type = "example_container")]
fn example_container_get(container: &mut ContainerImpl<ExampleContainer>) -> JuizResult<Capsule> {
println!("example_container_get() called");
return Ok(jvalue!(container.value).into());
}

get は Container 以外の引数を持たず、値を返すだけです。

こうして、状態(Container)と振る舞い(ContainerProcess)を別々に書きます。 状態を変えたり読んだりできるのは、その Container に結びついた ContainerProcess だけです。 「この値はどこで書き換わるのか」を知りたいときは、ContainerProcess を見ればよいことになります。

まずは第 1 章と同じように、ファイルを指定して juiz を起動します。 Container は --container、ContainerProcess は --container_process で読み込みます。

ターミナルウィンドウ
juiz --container ./target/debug/libexample_container.so \
--container_process ./target/debug/libexample_container_increment.so -1 -d

macOS では拡張子が .dylib になります。

-1 を付けると、読み込んだ Container の型からインスタンスを 1 つ作り、そこに ContainerProcess のインスタンスを 1 つ結びつけます。 インスタンス名は第 1 章と同じく型名の後ろに 0 を付けた名前で、Container は example_container0、ContainerProcess は example_container_increment0 になります(Python と C++ では型名に _python / _cpp が付きます)。 Container は、引数の既定値どおり値 0 から始まります。

juiz を起動したまま、別のターミナルを開きます。まず、動いている ContainerProcess の一覧を見ます。

ターミナルウィンドウ
juiz container-process list
# => ["core://core/container_process/example_container_increment0::example_container_increment:example_container0"]

ContainerProcess の ID は「インスタンス名::型名:Container のインスタンス名」の形をしていて、どの Container に結びついているかが ID からわかります。 一覧には、ブローカー自身の中を指す core://core/... の形で表示されます。外から呼び出すときは、第 1 章と同じように先頭を http://127.0.0.1:8000 に置き換えた ID を使います。

increment を、同じ引数で 2 回呼び出してみましょう。

ターミナルウィンドウ
juiz container-process call "http://127.0.0.1:8000/container_process/example_container_increment0::example_container_increment:example_container0" '{"arg1": 5}' -p
# => CapsulePtr((value:Value(5), option: []))
juiz container-process call "http://127.0.0.1:8000/container_process/example_container_increment0::example_container_increment:example_container0" '{"arg1": 5}' -p
# => CapsulePtr((value:Value(10), option: []))

同じ {"arg1": 5} を渡しているのに、1 回目と 2 回目で結果が違います。 第 1 章の Process と違って、ContainerProcess の結果は引数だけでなく Container の状態にも左右されるからです。

2 つの ContainerProcess で Container を共有する

Section titled “2 つの ContainerProcess で Container を共有する”

次は、increment が変えた値を get で読んでみます。 コマンドラインからは ContainerProcess を 1 つしか読み込めないので、「どの Container に、どの ContainerProcess を結びつけるか」を書いた設定ファイル example_container.conf を使います。 起動中の juiz を Ctrl+C で止めてから、設定ファイルを指定して起動し直します。

ターミナルウィンドウ
juiz -f example_container.conf -d

設定ファイルには、次のことが書かれています。

  • Container の型と ContainerProcess の型を、どのファイルから読み込むか
  • Container のインスタンス container0 を 1 つ作ること
  • container0 に結びつけて、increment のインスタンス increment0 と、get のインスタンス get0 を作ること

設定ファイルの書き方は、次の第 3 章で詳しく扱います。ここでは「1 つの Container に、2 つの ContainerProcess がぶら下がっている」という形だけ押さえておいてください。

一覧を見ると、今度は ContainerProcess が 2 つあります。

ターミナルウィンドウ
juiz container-process list
# => ["core://core/container_process/increment0::example_container_increment:container0", "core://core/container_process/get0::example_container_get:container0"]

表示される順番は実行ごとに変わることがあります。

get で今の値を見てから、increment を 2 回呼び出し、もう一度 get で値を見てみましょう。

ターミナルウィンドウ
juiz container-process call "http://127.0.0.1:8000/container_process/get0::example_container_get:container0" '{}' -p
# => CapsulePtr((value:Value(0), option: []))
juiz container-process call "http://127.0.0.1:8000/container_process/increment0::example_container_increment:container0" '{"arg1": 5}' -p
# => CapsulePtr((value:Value(5), option: []))
juiz container-process call "http://127.0.0.1:8000/container_process/increment0::example_container_increment:container0" '{"arg1": 5}' -p
# => CapsulePtr((value:Value(10), option: []))
juiz container-process call "http://127.0.0.1:8000/container_process/get0::example_container_get:container0" '{}' -p
# => CapsulePtr((value:Value(10), option: []))

increment が変えた値を、別の ContainerProcess である get が読めています。2 つの ContainerProcess は、同じ Container を共有しています。

第 1 章と同じく、Swagger UI(http://127.0.0.1:8000/docs/)からも呼び出せます。 ContainerProcess を呼び出すときは、PATCH /api/container_process/call を開いて、identifier に juiz container-process list で表示された core://core/... の ID をそのまま入力します。

  • identifier: core://core/container_process/get0::example_container_get:container0
  • Request body: {}
  • increment に負の数({"arg1": -3} など)を渡して、値が減ることを確かめてみましょう。
  • juiz を Ctrl+C で止めてから起動し直し、get を呼び出してみましょう。値は最初に戻っています。状態は、動いている Container の中にだけあることがわかります。
  • 値を 0 に戻す ContainerProcess(reset)を書くとしたら、どんな関数になるか考えてみましょう。引数は Container だけで、value を 0 にして返せば十分です。動かすには設定ファイルに書き足す必要があるので、第 3 章を読んでから試してみてください。
  • 状態は Container に置きます。Container には「何を覚えておくか」だけを書き、処理は書きません。
  • 状態を読み書きするのは、Container に結びついた ContainerProcess だけです。ContainerProcess は最初の引数で Container を受け取ります。
  • 1 つの Container に、役割の違う ContainerProcess をいくつも結びつけられます。それらは同じ状態を共有します。
  • ContainerProcess の結果は、引数だけでなく Container の状態にも左右されます。冪等な Process と、状態を扱う ContainerProcess を分けておくことで、状態がどこで変わるのかがはっきりします。

次の章では、この章で使った設定ファイルの書き方を学び、第 1 章の Process とこの章の Container を 1 つの設定ファイルでまとめて起動します。設定ファイルでまとめて起動する に進みましょう。