service_scope library

This library enables one to create a service scope in which code can run.

A service scope is an environment in which code runs. The environment is a Zone with added functionality. Code can be run inside a new service scope by using the fork(callback) method. This will call callback inside a new service scope and will keep the scope alive until the Future returned by the callback completes. At this point the service scope ends.

Code running inside a new service scope can

  • register objects (e.g. a database connection pool or a logging service)
  • look up previously registered objects
  • register on-scope-exit handlers

Service scopes can be nested. All registered values from the parent service scope are still accessible as long as they have not been overridden. The callback passed to fork() is responsible for not completing it's returned Future until all nested service scopes have ended.

The on-scope-exit callbacks will be called when the service scope ends. The callbacks are run in reverse registration order and are guaranteed to be executed. During a scope exit callback the active service scope cannot be modified anymore and lookup()s will only return values which were registered before the registration of the on-scope-exit callback.

One use-case of this is making services available to a server application. The server application will run inside a service scope which will have all necessary services registered. Once the server app shuts down, the registered on-scope-exit callbacks will automatically be invoked and the process will shut down cleanly.

Here is an example use case:

 import 'dart:async';
 import 'package:gcloud/service_scope.dart' as scope;

 class DBPool { ... }

 DBPool get dbService => scope.lookup(#dbpool);

 Future runApp() {
   // The application can use the registered objects (here the
   // dbService). It does not need to pass it around, but can use a
   // global getter.
   return dbService.query( ... ).listen(print).asFuture();
 }

 main() {
   // Creates a new service scope and runs the given closure inside it.
   ss.fork(() {
     // We create a new database pool with a 10 active connections and
     // add it to the current service scope with key `#dbpool`.
     // In addition we insert a on-scope-exit callback which will be
     // called once the application is done.
     var pool = new DBPool(connections: 10);
     scope.register(#dbpool, pool, onScopeExit: () => pool.close());
     return runApp();
  }).then((_) {
    print('Server application shut down cleanly');
  });
}

As an example, the package:appengine/appengine.dart package runs request handlers inside a service scope, which has most package:gcloud services registered.

The core application code can then be independent of package:appengine and instead depend only on the services needed (e.g. package:gcloud/storage.dart) by using getters in the service library (e.g. the storageService) which are implemented with service scope lookups.

Functions

fork(Future func(), {Function? onError}) Future
Start a new zone with a new service scope and run func inside it.
lookup(Object key) Object?
Look up an item by it's key in the currently active service scope.
register(Object key, Object value, {ScopeExitCallback? onScopeExit}) → void
Register a new object into the current service scope using the given key.
registerScopeExitCallback(ScopeExitCallback onScopeExitCallback) → void
Register a onScopeExitCallback to be invoked when this service scope ends.

Typedefs

ScopeExitCallback = FutureOr Function()