1. Your first Process
In this first chapter you run a Process that just adds 1 to its argument and returns it. The code you write is a single short function, but juiz loads it and turns it into a building block you can call over the network.
Prepare the samples
Section titled “Prepare the samples”Clone and build the example repository. All later commands are run from the root of the cloned repository.
git clone https://github.com/OpenJUIZ/juiz_examples_rust.gitcd juiz_examples_rustcargo buildEach example is built as a shared library in target/debug.
git clone https://github.com/OpenJUIZ/juiz_examples_python.gitcd juiz_examples_pythonpython3 -m venv .venvsource .venv/bin/activate # on Windows: .venv\Scripts\activatepip install -r requirements.txt # installs juiz-clirequirements.txt lists juiz-cli, which installs the juiz command and pyjuiz into the venv. No Rust toolchain is needed.
Python needs no build step either. The juiz command loads the Python files directly.
Run the following steps in a terminal where this venv is activated.
git clone https://github.com/OpenJUIZ/juiz_examples_cpp.gitcd juiz_examples_cppcmake -S . -B buildcmake --build buildCMake fetches the C++ headers and libjuiz_sdk from the juiz repository and builds them. The first build takes a while.
Each example is built as a shared library in build/plugins.
Read the Process code
Section titled “Read the Process code”This Process adds 1 to the argument arg1 and returns it.
process/increment_process/src/lib.rs
use juiz_sdk::prelude::*;
#[juiz_process]fn increment_process(arg1: i64) -> JuizResult<Capsule> { log::trace!("increment_process({:?}) called", arg1); return Ok(jvalue!(arg1 + 1).into());}A function marked with #[juiz_process] becomes a Process. The function name increment_process is the Process’s type name, and the parameter name arg1 becomes the argument name as is.
The return value is created with jvalue! and converted into a Capsule.
The crate’s Cargo.toml builds it as a shared library (crate-type = ["cdylib"]).
process/increment_process_python.py
from juiz import juiz_process
@juiz_processdef increment_process_python(arg1: int = 0) -> int: """Adds 1 to the argument and returns it""" print(f"increment_process_python: {arg1} -> {arg1 + 1}") return arg1 + 1A function decorated with @juiz_process becomes a Process. The function name increment_process_python is the Process’s type name.
juiz reads the argument names, types and defaults from the type hints and default values, and the output type from the return type hint (-> int). The return type hint is required.
If the file name matches the function name, juiz can find the function from the file name.
process/increment_process_cpp/increment_process_cpp.cpp
#include "juiz/juiz.h"
// A Process that adds 1 to the argument arg1 and returns it//// manifest() declares the Process name and its arguments (name, description, default),// and PROCESS_FACTORY exports it as a function juiz can call.auto manifest() { return ProcessManifest{"increment_process_cpp"} .description("Adds 1 to arg1.") .add_int_arg("arg1", "value to be incremented", 1);}
std::optional<int64_t> increment_process_cpp(int64_t arg1) { return arg1 + 1;}
PROCESS_FACTORY(manifest, increment_process_cpp);In C++ you write the Process’s type name and its arguments (name, description, default) yourself in manifest().
The function receives the arguments in the order they are declared in manifest(). Returning std::nullopt when there is no value results in an error.
Finally, PROCESS_FACTORY exports the function so juiz can load it.
In every language, a Process is just a function that takes arguments and returns a result. It does not read or write any outside state, so the same arguments always give the same result (it is idempotent). Thanks to this, a Process is easy to test, and its behavior does not change when you later connect it to others or move it to another machine.
Run it
Section titled “Run it”Start juiz with the file given by --process.
juiz --process ./target/debug/libincrement_process.so -e -1 -dOn macOS the extension is .dylib (./target/debug/libincrement_process.dylib).
juiz --process ./process/increment_process_python.py -l python -e -1 -d-e runs it once, so you can see it was called with the default arg1 = 0.
increment_process_python: 0 -> 1juiz --process ./build/plugins/libincrement_process_cpp.so -l cpp -e -1 -dOn macOS the extension is .dylib.
The options mean the following.
| Option | Meaning |
|---|---|
--process <path> |
Load a Process file |
-l <language> |
Language of the file (rust / python / cpp, default rust) |
-1 |
Create one instance from the loaded Process |
-e |
Run the created instance once with its default arguments |
-d |
Start the HTTP broker on port 8000 and keep running (exit with Ctrl+C) |
Notice that “loading” and “creating an instance” are separate steps.
When juiz loads a file, it learns the Process’s type (how to make it). With -1, it creates one instance from that type.
The instance name is the type name followed by 0 (for example increment_process0).
You can also create several instances with different names from the same type (covered in chapter 3).
Call it from outside
Section titled “Call it from outside”Leave juiz running, open another terminal, and call the Process.
First, list the running Processes.
juiz process list# => ["http://127.0.0.1:8000/process/increment_process0::increment_process"]juiz process list# => ["http://127.0.0.1:8000/process/increment_process_python0::increment_process_python"]juiz process list# => ["http://127.0.0.1:8000/process/increment_process_cpp0::increment_process_cpp"]The URL shown is this Process’s ID.
http://127.0.0.1:8000 is where the Process lives (its broker), and the part after process/ means “instance name::type name”.
Call the Process with this ID and its arguments (call). Arguments are passed as JSON.
juiz process call "http://127.0.0.1:8000/process/increment_process0::increment_process" '{"arg1": 3}' -p# => CapsulePtr((value:Value(4), option: []))juiz process call "http://127.0.0.1:8000/process/increment_process_python0::increment_process_python" '{"arg1": 3}' -p# => CapsulePtr((value:Value(4), option: []))The terminal running juiz prints increment_process_python: 3 -> 4.
juiz process call "http://127.0.0.1:8000/process/increment_process_cpp0::increment_process_cpp" '{"arg1": 3}' -p# => CapsulePtr((value:Value(4), option: []))You passed 3 and got 4 back. -p prints the result.
Try calling it several times to confirm that the same arguments always give the same result.
Call it from a browser
Section titled “Call it from a browser”The HTTP broker started with -d also serves Swagger UI. Open this URL in your browser.
http://127.0.0.1:8000/docs/Open PATCH /api/process/call in the list, press “Try it out”, enter the following, and press “Execute” to get the result.
- identifier:
core://core/process/increment_process0::increment_process - Request body:
{"arg1": 3}
- identifier:
core://core/process/increment_process_python0::increment_process_python - Request body:
{"arg1": 3}
- identifier:
core://core/process/increment_process_cpp0::increment_process_cpp - Request body:
{"arg1": 3}
Both the juiz command and Swagger UI simply call the same API over HTTP.
In other words, any program that speaks HTTP, in any language, can call a Process and get the result back.
This is call, the way of using juiz like a server and client.
Try it yourself
Section titled “Try it yourself”Change what the Process does and run it again.
For example, make it return double the argument instead of adding 1, rebuild it (Python needs no rebuild), and restart juiz. Calling it with {"arg1": 3} should now return 6.
The example repository also contains a Process that subtracts 1 (decrement_process). You can run it the same way.
Summary
Section titled “Summary”- A Process is an idempotent function that just takes arguments and returns a result. You can write it in Rust, Python or C++.
- juiz loads a file to learn a Process type, then creates named instances from it.
- A running Process has an ID shaped like a URL, and you can call it from outside with that ID and arguments (call).
In the next chapter you learn how to write a building block that keeps state, using a counter as the example. Move on to Keeping state: Container and ContainerProcess.