Package: microcosm
Simple Microservice ConfigurationWell-written microservices are small and single-purpose; any non-trivial ecosystem will have
a fleet of such services, each performing a different function. Inevitably, these services
will use common code and structure; this library provides a simple mechanism for constructing
these shared components and wiring them together into services.TerminologyAmicroserviceis a small software application. It is composed of several smaller pieces
of software, many of which are reusable.Acomponentis one of these (possibly reusable) pieces of software.Afactoryis a function used to create a component; it may be an object's constructor.Aconfig dictis a nested dictionary with string-valued keys. It contains data used
by factories to create components.Anobject graphis a collection of components that may reference each other (acyclically).Abindingis a string-valued key. It is used to identify a component within an object graph
and the subsection of the config dict reserved for a component's factory.Basic UsageDefine factory functions forcomponents, attach them to abinding, and provide
(optional) configurationdefaults:from microcosm.api import defaults, binding

@binding("foo")
@defaults(baz="value")
def create_foo(graph):
    return dict(
        # factories can reference other components
        bar=graph.bar,
        # factories can reference configuration
        baz=graph.config.foo.baz,
    )

@binding("bar")
def create_bar(graph):
    return dict()Factory functions have access to theobject graphand, through it, theconfig dict. Default
configuration values, if provided, are pre-populated within the provided binding; these may be
overridden from data loaded from an external source.Wire together the microservice by creating a new object graph along with service metadata:from microcosm.api import create_object_graph

graph = create_object_graph(
    name="myservice",
    debug=False,
    testing=False,
)Factories may access the service metadata viagraph.metadata. This allows for several
best practices:Components can implement ecosystem-wide conventions (e.g. for logging or persistence),
using the service name as a discriminator.Components can customize their behavior during development (debug=True) and unit
testing (testing=True)Reference anybindingin theobject graphto access the correspondingcomponent:print(graph.foo)Components are initializedlazily. In this example, the first timegraph.foois accessed,
the bound factory (create_foo()) is automatically invoked. Since this factory in turn
accessesgraph.bar, the next factory in the chain (create_bar()) would also be called
if it had not been called yet.Graph cycles are not allowed, although dependent components may cache the graph instance
to access depending components after initialization completes.Optionally, initialize the microservice's components explicitly:graph.use(
    "foo",
    "bar",
)While the same effect could be achieved by accessinggraph.fooorgraph.bar, this
construction has the advantage of initializes the listed components up front and triggering
any configuration errors as early as possible.It is also possible to thendisableany subsequent lazy initialization, preventing any
unintended initialization during subsequent operations:graph.lock()AssumptionsThis library was influenced by thepinjectproject, but
makes a few assumption that allow for a great deal of simplication:Microservices are small enough that simple string bindings suffice. Or, put another way,
conflicts between identically bound components are a non-concern and there is no need
for explicit scopes.Microservices use processes, not threads to scale. As such, thread synchronization is
a non-goal.Mocking (and patching) of the object graph is important and needs to be easy. Unit tests
expect to use `unittest.mock library; it should be trivial to temporarily replace a component.Some components will be functions that modify other components rather than objects
that need to be instantiated.SetupCreate a virtualenvpython -m venv venv
. ./venv/bin/activateInstall dependenciespip install -U -e .TestsRun the testspython setup.py nosetestsLintLint the code:NAME=microcosm ./entrypoint.sh lint
NAME=microcosm ./entrypoint.sh typehinting