Develop a multi-modules service
Each service in the Fluence network consists of at least one module, and there is a special configuration file that manages the service creation process. In particular, it controls the order in which the modules are instantiated, the permissions that each module will have, the maximum memory limit, and some other parameters. This section explains how to develop a simple multi-module service based on the url-downloader example.
Creating a url-downloader service
To demonstrate the capabilities of Wasm multi-modules in Marine, let us build a service that is able to download a site by its url and store it locally with a specified name. For demonstration purposes, this service will contain three modules: curl
for downloading files, local_storage
for saving files to the file system, and facade
as a facade module that combines two previous ones. And let us give the whole service the name url-downloader
.
Preparation
First, create an empty directory with the url-downloader
name (further, we will call it the service root directory) for our service and initialize three new modules:
sh
# create a root directory for service and go to itmkdir url-downloader & cd url-downloader# create three new Rust projects for modulescargo new curl-adapter --bincargo new local-storage --bincargo new facade --bin
sh
# create a root directory for service and go to itmkdir url-downloader & cd url-downloader# create three new Rust projects for modulescargo new curl-adapter --bincargo new local-storage --bincargo new facade --bin
Then add marine-rs-sdk = 0.7.1
to the dependencies
section of each Cargo.toml
file.
Curl module
The main purpose of this module is to simply provide a wrapper for the curl
CLI tool. This is done by importing the curl
function from the host (in the same way as described in mounted binaries section and exporting the function called get
.
Open the curl-adapter/src/main.rs
file in an editor and paste the code of the curl
module there:
rust
use marine_rs_sdk::marine;pub fn main() {}#[marine]pub fn download(url: String) -> String {curl(url)}#[marine]#[link(wasm_import_module = "host")]extern "C" {fn curl(url: String) -> String;}
rust
use marine_rs_sdk::marine;pub fn main() {}#[marine]pub fn download(url: String) -> String {curl(url)}#[marine]#[link(wasm_import_module = "host")]extern "C" {fn curl(url: String) -> String;}
Note that errors are not handled in these examples for simplicity.
Local-storage module
This module is intended for loading and storing files in the file system. It provides two exported functions: get
, which returns the content of a file by its name in the sites
directory, and put
, which saves a file in the same directory and gives it the specified name.
Open the local-storage/src/main.rs
file in an editor and paste the code of the local-storage
module there:
rust
use marine_rs_sdk::marine;use std::path::PathBuf;const SITES_DIR: &str = "/sites/";pub fn main() {}#[marine]pub fn put(name: String, file_content: Vec<u8>) {let rpc_tmp_filepath = format!("{}{}", SITES_DIR, name);let _ = std::fs::write(PathBuf::from(rpc_tmp_filepath.clone()), file_content);}#[marine]pub fn get(file_name: String) -> Vec<u8> {let tmp_filepath = format!("{}{}", SITES_DIR, file_name);fs::read(tmp_filepath).unwrap()}
rust
use marine_rs_sdk::marine;use std::path::PathBuf;const SITES_DIR: &str = "/sites/";pub fn main() {}#[marine]pub fn put(name: String, file_content: Vec<u8>) {let rpc_tmp_filepath = format!("{}{}", SITES_DIR, name);let _ = std::fs::write(PathBuf::from(rpc_tmp_filepath.clone()), file_content);}#[marine]pub fn get(file_name: String) -> Vec<u8> {let tmp_filepath = format!("{}{}", SITES_DIR, file_name);fs::read(tmp_filepath).unwrap()}
Facade module
The facade
module combines the logic of the previous modules in one exported function: get_n_save
. This function downloads the site with the specified name using the get
function from the curl_adapter
module and then saves it into a file on the file system using the put
function from the local-storage
module. To import functions from another module, their signatures must be declared in an extern block wrapped with the #[marine]
procedure macro.
Open the facade/src/main.rs
file in an editor and paste the code of the facade
module there:
rust
use marine_rs_sdk::marine;pub fn main() {}#[marine]fn get_n_save(url: String, file_name: String) -> String {let downloaded = download(url);file_put(file_name, downloaded.into_bytes());String::from("Ok")}#[marine]#[link(wasm_import_module = "curl_adapter")]extern "C" {pub fn download(url: String) -> String;}#[marine]#[link(wasm_import_module = "local_storage")]extern "C" {// this link_name attribute allows using "file_get" name// for function imported by "get" name#[link_name = "get"]pub fn file_get(file_name: String) -> Vec<u8>;#[link_name = "put"]pub fn file_put(name: String, file_content: Vec<u8>);}
rust
use marine_rs_sdk::marine;pub fn main() {}#[marine]fn get_n_save(url: String, file_name: String) -> String {let downloaded = download(url);file_put(file_name, downloaded.into_bytes());String::from("Ok")}#[marine]#[link(wasm_import_module = "curl_adapter")]extern "C" {pub fn download(url: String) -> String;}#[marine]#[link(wasm_import_module = "local_storage")]extern "C" {// this link_name attribute allows using "file_get" name// for function imported by "get" name#[link_name = "get"]pub fn file_get(file_name: String) -> Vec<u8>;#[link_name = "put"]pub fn file_put(name: String, file_content: Vec<u8>);}
Building all modules
Now the modules are ready to be built, so let us do it:
sh
# cd to the root service directory where all services located# compile the curl_adapter modulecd curl_adapter & marine build & cd ..# compile the local-storage modulecd local-storage & marine build & cd ..# compile the facade modulecd facade & marine build & cd ..
sh
# cd to the root service directory where all services located# compile the curl_adapter modulecd curl_adapter & marine build & cd ..# compile the local-storage modulecd local-storage & marine build & cd ..# compile the facade modulecd facade & marine build & cd ..
Url-downloader service config
To run a module with the Marine REPL, we should create a config file where some necessary module loading details will be described. For more details about the structure of this file please refer to this section.
The configuration of the url-downloader
service should look as follows:
toml
modules_dir = "artifacts/"[[module]]name = "local_storage"logger_enabled = true[module.wasi]preopened_files = ["./sites"]# this is an alias to a full path for the sites dirmapped_dirs = { "sites" = "./sites" }[[module]]name = "curl_adapter"logger_enabled = true[module.mounted_binaries]curl = "/usr/bin/curl"[[module]]name = "facade"logger_enabled = true
toml
modules_dir = "artifacts/"[[module]]name = "local_storage"logger_enabled = true[module.wasi]preopened_files = ["./sites"]# this is an alias to a full path for the sites dirmapped_dirs = { "sites" = "./sites" }[[module]]name = "curl_adapter"logger_enabled = true[module.mounted_binaries]curl = "/usr/bin/curl"[[module]]name = "facade"logger_enabled = true
The important things here are:
local_storage
is allowed to usesites
directory; it'll be used for storing downloaded filescurl_adapter
is allowed to use thecurl
binary in the OSfacade
module is actually the facade module for the service because it's the last module in the list
Save this config as the Config.toml
file in the service root directory.
Run url-downloader with REPL
Let's start with creating a directory called artifacts
in the root of the url-downloader
service and copying the three resulting .wasm
file into it:
sh
# create an artifacts foldermkdir artifacts# copy compiled services into itcp target/wasm32-wasi/debug/local_storage.wasm artifacts/cp target/wasm32-wasi/debug/curl.wasm artifacts/cp target/wasm32-wasi/debug/facade.wasm artifacts/
sh
# create an artifacts foldermkdir artifacts# copy compiled services into itcp target/wasm32-wasi/debug/local_storage.wasm artifacts/cp target/wasm32-wasi/debug/curl.wasm artifacts/cp target/wasm32-wasi/debug/facade.wasm artifacts/
Alternatively, you can find the ready-to-run url-downloader
service here.
Before running it, create a directory named sites
, which is required by the local-storage
module to store the downloaded data.
Then run the REPL with the Config.toml
file and examine the url-downloader
service interface:
sh
# create sites directorymkdir sites# start REPL with Config.tomlmrepl Config.toml
sh
# create sites directorymkdir sites# start REPL with Config.tomlmrepl Config.toml
sh
Welcome to the Marine REPL (version 0.16.0)Minimal supported versionssdk: 0.6.0interface-types: 0.20.0app service was created with service id = 732f4136-7542-4101-851e-511cc83e6ac0elapsed time 1.444318ms1> interfaceApplication service interface:local_storage:fn put(name: String, file_content: Array<U8>) -> Stringfn get(file_name: String) -> Array<U8>curl:fn get(cmd: String) -> Stringfacade:fn get_n_save(url: String, file_name: String) -> String
sh
Welcome to the Marine REPL (version 0.16.0)Minimal supported versionssdk: 0.6.0interface-types: 0.20.0app service was created with service id = 732f4136-7542-4101-851e-511cc83e6ac0elapsed time 1.444318ms1> interfaceApplication service interface:local_storage:fn put(name: String, file_content: Array<U8>) -> Stringfn get(file_name: String) -> Array<U8>curl:fn get(cmd: String) -> Stringfacade:fn get_n_save(url: String, file_name: String) -> String
There are three interface modules here. Note that the service interface is only represented by the facade
module. However, the REPL allows all loaded modules to be called for debugging purposes.
Let us download the google.com
page:
sh
2> call facade get_n_save ["google.com", "google"]INFO: Running "/usr/bin/curl google.com" ...% Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed100 219 100 219 0 0 2105 0 --:--:-- --:--:-- --:--:-- 2105result: String("Ok")elapsed time: 111.1222ms
sh
2> call facade get_n_save ["google.com", "google"]INFO: Running "/usr/bin/curl google.com" ...% Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed100 219 100 219 0 0 2105 0 --:--:-- --:--:-- --:--:-- 2105result: String("Ok")elapsed time: 111.1222ms
The downloaded page can be found at sites/google
. As already mentioned, each module within a service can be called directly, for example, local_storage
can be treated as a separate module:
sh
3> call local_storage put ["array", [1,2,3]]result: String("Ok")elapsed time: 322.108µs4> call local_storage get "array"result: Array([Number(1), Number(2), Number(3)])elapsed time: 194.216µs
sh
3> call local_storage put ["array", [1,2,3]]result: String("Ok")elapsed time: 322.108µs4> call local_storage get "array"result: Array([Number(1), Number(2), Number(3)])elapsed time: 194.216µs