Nats (Message Bus / PubSub)
NATS is a Message Bus which can optionally be enabled in Iterapp. It makes communicating between your pods easier. More importantly it supports pub/sub, which means that one of your pods can broadcast a message to all of your other pods. This is essential for real-time-updates, like for collaboration or chat. For instance this is what makes collaborative editing possible in Icecalc, and powers the chat in Anywhere.
When enabled, your app get its own nats-account for each of its environments. Since a nats-account also functions as a namespace, apps cannot communicate with each other, and cannot communicate between environments.
This means that when for instance broadcast anything to a subject called test
,
only subscribers in pods for the same app and same environment will receive the
broadcast.
Nats is at most once delivery. That is, it gives no guarantees of message delivery. In most cases the message will be delivered, but for instance if your pod was reconnecting to NATS the second the package was sent, it will not receive it. If you need to have guarantees, you can do that using a number of ways. For instance sending acks when the package was received, or using sequence numbers to discover missed packages.
In the future, we might want to add JetStream to the NATS-cluster in Iterapp. JetStream will give at least once delivery. However right now that technology is a bit too immature.
For a basic example of a iterapp-app with nats, take a look at iterate/example-nats
Enabling
Add the following to your iterapp.toml
[nats]
When deploying your app, an account will be created for you, and the app will get the environment-variables needed to connect.
Environment Variables
These are the env-variables your app will receive.
Name | Description |
---|---|
NATS_URL | URL used to connect to the nats-server |
NATS_CREDENTIALS | The credentials required to connect to the nats-server |
NATS_CREDENTIALS_FILE | Path of a file with the same content as in NATS_CREDENTIALS |
NATS_CA_FILE | Path to a file with the CA-certificate used to sign the TLS-certificate used by NATS |
NATS is using a self-signed certificate, so you'll need to add the CA to the certificate store when connection, if not you'll get an error.
Running locally
When developing an application that uses NATS, you should have something runninc locally that NATS can connect to. You don't need to configure anything, just start a local nats-instance.
Using docker
> docker run -p 4222:4222 -ti nats:latest
Using homebrew
> brew install nats-server
> nats-server
Downloading a release
Download the latest release from the release-page for NATS.
Run it without any configuration
nats-server
Build from source
> GO111MODULE=on go get github.com/nats-io/nats-server/v2
> nats-server
Connecting to NATS
Here is examples for how to connect to the terapp NATS server in different languages
NATS has clients in most languages, and it should be relatively straightforward to take the ideas from here and write in other languages, but please if you do, update this documentation.
NODE.js (Javascript)
Note that we only use credentials if they are defined. This means that when developing locally you can connect to a local nats server that does not require authentication, nor SSL, which is the default nats-server config.
(If someone converts this to typescript, please add the example here :) )
const { connect, StringCodec, credsAuthenticator } = require('nats');
let authenticator;
// NATS_CREDENTIALS is undefined in development, but defined in production
if (process.env.NATS_CREDENTIALS) {
authenticator = credsAuthenticator(
new TextEncoder().encode(process.env.NATS_CREDENTIALS)
);
}
const nc = await connect({
servers: process.env.NATS_URL,
// NATS_CA_FILE is undefined in development, but defined in production
tls: process.env.NATS_CA_FILE && { caFile: process.env.NATS_CA_FILE },
authenticator,
});
Rust
Note that we only use credentials if they are defined. This means that when developing locally you can connect to a local nats server that does not require authentication, nor SSL, which is the default nats-server config.
This uses the async nats, it should be relatively straightforward to convert to using the sync nats packages, since the API is similar.
#![allow(unused)] fn main() { // We use anyhow for easier error-management use anyhow::Result; use async_nats::Connection; pub async fn create_connection() -> Connection { loop { match try_create_connection().await { Ok(conn) => return conn, Err(err) => { println!("Error connecting to nats: {}. Retrying...", err); tokio::time::sleep(Duration::from_secs(2)).await; } } } } pub async fn try_create_connection() -> Result<Connection> { let nats_system_account_cred_file = std::env::var("NATS_CREDENTIALS_FILE").ok(); let nats_ca_file = std::env::var("NATS_CA_FILE").ok(); let nats_url = env_or_die("NATS_URL")?; let mut options = match nats_system_account_cred_file { Some(cred_file) => async_nats::Options::with_credentials(&cred_file), None => async_nats::Options::new(), }; if let Some(ca_root) = nats_ca_file { options = options.add_root_certificate(&ca_root); } let nc = options.max_reconnects(None).connect(&nats_url).await?; Ok(nc) } }