7.6 KiB
Service API and Higher-Level Abstractions
This document covers the Service API and other higher-level abstractions built on top of the core NATS client.
Service API
Location: service/ (feature: service)
The Service API provides a framework for building NATS-based microservices with built-in monitoring, health checks, and statistics.
Service
#[derive(Debug)]
pub struct Service {
client: Client,
info: Info,
endpoints: HashMap<String, Endpoint>,
started: DateTime,
stats_handler: Arc<dyn Fn(&str, &Stats) -> serde_json::Value + Send + Sync>,
stop_sender: mpsc::Sender<()>,
stop_receiver: Option<mpsc::Receiver<()>>,
}
Creating a Service
use async_nats::service::ServiceExt;
let mut service = client
.service_builder()
.description("Product service")
.stats_handler(|endpoint, stats| {
serde_json::json!({
"endpoint": endpoint,
"requests": stats.num_requests,
"errors": stats.num_errors,
})
})
.start("products", "1.0.0")
.await?;
ServiceBuilder
impl ServiceBuilder {
pub fn description(mut self, description: impl Into<String>) -> Self
pub fn stats_handler<F>(mut self, handler: F) -> Self
pub async fn start(self, name: impl Into<String>, version: impl Into<String>) -> Result<Service, ServiceError>
}
Endpoints
A service exposes one or more endpoints, each handling requests on a specific subject:
// Add an endpoint
let mut endpoint = service
.endpoint("get_product")
.await?;
// Process requests
while let Some(request) = endpoint.next().await {
let request = request?;
// Handle the request
request.respond(serde_json::json!({ "id": 1, "name": "Widget" })).await?;
}
Endpoint
Location: service/endpoint.rs
pub struct Endpoint {
subject: Subject,
queue_group: Option<String>,
info: EndpointInfo,
stats: Stats,
subscriber: Subscriber,
}
Implements futures::Stream yielding ServiceRequest objects.
ServiceRequest
pub struct ServiceRequest {
pub subject: Subject,
pub payload: Bytes,
pub headers: Option<HeaderMap>,
pub reply: Option<Subject>,
pub client: Client,
}
Methods:
respond(payload)— send a response to the requesterrespond_with_headers(payload, headers)— send a response with headers
Monitoring Subjects
The Service API automatically creates monitoring endpoints:
| Subject | Description |
|---|---|
$SRV.PING |
Ping all services (returns service info) |
$SRV.PING.<name> |
Ping specific service by name |
$SRV.PING.<name>.<id> |
Ping specific service instance |
$SRV.INFO |
Get service info |
$SRV.STATS |
Get service statistics |
Service Info
pub struct Info {
pub name: String,
pub id: String,
pub version: String,
pub description: String,
pub endpoints: Vec<EndpointInfo>,
}
Stats
pub struct Stats {
pub num_requests: u64,
pub num_errors: u64,
pub last_error: Option<String>,
pub processing_time: Duration,
pub average_processing_time: Duration,
}
ID Generation
Location: id_generator.rs
The client needs unique IDs for inbox subjects and other purposes.
With nuid Feature (Default)
Uses the NUID library for high-performance, cryptographically strong, collision-resistant IDs:
pub(crate) fn next() -> String {
nuid::next().to_string()
}
NUID generates 22-character alphanumeric strings using a combination of a random prefix and a sequential counter.
Without nuid Feature
Falls back to rand-based generation:
pub(crate) fn next() -> String {
rng()
.sample_iter(Alphanumeric)
.take(22)
.map(char::from)
.collect()
}
Both approaches produce 22-character alphanumeric strings, but NUID is more performant and has better collision resistance.
Inbox Generation
The Client::new_inbox() method generates globally unique inbox subjects for request-reply:
pub fn new_inbox(&self) -> String {
format!("{}.{}", self.inbox_prefix, crate::id_generator::next())
}
Default prefix is _INBOX, producing subjects like _INBOX.UaBG3f3q5NxX3KdNcRmF2f.
Custom prefix via ConnectOptions::custom_inbox_prefix():
let client = ConnectOptions::new()
.custom_inbox_prefix("MYAPP")
.connect("demo.nats.io")
.await?;
// Inbox subjects: MYAPP.UaBG3f3q5KdNcRmF2f
DateTime Helpers
Location: datetime.rs (feature: jetstream or service or chrono)
Provides date/time types for JetStream and Service API timestamps:
- Uses the
timecrate by default - Optionally uses
chronovia thechronofeature flag - Supports RFC 3339 formatting and parsing
DateTimetype wraps eithertime::OffsetDateTimeorchrono::DateTime<Utc>
Crypto Module
Location: crypto.rs (feature: crypto)
Provides encryption/decryption support used by the Object Store for server-side encryption.
Subject Validation
Location: lib.rs
The client provides two levels of subject validation:
is_valid_publish_subject
pub(crate) fn is_valid_publish_subject<T: AsRef<str>>(subject: T) -> bool
Checks for protocol safety only:
- Not empty
- No whitespace (space, tab, CR, LF) which would break protocol framing
Used for publish operations. Can be disabled with skip_subject_validation.
is_valid_subject
pub(crate) fn is_valid_subject<T: AsRef<str>>(subject: T) -> bool
Checks structural validity:
- Not empty
- No leading/trailing dots
- No consecutive dots (
..) - No whitespace
Used for subscribe operations (always runs, matching Go/Java behavior).
is_valid_queue_group
pub(crate) fn is_valid_queue_group(queue_group: &str) -> bool
Checks:
- Not empty
- No whitespace
JetStream Name Validation
Location: jetstream/mod.rs
pub(crate) fn is_valid_name(name: &str) -> bool {
!name.is_empty()
&& name.bytes().all(|c| !c.is_ascii_whitespace() && c != b'.' && c != b'*' && c != b'>')
}
JetStream names (stream names, consumer names) must not contain:
- Whitespace
- Dots (
.) — would conflict with subject delimiters - Wildcards (
*,>) — would conflict with subject wildcards
CallbackArg1
Location: options.rs
A type-erased async callback wrapper used throughout the crate:
pub(crate) type AsyncCallbackArg1<A, T> =
Arc<dyn Fn(A) -> Pin<Box<dyn Future<Output = T> + Send + Sync + 'static>> + Send + Sync>;
#[derive(Clone)]
pub(crate) struct CallbackArg1<A, T>(AsyncCallbackArg1<A, T>);
impl<A, T> CallbackArg1<A, T> {
pub(crate) async fn call(&self, arg: A) -> T {
(self.0.as_ref())(arg).await
}
}
Used for:
event_callback—CallbackArg1<Event, ()>auth_callback—CallbackArg1<Vec<u8>, Result<Auth, AuthError>>reconnect_to_server_callback—CallbackArg1<(Vec<Server>, ServerInfo), Option<ReconnectToServer>>signature_callback—CallbackArg1<String, Result<String, AuthError>>
Version Compatibility Checking
The Client::is_server_compatible method checks if the server version meets a minimum requirement:
pub fn is_server_compatible(&self, major: i64, minor: i64, patch: i64) -> bool
This parses the server version string from ServerInfo::version using a regex and compares major/minor/patch components. Note: this checks the directly-connected server, not necessarily the JetStream leader.
The server_2_10, server_2_11, server_2_12, and server_2_14 feature flags enable version-specific API fields and methods without runtime checks.