Entry point & builder
The GGCommons object is your component’s runtime. You build it once at startup from the
standard CLI args, and it wires every subsystem — config, messaging, metrics, heartbeat, health, and
the opt-in credentials/parameters/streaming — behind one handle that you hold for the component’s
lifetime. You reach each subsystem through a typed accessor, and you tear the runtime down with a
language-appropriate lifecycle mechanism.
The runtime is always constructed through a fluent builder. Rust and TypeScript expose only the
builder; Python additionally keeps a public constructor; Java additionally keeps deprecated direct
constructors for back-compat (see Deprecated Java constructors).
There is no init() facade in any language.
The builder
Section titled “The builder”The builder takes the component name, the raw CLI args, optional app-specific CLI options, and an
optional receiveOwnMessages flag, then produces the runtime.
static GGCommonsBuilder create(String componentName);GGCommonsBuilder withArgs(String[] args); // already-stripped app args (argv minus program)GGCommonsBuilder withAppOptions(org.apache.commons.cli.Options appOptions);GGCommonsBuilder receiveOwnMessages(boolean receiveOwnMessages); // default trueGGCommons build(); // throws IllegalStateException if componentName is nulldef __init__(self, component_name: str) # raises ValueError if empty@staticmethoddef create(component_name: str) -> "GGCommonsBuilder"def with_args(self, args: list[str]) -> "GGCommonsBuilder" # raises ValueError if Nonedef with_app_options(self, app_options: argparse.ArgumentParser) -> "GGCommonsBuilder"def receive_own_messages(self, receive_own_messages: bool) -> "GGCommonsBuilder" # default Truedef build(self) -> "GGCommons"// ggcommons::GgCommonsBuilderfn new(component_name: impl Into<String>) -> Self;fn receive_own_messages(self, receive_own_messages: bool) -> Self; // default true; false is a no-op (see note)fn args<I, T>(self, args: I) -> Selfwhere I: IntoIterator<Item = T>, T: Into<std::ffi::OsString>; // argv INCLUDES the program nameasync fn build(self) -> ggcommons::Result<GgCommons>;Rust has no with_app_options equivalent. build() is async and returns a Result.
// ggcommons.GGCommonsBuilderconstructor(componentNameValue: string);args(argv: string[]): this; // argv WITHOUT the node/script prefix (process.argv.slice(2))receiveOwnMessages(value: boolean): this; // default true; honored on IPC, MQTT uses broker semanticsbuild(): Promise<GGCommons>; // asyncTypeScript has no withAppOptions equivalent. build() returns a Promise.
Building the runtime
Section titled “Building the runtime”A minimal startup in each language. Pass the full component name (the same name used in your
recipe), the CLI args, and call build.
import com.mbreissi.ggcommons.GGCommons;import com.mbreissi.ggcommons.GGCommonsBuilder;
public static void main(String[] args) { GGCommons gg = GGCommonsBuilder.create("com.mbreissi.greengrass.JavaComponentSkeleton") .withArgs(args) .build();
var config = gg.getConfigManager(); var messaging = gg.getMessaging(); var metrics = gg.getMetrics();
// Do NOT register your own shutdown hook — the library wires SIGTERM/SIGINT to a graceful, // idempotent shutdown() itself. A second hook just double-runs teardown. // ... business logic ...}from ggcommons.ggcommons_builder import GGCommonsBuilder
gg = ( GGCommonsBuilder.create("ggcommons_python") .with_args([ "-c", "FILE", "config.json", "--platform", "HOST", "--transport", "MQTT", "messaging.json", "-t", "my-thing", ]) .build())
messaging = gg.get_messaging() # the MessagingClient class (static-method handle)config = gg.get_config_manager()# ... 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(); println!("component: {} thing: {}", gg.component_name(), cfg.thing_name);
if let Ok(messaging) = gg.messaging() { // Result, not Option let _ = messaging; // ... publish/subscribe ... } Ok(()) // dropping `gg` here releases all resources (RAII) — there is no close()}import { GGCommonsBuilder, GGCommons } from "ggcommons";
const gg = await new GGCommonsBuilder("com.example.Lc") .args([ "--platform", "HOST", "--transport", "MQTT", "messaging.json", "-c", "ENV", "GGC_LIFECYCLE_CFG", "-t", "lc-thing", ]) // argv WITHOUT the node/script prefix (process.argv.slice(2)) .build();
const messaging = gg.messaging(); // throws if no transport wiredconst metrics = gg.metrics();// Opt-in accessors return undefined when their config section is absent:// gg.credentials(), gg.parameters(), gg.streams()await gg.close(); // idempotentAccessors
Section titled “Accessors”The runtime exposes one accessor per subsystem. The always-on accessors (config, messaging, metrics) return the concrete service. The opt-in accessors (streams, credentials, parameters) follow the opt-in return contract below.
// com.mbreissi.ggcommons.GGCommons — accessorsConfigManager getConfigManager(); // concrete; throws if not initializedMessagingClient getMessaging(); // concrete instanceMetricEmitter getMetrics(); // concrete instanceStreamService getStreams(); // null when no `streaming` config sectionCredentialService getCredentials(); // null when no `credentials` sectionParameterService getParameters(); // null when no `parameters` sectionThere is no getConfig() accessor in Java — config is reached via getConfigManager().
# ggcommons.ggcommons.GGCommons — accessorsdef get_config_manager(self) -> ConfigManager # raises RuntimeError if not initializeddef get_messaging(self) # returns the MessagingClient CLASS (static-method ops)def get_metrics(self) # returns the MetricEmitter CLASSdef get_streams(self) # None when no `streaming` sectiondef get_credentials(self) # None when no `credentials` sectiondef get_parameters(self) # None when no `parameters` sectionget_messaging() and get_metrics() return the class, not an instance — their operations are
static, so you call them directly on the returned object.
// ggcommons::GgCommons — accessorsfn component_name(&self) -> &str;fn args(&self) -> &ParsedArgs;fn config(&self) -> Arc<Config>; // atomic snapshot (ArcSwap)fn messaging(&self) -> Result<Arc<dyn messaging::MessagingService>>; // Err if no transport wiredfn metrics(&self) -> Arc<dyn metrics::MetricService>;
#[cfg(feature = "streaming")]fn streams(&self) -> Arc<dyn streaming::StreamService>; // NOT Option — empty service when no section
#[cfg(feature = "credentials")]fn credentials(&self) -> Option<Arc<dyn credentials::CredentialService>>; // None when no section
#[cfg(feature = "parameters")]fn parameters(&self) -> Option<Arc<dyn parameters::ParameterService>>; // None when no sectionmessaging() returns a Result (not Option): it is Err(GgError::Messaging) if no transport was
wired. config() returns an Arc<Config> snapshot.
// ggcommons.GGCommons — accessorscomponentName(): string;args(): ParsedArgs;config(): Config; // snapshotmessaging(): IMessagingService; // throws GgError.messaging(...) if no transport wiredmetrics(): MetricService;streams(): StreamService | undefined; // undefined when no `streaming` sectioncredentials(): CredentialService | undefined; // undefined when no `credentials` sectionparameters(): ParameterService | undefined; // undefined when no `parameters` section“No transport wired” behavior
Section titled ““No transport wired” behavior”When no messaging transport is wired, the messaging accessor behaves differently per language:
- Java / Python return the concrete handle regardless (Java a
MessagingClientinstance, Python theMessagingClientclass). - Rust returns
Err(GgError::Messaging)— handle it with?or a match. - TypeScript throws
GgError.messaging(...)— guard with try/catch if messaging is optional.
Opt-in return contract
Section titled “Opt-in return contract”The newer subsystems — streaming, credentials, parameters — are opt-in. The accessor yields a usable service only when the matching config section is present (and, in Rust, the matching cargo feature is enabled). Otherwise it returns an “absent” value whose exact form is language-specific.
| Subsystem | Java | Python | Rust | TypeScript |
|---|---|---|---|---|
streams |
null |
None |
Arc<dyn StreamService> (empty, never None) |
undefined |
credentials |
null |
None |
Option<Arc<...>> → None |
undefined |
parameters |
null |
None |
Option<Arc<...>> → None |
undefined |
Two Rust-specific points to internalize:
streams()is notOption. It always returns anArc<dyn StreamService>; when there is nostreamingconfig section the service is simply empty. So Rust callers checkstreams().stream_names()for emptiness rather than testing forNone. (credentials()andparameters()areOption.)- The opt-in accessors are feature-gated.
streams()requiresfeature = "streaming",credentials()requiresfeature = "credentials", andparameters()requiresfeature = "parameters". The features are off by default, so without the feature the method does not even exist (it will not compile). In Java, Python, and TypeScript the accessor always exists and returns the null/None/undefined sentinel.
StreamService streams = gg.getStreams();if (streams != null) { // streaming is configured}CredentialService creds = gg.getCredentials(); // null when no `credentials` sectionstreams = gg.get_streams()if streams is not None: ... # streaming is configured
creds = gg.get_credentials() # None when no `credentials` section// streams() is NOT Option — always returns a (possibly empty) service.#[cfg(feature = "streaming")]if !gg.streams().stream_names().is_empty() { // streaming is configured}
// credentials()/parameters() ARE Option.#[cfg(feature = "credentials")]if let Some(creds) = gg.credentials() { let _ = creds;}const streams = gg.streams();if (streams !== undefined) { // streaming is configured}const creds = gg.credentials(); // undefined when no `credentials` sectionReadiness
Section titled “Readiness”Components report readiness through setReady. This drives the health endpoint’s readyz signal
(see Health). The flag defaults to ready at construction.
void setReady(boolean ready); // defaults to trueJava exposes only the setter publicly; the corresponding getters (isReadyz(),
messagingConnected()) are package-private.
def set_ready(self, ready: bool) -> None # no-op if readiness state was never builtPython exposes only the setter publicly.
fn set_ready(&self, ready: bool);fn is_shutting_down(&self) -> bool; // public readiness/shutdown gettersetReady(ready: boolean): void;ready(): boolean; // public readiness getterLifecycle & shutdown
Section titled “Lifecycle & shutdown”The shutdown story differs fundamentally per language, but one rule is shared everywhere: the library installs its own signal handlers — do not register your own.
void shutdown(); // idempotent (AtomicBoolean compareAndSet)The library installs a JVM shutdown hook on SIGTERM and SIGINT that runs a graceful
teardown. Calling shutdown() yourself is allowed and idempotent; it also deregisters the hook.
Do not add your own Runtime.getRuntime().addShutdownHook(...) — that double-runs teardown.
def shutdown(self) -> None # defensive per-subsystem teardown; safe on partial init
# Context-manager form runs shutdown() on exit:with GGCommonsBuilder.create("my-component").with_args(args).build() as gg: ... # business logicThe library installs a SIGTERM-only signal handler (main thread only) that calls shutdown()
then sys.exit(0). __enter__/__exit__ make GGCommons a context manager that calls
shutdown() on exit.
// There is NO close()/shutdown() method. Teardown is pure RAII:// dropping `GgCommons` aborts the heartbeat, hot-reload, signal-watcher, and// health-server tasks (AbortOnDrop).drop(gg); // or simply let `gg` go out of scopeRust has no close()/shutdown() — teardown is RAII via Drop. The library’s signal watcher
reacts to SIGTERM/Ctrl-C by only flipping readiness to “shutting down” (503) and logging; it does
not exit the process or tear down, because the library does not own your run loop. Teardown
happens when your loop ends and GgCommons is dropped.
async close(): Promise<void>; // idempotent (this.closed flag)The library installs both SIGTERM and SIGINT handlers that flip readiness to 503, run close(),
then process.exit(0). The handlers are removed inside close(). Calling close() yourself is
idempotent.
Parsing args without building
Section titled “Parsing args without building”Java exposes a static helper to parse the standard CLI contract without constructing a runtime — useful for tools and tests that need the resolved platform/transport/thing values.
static ParsedCommandLine processArgs(String componentName, String[] args, Options appOptions);
// ParsedCommandLine — public fields:// CommandLine commandLine// String[] configArgs// Platform platform// Transport transport// String standaloneConfigPath// String thingNamePython has no standalone processArgs equivalent on the runtime. Arg parsing happens inside
build(); reach the resolved values through the config manager after building.
Rust has no standalone processArgs equivalent. The parsed args are reachable on the built runtime
via gg.args(), which returns a &ParsedArgs.
TypeScript has no standalone processArgs equivalent. The parsed args are reachable on the built
runtime via gg.args(), which returns a ParsedArgs.
Deprecated Java constructors
Section titled “Deprecated Java constructors”Java keeps three deprecated direct constructors for backward compatibility. They are still
functional but superseded by the builder — use GGCommonsBuilder.create(...) for new code.
@Deprecated GGCommons(String componentName, String[] args);@Deprecated GGCommons(String componentName, String[] args, Options appOptions);@Deprecated GGCommons(String componentName, String[] args, Options appOptions, boolean receiveOwnMessages);These delegate to a package-private init(...); there is no public init() facade for callers.
Python keeps a public constructor
GGCommons(component_name, args, app_options=None, receive_own_messages=True) (raises ValueError
if component_name is falsy), but the builder is the recommended construction path.
Rust is builder-only — there is no public direct constructor and no deprecated surface.
TypeScript is builder-only — there is no public direct constructor and no deprecated surface.
Cross-language quick reference
Section titled “Cross-language quick reference”| Concept | Java | Python | Rust | TypeScript |
|---|---|---|---|---|
| Runtime type | GGCommons |
GGCommons |
GgCommons |
GGCommons |
| Builder type | GGCommonsBuilder |
GGCommonsBuilder |
GgCommonsBuilder |
GGCommonsBuilder |
| Start a builder | create(name) |
create(name) |
new(name) |
new GGCommonsBuilder(name) |
| Set args | withArgs(args) |
with_args(args) |
args(...) (incl. program name) |
args(...) (sliced) |
| App CLI options | withAppOptions(...) |
with_app_options(...) |
— (n/a) | — (n/a) |
| Build | build() |
build() |
build().await |
await build() |
| Config accessor | getConfigManager() |
get_config_manager() |
config() |
config() |
| Messaging accessor | getMessaging() |
get_messaging() |
messaging() (Result) |
messaging() (throws) |
| Metrics accessor | getMetrics() |
get_metrics() |
metrics() |
metrics() |
| Opt-in absent value | null |
None |
Option None (streams() empty service) |
undefined |
| Readiness setter | setReady(b) |
set_ready(b) |
set_ready(b) |
setReady(b) |
| Teardown | shutdown() + JVM hook |
shutdown() + context mgr |
RAII Drop (no method) |
close() |
| Signals wired | SIGTERM + SIGINT | SIGTERM only | watcher flips readiness only | SIGTERM + SIGINT |
| Direct construction | deprecated ctors | public ctor | builder-only | builder-only |