# Simple Microservice Configuration
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.
[![Circle CI](https://circleci.com/gh/globality-corp/microcosm/tree/develop.svg?style=svg)](https://circleci.com/gh/globality-corp/microcosm/tree/develop)
## Terminology
- A `microservice` is a small software application. It is composed of several smaller pieces
of software, many of which are reusable.
- A `component` is one of these (possibly reusable) pieces of software.
- A `factory` is a function used to create a component; it may be an object's constructor.
- A `config dict` is a nested dictionary with string-valued keys. It contains data used
by factories to create components.
- An `object graph` is a collection of components that may reference each other (acyclically).
- A `binding` is 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 Usage
1. 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.
2. 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`)
3. 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.
4. 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()
## Assumptions
This library was influenced by the [pinject](https://github.com/google/pinject) project, but
makes a few assumption that allow for a great deal of simplication:
1. 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.
2. Microservices use processes, not threads to scale. As such, thread synchronization is
a non-goal.
3. 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.
4. Some components will be functions that modify other components rather than objects
that need to be instantiated.
## Setup
Create a virtualenv
```
python -m venv venv
. ./venv/bin/activate
```
Install dependencies
```
pip install -U -e .
```
## Tests
Run the tests
```
python setup.py nosetests
```
## Lint
Lint the code:
```
NAME=microcosm ./entrypoint.sh lint
NAME=microcosm ./entrypoint.sh typehinting
```
Raw data
{
"_id": null,
"home_page": "https://github.com/globality-corp/microcosm",
"name": "microcosm",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "microcosm",
"author": "Globality Engineering",
"author_email": "engineering@globality.com",
"download_url": "https://files.pythonhosted.org/packages/23/11/f1badbae3dc6ef64316ba6f00481a0a44d7515c3c560546964341eb4235e/microcosm-4.1.0.tar.gz",
"platform": null,
"description": "# Simple Microservice Configuration\n\nWell-written microservices are small and single-purpose; any non-trivial ecosystem will have\na fleet of such services, each performing a different function. Inevitably, these services\nwill use common code and structure; this library provides a simple mechanism for constructing\nthese shared components and wiring them together into services.\n\n[![Circle CI](https://circleci.com/gh/globality-corp/microcosm/tree/develop.svg?style=svg)](https://circleci.com/gh/globality-corp/microcosm/tree/develop)\n\n\n## Terminology\n\n - A `microservice` is a small software application. It is composed of several smaller pieces\n of software, many of which are reusable.\n - A `component` is one of these (possibly reusable) pieces of software.\n - A `factory` is a function used to create a component; it may be an object's constructor.\n - A `config dict` is a nested dictionary with string-valued keys. It contains data used\n by factories to create components.\n - An `object graph` is a collection of components that may reference each other (acyclically).\n - A `binding` is a string-valued key. It is used to identify a component within an object graph\n and the subsection of the config dict reserved for a component's factory.\n\n\n## Basic Usage\n\n 1. Define factory functions for `components`, attach them to a `binding`, and provide\n (optional) configuration `defaults`:\n\n from microcosm.api import defaults, binding\n\n @binding(\"foo\")\n @defaults(baz=\"value\")\n def create_foo(graph):\n return dict(\n # factories can reference other components\n bar=graph.bar,\n # factories can reference configuration\n baz=graph.config.foo.baz,\n )\n\n @binding(\"bar\")\n def create_bar(graph):\n return dict()\n\n Factory functions have access to the `object graph` and, through it, the `config dict`. Default\n configuration values, if provided, are pre-populated within the provided binding; these may be\n overridden from data loaded from an external source.\n\n 2. Wire together the microservice by creating a new object graph along with service metadata:\n\n from microcosm.api import create_object_graph\n\n graph = create_object_graph(\n name=\"myservice\",\n debug=False,\n testing=False,\n )\n\n Factories may access the service metadata via `graph.metadata`. This allows for several\n best practices:\n\n - Components can implement ecosystem-wide conventions (e.g. for logging or persistence),\n using the service name as a discriminator.\n - Components can customize their behavior during development (`debug=True`) and unit\n testing (`testing=True`)\n\n 3. Reference any `binding` in the `object graph` to access the corresponding `component`:\n\n print(graph.foo)\n\n Components are initialized *lazily*. In this example, the first time `graph.foo` is accessed,\n the bound factory (`create_foo()`) is automatically invoked. Since this factory in turn\n accesses `graph.bar`, the next factory in the chain (`create_bar()`) would also be called\n if it had not been called yet.\n\n Graph cycles are not allowed, although dependent components may cache the graph instance\n to access depending components after initialization completes.\n\n 4. Optionally, initialize the microservice's components explicitly:\n\n graph.use(\n \"foo\",\n \"bar\",\n )\n\n While the same effect could be achieved by accessing `graph.foo` or `graph.bar`, this\n construction has the advantage of initializes the listed components up front and triggering\n any configuration errors as early as possible.\n\n It is also possible to then *disable* any subsequent lazy initialization, preventing any\n unintended initialization during subsequent operations:\n\n graph.lock()\n\n\n## Assumptions\n\nThis library was influenced by the [pinject](https://github.com/google/pinject) project, but\nmakes a few assumption that allow for a great deal of simplication:\n\n 1. Microservices are small enough that simple string bindings suffice. Or, put another way,\n conflicts between identically bound components are a non-concern and there is no need\n for explicit scopes.\n\n 2. Microservices use processes, not threads to scale. As such, thread synchronization is\n a non-goal.\n\n 3. Mocking (and patching) of the object graph is important and needs to be easy. Unit tests\n expect to use `unittest.mock library; it should be trivial to temporarily replace a component.\n\n 4. Some components will be functions that modify other components rather than objects\n that need to be instantiated.\n\n## Setup\n\nCreate a virtualenv\n\n```\npython -m venv venv\n. ./venv/bin/activate\n```\n\nInstall dependencies\n\n```\npip install -U -e .\n```\n\n## Tests\n\nRun the tests\n\n```\npython setup.py nosetests\n```\n\n## Lint\n\nLint the code:\n\n```\nNAME=microcosm ./entrypoint.sh lint\nNAME=microcosm ./entrypoint.sh typehinting\n```\n",
"bugtrack_url": null,
"license": null,
"summary": "Microcosm - Simple microservice configuration",
"version": "4.1.0",
"project_urls": {
"Homepage": "https://github.com/globality-corp/microcosm"
},
"split_keywords": [
"microcosm"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "6b469a113fa4b6412a170333c566707db642095c45a60eb2c1ea618e846d4c37",
"md5": "c649b6faccfbd9a852553e389b7407db",
"sha256": "f85d7f60a388b5a2d59083c0fd5e9c120c44bfb3a779d62d1094f1bc37d6fae0"
},
"downloads": -1,
"filename": "microcosm-4.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "c649b6faccfbd9a852553e389b7407db",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 30699,
"upload_time": "2024-06-28T21:36:28",
"upload_time_iso_8601": "2024-06-28T21:36:28.932143Z",
"url": "https://files.pythonhosted.org/packages/6b/46/9a113fa4b6412a170333c566707db642095c45a60eb2c1ea618e846d4c37/microcosm-4.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "2311f1badbae3dc6ef64316ba6f00481a0a44d7515c3c560546964341eb4235e",
"md5": "57fb85d77845bcdadcfa3ef2fb7eefdb",
"sha256": "2d9297ffdd296d4ca96c81b6c96db6dfe89bf71371d5abf58865a0b699852279"
},
"downloads": -1,
"filename": "microcosm-4.1.0.tar.gz",
"has_sig": false,
"md5_digest": "57fb85d77845bcdadcfa3ef2fb7eefdb",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 25126,
"upload_time": "2024-06-28T21:36:30",
"upload_time_iso_8601": "2024-06-28T21:36:30.972692Z",
"url": "https://files.pythonhosted.org/packages/23/11/f1badbae3dc6ef64316ba6f00481a0a44d7515c3c560546964341eb4235e/microcosm-4.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-06-28 21:36:30",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "globality-corp",
"github_project": "microcosm",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "microcosm"
}