NAME

App::Ground - Ground your application.

DESCRIPTION

In circuit design, "ground" (GND) is a shared reference node (often called "0V") and a current return path. It is convenient because you can connect to it from many places, but it is not magic: real circuits may have multiple grounds, local grounds, and bad grounding can create noise or loops.

App::Ground uses the same metaphor for programming: it provides a small set of modules that act like a "ground" for infrastructure dependencies (logging, configuration, application handle, optional request context). This reduces parameter plumbing, but it should be used for infrastructure, not business data.

This is the classic trade-off of ambient authority vs dependency injection: globals are convenient, DI is explicit.

EXAMPLES IN OTHER LANGUAGES

JavaScript / Node.js

Helpers can use the global console and environment without receiving them as arguments.

function deepCall() {
  console.log('hello', process.env.PORT);
}

Rust

After the logger backend is initialized, helpers use log::info! without receiving a logger.

fn deep_call() {
    log::info!("hello");
}

Go

Go's standard logger is shared across packages, so helpers can log without receiving a logger.

import "log"

func deepCall() {
    log.Print("hello")
}

Node.js (AsyncLocalStorage)

A shared AsyncLocalStorage instance gives helpers access to context specific to their asynchronous call chain.

const { AsyncLocalStorage } = require('node:async_hooks');
const context = new AsyncLocalStorage();

function deepCall() {
  console.log(context.getStore().requestId);
}

context.run({ requestId: 'req-123' }, () => setImmediate(deepCall));

Java

Within an application class, a static SLF4J logger uses the shared logging backend without being passed to each method.

private static final Logger LOG = LoggerFactory.getLogger(Example.class);

void deepCall() {
    LOG.info("hello");
}

USAGE (THIS DISTRO)

This distribution ships several tiny "ground" modules.

A - Application

A is a place for your application's bootstrap code, giving scripts a shared environment through one import.

use A;  # Load the setup defined in your application's lib/A.pm.

C - Context

C gives deep code access to the application and its current context, as well as the application's database and configuration.

C::app( $app );  # Register the application at startup.

my $db  =  C::db;
my $cfg =  C::config;

sub handler {
  my( $c ) =  @_;
  local $C::C =  $c;
  deep_call();
  $c->render( text => 'ok' );
}

sub deep_call {
  my $c =  C::C()   or return;
  # ...
}

See C for accessors and the tutorial below for initialization.

I - Iterator

I lets tests supply in-memory rows to code that uses first and next, without querying a database.

my $rows = I->new([ { name => 'Alice' }, { name => 'Bob' } ]);
my $first = $rows->first;  # { name => 'Alice' }
my $next  = $rows->next;   # { name => 'Bob' }

L - Logging

L lets you choose a logging backend at startup and use the same logging calls throughout the application.

$L::logger = sub { $app->log };
L::info "Application ready\n";

M - Memory / Mapping

M keeps shared lookup tables available wherever they are needed.

my $month = $M::mon2num->{ jan };  # "01"

S - Singleton / Shared

S is a namespace for services or state your application initializes once and shares across its code.

$S::service = $service;  # Your application supplies this shared service.
$S::service->perform_action();

T - Time

T groups date and time helpers, including adding months with shorter months handled automatically.

my $date = T::nmonth '2024-01-31', 1;   # next month
my $str  = T::fd $date;                 # format date "2024-02-29"

U - Utilities

U provides a set of stable utility functions that are safe to use as global helpers from anywhere in an initialized application; see U for more examples.

U::ewarn 'Import failed', 'Could not read the input file';  # Log and email.
U::email $to, $subject, $body, \@attachments;
my $str = U::round $number;

TUTORIAL

Initialize once, then use shared infrastructure

Initialize the application before using utilities: load configuration, set up logging handlers and database connections, then register the application. The application and its infrastructure are intended to be effective singletons within a process. Treat configuration as stable after startup; utility callers reuse these facilities rather than constructing or replacing them on each call.

use C;
use L;
use U;

my $cfg =  C::load './app.conf';
# Construct/configure $app with $cfg, its logger and database connections.
# Its config method must expose $cfg; its mode selects development behavior.
C::app( $app );
$L::logger = sub { $app->log };

# Application code can now use the initialized infrastructure.
L::info "Application ready\n";
U::email 'user@example.com', 'Welcome', "Hello\n";
my $amount = U::round 5.6777, 2;  # "5.68"

When an application is registered, C::config reads $app->config. C::load supplies the fallback configuration when no application is registered; it does not automatically configure the application object.

Request context and deferred work

Set $C::C with local at the request boundary so helpers can use C::C(). Dynamic scoping does not automatically cross asynchronous or promise boundaries; capture the context and re-localize it in a deferred callback.

Depend on outcomes

The configured infrastructure may differ between applications, but callers should be able to rely on a clear action and expected result without worrying about which implementation was configured or what happens inside it.

Logging: whichever logger you configure, call L::info and a string is logged.

Email: whichever mail server you configure for sending, call U::email and an email is sent to the recipient.

Initialize these services to provide the expected behavior. Their shared configuration lets callers use the same operations throughout the application. U should not introduce persistent state of its own: call -> result or immediate effect.

AUTHOR

Eugen Konkov

SEE ALSO

A, C, I, L, M, S, T, U.