Well-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.
-
Define factory functions for components
, attach them to a binding
, and provide
(optional) configuration defaults
:
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 the object graph
and, through it, the config 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 via graph.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 any binding
in the object graph
to access the corresponding component
:
print(graph.foo)
Components are initialized lazily. In this example, the first time graph.foo
is accessed,
the bound factory (create_foo()
) is automatically invoked. Since this factory in turn
accesses graph.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 accessing graph.foo
or graph.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 then disable any subsequent lazy initialization, preventing any
unintended initialization during subsequent operations:
graph.lock()