Introduction
GGCommons (Greengrass Commons) is one SDK that gives every AWS IoT Greengrass v2 component the cross-cutting plumbing it would otherwise rewrite by hand — configuration, messaging, metrics, heartbeat, and logging — plus opt-in credentials, parameters, and telemetry streaming. You wire it up once at startup and write only business logic.
The unusual part: GGCommons is implemented four times, in Java, Python, Rust, and TypeScript, as deliberate mirrors of one another. The same config schema, the same CLI contract, the same subsystem boundaries, and the same on-wire message envelope apply to all four. Java is the canonical reference; the other three track it.
What’s in the box
Section titled “What’s in the box”Every component gets these subsystems out of the box — they are always present:
- config — loads the component config from one of several sources (
FILE,ENV,GG_CONFIG,SHADOW,CONFIG_COMPONENT,CONFIGMAP), validates it against the canonical JSON schema, substitutes template variables like{ComponentName}and{ThingName}, and supports hot reload. - messaging — one interface over two transports (Greengrass IPC and dual-MQTT). Connections and subscriptions block until confirmed; request/reply uses correlation IDs; the message envelope is identical across all four languages.
- metrics — pluggable targets: CloudWatch EMF, a cloudwatch component, the messaging bus, a local rotated log file, or a pull-based Prometheus endpoint.
- heartbeat — periodic system metrics (CPU, memory, disk, threads, file descriptors) emitted through the metric or messaging subsystem.
- logging — console logging plus optional size-rotated file logging, with a per-language
format token (
java_format,python_format,rust_format,ts_format).
Three more subsystems are opt-in — their accessor returns nothing unless the matching config section is present:
- credentials — an encrypted local vault (envelope encryption) with optional central sync from AWS Secrets Manager.
- parameters — offline-first externalized config from env, a mounted directory, or AWS SSM, reusing the credentials vault as an encrypted cache.
- streaming — high-rate telemetry streaming with a durable (or in-memory) store-and-forward buffer that drains to Kinesis or Kafka.
Platform × transport, at a glance
Section titled “Platform × transport, at a glance”A component runs on a platform and talks over a transport. These are two orthogonal axes,
chosen at startup with --platform and --transport:
| Platform | What it is | Default transport | Default config source |
|---|---|---|---|
GREENGRASS |
On-device, Nucleus-managed | IPC |
GG_CONFIG |
HOST |
Docker or a bare host | MQTT (dual: local broker + AWS IoT Core) |
FILE |
KUBERNETES |
A pod in a cluster | MQTT |
CONFIGMAP |
The transport is one of IPC (Greengrass IPC, valid only on GREENGRASS) or MQTT
(the dual-MQTT provider that connects to a local broker and, optionally, AWS IoT Core).
By default --platform is auto: the library detects the platform from the environment
(Greengrass Nucleus signals first, then a Kubernetes service-account token, then a HOST
fallback) and derives the transport, config source, and other defaults from the resolved platform
profile. An explicit --platform or --transport always wins. See the
Quickstart for runnable command lines.
The mental model
Section titled “The mental model”Working with GGCommons is the same three steps in every language:
- Build the runtime once. A fluent builder (
GGCommonsBuilder/GgCommonsBuilder) takes your component name and the process arguments and returns a single runtime object. That object parses the CLI, resolves the platform profile, loads and validates config, and wires up every subsystem. - Reach subsystems through accessors. Hold the runtime for your component’s lifetime and ask it for what you need — messaging, metrics, config, and the opt-ins. Each accessor returns the already-wired service.
- Let the library own the lifecycle. GGCommons installs its own signal handling and tears subsystems down on shutdown — you do not register your own signal handler.
A first bootstrap
Section titled “A first bootstrap”The shape is the same everywhere — only the naming conventions differ. Note especially how each language passes process arguments and how each one shuts down.
import com.mbreissi.ggcommons.GGCommons;import com.mbreissi.ggcommons.GGCommonsBuilder;
public static void main(String[] args) { // Build the runtime from the standard CLI args (use the FULL component name). GGCommons gg = GGCommonsBuilder.create("com.mbreissi.greengrass.JavaComponentSkeleton") .withArgs(args) .build();
var config = gg.getConfigManager(); var messaging = gg.getMessaging(); var metrics = gg.getMetrics();
// ... business logic ... // The library wired SIGTERM/SIGINT to a graceful, idempotent shutdown() — do not add your own.}import sysfrom ggcommons.ggcommons_builder import GGCommonsBuilder
gg = GGCommonsBuilder.create("com.example.python.Component") \ .with_args(sys.argv[1:]) \ .build()
config = gg.get_config_manager()messaging = gg.get_messaging() # the MessagingClient class (its operations are static)metrics = gg.get_metrics()
# ... business logic ...gg.shutdown() # or: with GGCommonsBuilder.create(...).build() as gg:use ggcommons::prelude::*;
#[tokio::main]async fn main() -> anyhow::Result<()> { let gg = GgCommonsBuilder::new("com.example.SkeletonComponent") .args(std::env::args_os()) // argv INCLUDES the program name .build() .await?;
let cfg = gg.config(); let metrics = gg.metrics(); if let Ok(messaging) = gg.messaging() { // returns a Result, not an Option let _ = messaging; }
// ... business logic ... Ok(()) // dropping `gg` here releases every resource (RAII) — there is no close()}import { GGCommonsBuilder } from "@mbreissi/ggcommons";
const gg = await new GGCommonsBuilder("com.example.Component") .args(process.argv.slice(2)) // argv WITHOUT the node/script prefix .build();
const config = gg.config();const messaging = gg.messaging(); // throws if no transport is wiredconst metrics = gg.metrics();
// ... business logic ...await gg.close(); // idempotentHow four-way parity works
Section titled “How four-way parity works”The four SDKs stay aligned because they share their contracts, not their code:
- One config schema. The JSON schema lives only at
schema/ggcommons-config-schema.jsonand is synced into each library; CI fails on drift. Every library validates fail-closed against its embedded copy. - One CLI contract. The same flags, defaults, platform profiles, and auto-detection rules are implemented by a shared resolver design in each language.
- One message envelope. The on-wire format is identical, so a component in any language can talk to a component in any other.
- Java is canonical. When public behavior changes, it changes in Java first; the other three
mirror it. Differences that remain are idiomatic (naming,
Optionvsnull, RAII vs explicit shutdown), never behavioral.
This is why the snippets above look like the same program written four ways — that is exactly what they are.