{"id":13594997,"url":"https://github.com/minos-framework/minos-python","last_synced_at":"2026-04-05T01:32:01.759Z","repository":{"id":37095822,"uuid":"451501976","full_name":"minos-framework/minos-python","owner":"minos-framework","description":"🐍 Minos is a framework which helps you create reactive microservices in Python","archived":false,"fork":false,"pushed_at":"2026-03-01T04:22:45.000Z","size":19442,"stargazers_count":468,"open_issues_count":10,"forks_count":38,"subscribers_count":9,"default_branch":"main","last_synced_at":"2026-03-14T01:59:06.148Z","etag":null,"topics":["aggregate","aggregates","circuit-breaker","cqrs","domain-driven-design","event-driven","event-sourcing","framework","microservices","microservices-architecture","minos","python","saga"],"latest_commit_sha":null,"homepage":"http://www.minos.run","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/minos-framework.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS.md","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-24T14:40:50.000Z","updated_at":"2026-02-13T10:46:06.000Z","dependencies_parsed_at":"2024-06-21T05:47:14.252Z","dependency_job_id":"1aea9051-dbfe-4767-8aad-dc17306b02b0","html_url":"https://github.com/minos-framework/minos-python","commit_stats":{"total_commits":4150,"total_committers":10,"mean_commits":415.0,"dds":"0.26795180722891565","last_synced_commit":"3427fdaed489d3b9330c80ce15d77975e0135b9c"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/minos-framework/minos-python","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minos-framework%2Fminos-python","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minos-framework%2Fminos-python/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minos-framework%2Fminos-python/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minos-framework%2Fminos-python/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/minos-framework","download_url":"https://codeload.github.com/minos-framework/minos-python/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/minos-framework%2Fminos-python/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31421869,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T00:25:07.052Z","status":"ssl_error","status_checked_at":"2026-04-05T00:25:05.923Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aggregate","aggregates","circuit-breaker","cqrs","domain-driven-design","event-driven","event-sourcing","framework","microservices","microservices-architecture","minos","python","saga"],"created_at":"2024-08-01T16:01:42.146Z","updated_at":"2026-04-05T01:32:01.732Z","avatar_url":"https://github.com/minos-framework.png","language":"Python","readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://minos.run\" target=\"_blank\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/minos-framework/.github/main/images/logo.png\" alt=\"Minos logo\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# minos-python: The framework which helps you create reactive microservices in Python\n\n[![GitHub Repo stars](https://img.shields.io/github/stars/minos-framework/minos-python?label=GitHub)](https://github.com/minos-framework/minos-python/stargazers)\n[![PyPI Latest Release](https://img.shields.io/pypi/v/minos-microservice-common.svg)](https://pypi.org/project/minos-microservice-common/)\n[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/minos-framework/minos-python/pages%20build%20and%20deployment?label=docs)](https://minos-framework.github.io/minos-python)\n[![License](https://img.shields.io/github/license/minos-framework/minos-python.svg)](https://github.com/minos-framework/minos-python/blob/main/LICENSE)\n[![Coverage](https://codecov.io/github/minos-framework/minos-python/coverage.svg?branch=main)](https://codecov.io/gh/minos-framework/minos-python)\n[![Stack Overflow](https://img.shields.io/badge/Stack%20Overflow-Ask%20a%20question-green)](https://stackoverflow.com/questions/tagged/minos)\n[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/minos-framework/community)\n[![Tokei](https://tokei.rs/b1/github/minos-framework/minos-python?category=code)](https://github.com/minos-framework/minos-python)\n\n## Summary\n\nMinos is a framework which helps you create [reactive](https://www.reactivemanifesto.org/) microservices in Python. Internally, it leverages Event Sourcing, CQRS and a message driven architecture to fulfil the commitments of an asynchronous environment.\n\n## Roadmap\n\nThe roadmap of this project is publicly accessible at this [GitHub Repository](https://github.com/minos-framework/roadmap).\n\n## Foundational Patterns\n\nThe `minos` framework is built strongly inspired by the following set of patterns:\n\n* [Microservice architecture](https://microservices.io/patterns/microservices.html): Architect an application as a collection of loosely coupled services.\n* [Decompose by subdomain](https://microservices.io/patterns/decomposition/decompose-by-subdomain.html): Define services corresponding to Domain-Driven Design (DDD) subdomains\n* [Self-contained Service](https://microservices.io/patterns/decomposition/self-contained-service.html): Microservices can respond to a synchronous request without waiting for the response from any other service.\n* [Database per service](https://microservices.io/patterns/data/database-per-service.html): Keep each microservice's persistent data private to that service and accessible only via its API. A service's transactions only involve its database.\n* [Saga](https://microservices.io/patterns/data/saga.html): Transaction that spans multiple services.\n* [CQRS](https://microservices.io/patterns/data/cqrs.html): view database, which is a read-only replica that is designed to support queries that retrieves data from microservice. The application keeps the replica up to date by subscribing to Domain events published by the service that own the data.\n* [Domain event](https://microservices.io/patterns/data/domain-event.html): A service often needs to publish events when it updates its data. These events might be needed, for example, to update a CQRS view.\n* [Event Sourcing](https://microservices.io/patterns/data/event-sourcing.html): Event sourcing persists the state of a business entity such an Order or a Customer as a sequence of state-changing events. Whenever the state of a business entity changes, a new event is appended to the list of events. Since saving an event is a single operation, it is inherently atomic. The application reconstructs an entity's current state by replaying the events.\n* [Messaging](https://microservices.io/patterns/communication-style/messaging.html): Services communicating by exchanging messages over messaging channels. (Apache Kafka is used in this case)\n* [API gateway](https://microservices.io/patterns/apigateway.html): Single entry point for all clients. The API gateway proxy/route to the appropriate service.\n* [Service registry](https://microservices.io/patterns/service-registry.html): Database of services. A service registry might invoke a service instance's health check API to verify that it is able to handle requests\n* [Self Registration](https://microservices.io/patterns/self-registration.html): Each service instance register on startup and unregister on stop.\n* [Access token](https://microservices.io/patterns/security/access-token.html): The API Gateway authenticates the request and passes an access token (e.g. JSON Web Token) that securely identifies the requestor in each request to the services. A service can include the access token in requests it makes to other services.\n* [Health Check API](https://microservices.io/patterns/observability/health-check-api.html): A service has a health check API endpoint (e.g. HTTP `/health`) that returns the health of the service.\n\n## Installation\n\nThe easiest way to manage a project is with the `minos` command-line interface, which provides commands to setup both the project skeleton (configures containerization, databases, brokers, etc.) and the microservice skeleton (the base microservice structure, environment configuration, etc.).\n\nYou can install it with:\n\n```shell\npip install minos-cli\n```\n\nHere is a summary containing the most useful commands:\n\n* `minos new project $NAME`: Create a new Project\n* `minos set $RESOURCE $BACKEND`: Configure an environment resource (broker, database, etc.).\n* `minos deploy project`: Deploy a project.\n* `minos new microservice $NAME`: Create a new microservice.\n* `minos deploy microservice` deploy a microservice.\n\nFor more information, visit the [`minos-cli`](https://github.com/minos-framework/minos-cli) repository.\n\n## Documentation\n\nThe best place to start learning how to use the Minos Framework is at [Minos Learn](https://www.minos.run/learn/). The official API Reference is publicly available at the [GitHub Pages](https://minos-framework.github.io/minos-python).\n\n## QuickStart\n\nThis section includes a quickstart guide to create your first `minos` microservice, so that anyone can get the gist of the framework.\n\n### Set up the environment\n\nThe required environment to run this quickstart is the following:\n\n* A `python\u003e=3.9` interpreter with version equal or greater to .\n* A `kafka` instance available at `localhost:9092`\n* A `postgres` instance available at `localhost:5432` with the `foo_db` and `foobar_db` databases accessible with the `user:pass` credentials.\n* Two TCP sockets available to use at `localhost:4545` and `localhost:4546`.\n\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show a \u003ccode\u003edocker-compose.yml\u003c/code\u003e that provides the \u003ccode\u003ekafka\u003c/code\u003e and \u003ccode\u003epostgres\u003c/code\u003e instances ready to be used!\u003c/summary\u003e\n\n```yaml\n# docker-compose.yml\nversion: \"3.9\"\nservices:\n  zookeeper:\n    restart: always\n    image: wurstmeister/zookeeper:latest\n  kafka:\n    restart: always\n    image: wurstmeister/kafka:latest\n    ports:\n      - \"9092:9092\"\n    depends_on:\n      - zookeeper\n    environment:\n      KAFKA_ADVERTISED_HOST_NAME: localhost\n      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181\n  postgres:\n    restart: always\n    image: postgres:latest\n    ports:\n      - \"5432:5432\"\n    environment:\n      - POSTGRES_USER=user\n      - POSTGRES_PASSWORD=pass\n```\n\nThen, start the environment:\n\n```shell\ndocker-compose up\n```\n\nTo create the databases, just run the following command:\n\n```shell\ndocker-compose exec postgres psql -U user -tc 'CREATE database foo_db'\ndocker-compose exec postgres psql -U user -tc 'CREATE database foobar_db'\n```\n\u003c/details\u003e\n\nNote that these parameters can be customized on the configuration files.\n\n### Install the dependencies\n\nIf you want to directly use `minos` without the command-line utility, the following command will install the needed packages:\n\n```shell\npip install \\\n  minos-microservice-aggregate \\\n  minos-microservice-common \\\n  minos-microservice-cqrs \\\n  minos-microservice-networks \\\n  minos-microservice-saga \\ \n  minos-broker-kafka \\\n  minos-http-aiohttp \\\n  minos-http-aiopg \\\n  minos-http-lmbdb\n```\n\n### Configure a Microservice\n\nTo keep things simpler, this quickstart will create a microservice assuming all the source code is stored on a single `foo/main.py` file. In addition to the source file, a `foo/config.yml` will contain all the configuration stuff.\n\nThe directory structure will become:\n\n```shell\n.\n└── foo\n    ├── config.yml\n    └── main.py\n```\n\nCreate a `foo/config.yml` file and add the following lines:\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show the full file\u003c/summary\u003e\n\n```yaml\n# foo/config.yml\n\nversion: 2\nname: foo\naggregate:\n  entities:\n    - main.Foo\n  repositories:\n    transaction: minos.aggregate.DatabaseTransactionRepository\n    event: minos.aggregate.DatabaseEventRepository\n    snapshot: minos.aggregate.DatabaseSnapshotRepository\ndatabases:\n  default:\n    client: minos.plugins.aiopg.AiopgDatabaseClient\n    database: foo_db\n    user: minos\n    password: min0s\n  saga:\n    client: minos.plugins.lmdb.LmdbDatabaseClient\n    path: \"./foo.lmdb\"\ninterfaces:\n  broker:\n    port: minos.networks.BrokerPort\n    publisher:\n      client: minos.plugins.kafka.KafkaBrokerPublisher\n      queue: minos.networks.DatabaseBrokerPublisherQueue\n    subscriber:\n      client: minos.plugins.kafka.KafkaBrokerSubscriber\n      queue: minos.networks.DatabaseBrokerSubscriberQueue\n      validator: minos.networks.DatabaseBrokerSubscriberDuplicateValidator\n  http:\n    port: minos.networks.HttpPort\n    connector:\n      client: minos.plugins.aiohttp.AioHttpConnector\n      port: 4545\n  periodic:\n    port: minos.networks.PeriodicPort\npools:\n  lock: minos.common.DatabaseLockPool\n  database: minos.common.DatabaseClientPool\n  broker: minos.networks.BrokerClientPool\nsaga:\n  manager: minos.saga.SagaManager\nrouters:\n  - minos.networks.BrokerRouter\n  - minos.networks.PeriodicRouter\n  - minos.networks.RestHttpRouter\nmiddleware:\n  - minos.saga.transactional_command\nservices:\n  - minos.networks.SystemService\n  - minos.aggregate.TransactionService\n  - minos.aggregate.SnapshotService\n  - minos.saga.SagaService\n  - main.FooCommandService\n  - main.FooQueryService\n```\n\n\u003c/details\u003e\n\nCreate a `foo/main.py` file and add the following content:\n\n```python\n# foo/main.py\n\nfrom pathlib import Path\nfrom minos.aggregate import Aggregate, RootEntity\nfrom minos.common import EntrypointLauncher\nfrom minos.cqrs import CommandService, QueryService\n\n\nclass Foo(RootEntity):\n    \"\"\"Foo RootEntity class.\"\"\"\n\n\nclass FooAggregate(Aggregate[Foo]):\n    \"\"\"Foo Aggregate class.\"\"\"\n\n\nclass FooCommandService(CommandService):\n    \"\"\"Foo Command Service class.\"\"\"\n\n\nclass FooQueryService(QueryService):\n    \"\"\"Foo Query Service class.\"\"\"\n\n\nif __name__ == '__main__':\n    launcher = EntrypointLauncher.from_config(Path(__file__).parent / \"config.yml\")\n    launcher.launch()\n```\n\nExecute the following command to start the microservice:\n\n```shell\npython foo/main.py\n```\n\n### Create an Aggregate\n\nThe way to model data in `minos` is highly inspired by the  [Event Sourcing](https://microservices.io/patterns/data/event-sourcing.html) ideas. For this reason, the classes to be used to model data are:\n\n* `minos.aggregate.Entity`: A model that has an identifier that gives it a unique identity, in the sense that some values from which it is composed could change, but its identity will continue being the same.\n* `minos.aggregate.ExternalEntity`: A model that belongs to another microservice (or aggregate boundary) but needs to be used for some reason inside this microservice (or aggregate boundary).\n* `minos.aggregate.RootEntity`: Is an `Entity` superset that provides global identity across the project compared to standard `Entity` models, that has only local identity (the `RootEntity` can be accessed from another microservices as `ExternalEntity` models, but standard `Entity` models can only be accessed within the microservice that define them). The `RootEntity` is also the one that interacts with the persistence layer (the `EventRepository` and `SnapshotRepository` instances).\n* `minos.aggregate.Ref`: A wrapper class that provides the functionality to store a reference of other `RootEntity` or `ExternalEntity` instances.\n* `minos.aggregate.EntitySet`: A container of `Entity` instances that takes advantage of the incremental behaviour of the `EventRepository`.\n* `minos.aggregate.ValueObject`: A model that is only identified by the values that compose it, so that if some of them changes, then the model becomes completely different (for that reason, these models are immutable).\n* `minos.aggregate.ValueObjectSet`: A container of `ValueObject` instances that takes advantage of the incremental behaviour of the `EventRepository.\n* `minos.aggregate.Aggregate`: A collection of `Entity` and/or `ValueObject` models that are related to each other through a `RootEntity`.\n* `minos.aggregate.Event`: A model that contains the difference between the a `RootEntity` instance and its previous version (if any).\n\nHere is an example of the creation the `Foo` aggregate. In this case, it has two attributes, a `bar` being a `str`, and a `foobar` being an optional reference to the external `FooBar` aggregate, which it is assumed that it has a `something` attribute.\n\n```python\n# foo/main.py\n\nfrom __future__ import annotations\nfrom typing import Optional\nfrom uuid import UUID\nfrom minos.aggregate import Aggregate, RootEntity, ExternalEntity, Ref\n\n\nclass Foo(RootEntity):\n    \"\"\"Foo RootEntity class.\"\"\"\n\n    bar: str\n    foobar: Optional[Ref[FooBar]]\n\n\nclass FooBar(ExternalEntity):\n    \"\"\"FooBar ExternalEntity clas.\"\"\"\n\n    something: str\n\n\nclass FooAggregate(Aggregate[Foo]):\n    \"\"\"Foo Aggregate class.\"\"\"\n\n    @staticmethod\n    async def create_foo(bar: str) -\u003e UUID:\n        \"\"\"Create a new Foo instance\n        \n        :param bar: The bar of the new instance.\n        :return: The identifier of the new instance.\n        \"\"\"\n        foo = await Foo.create(bar)\n\n        return foo.uuid\n\n    @staticmethod\n    async def update_foobar(uuid: UUID, foobar: Optional[Ref[FooBar]]) -\u003e None:\n        \"\"\"Update the foobar attribute of the ``Foo`` instance.\n        \n        :param uuid: The identifier of the ``Foo`` instance.\n        :param foobar: The foobar value to be set.\n        :return: This method does not return anything.\n        \"\"\"\n        foo = await Foo.get(uuid)\n        foo.foobar = foobar\n        await foo.save()\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show the full file\u003c/summary\u003e\n\n```python\n# foo/main.py\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\nfrom typing import Optional\nfrom uuid import UUID\n\nfrom minos.aggregate import Aggregate, RootEntity, ExternalEntity, Ref\nfrom minos.common import EntrypointLauncher\nfrom minos.cqrs import CommandService, QueryService\n\n\nclass Foo(RootEntity):\n    \"\"\"Foo RootEntity class.\"\"\"\n\n    bar: str\n    foobar: Optional[Ref[FooBar]]\n\n\nclass FooBar(ExternalEntity):\n    \"\"\"FooBar ExternalEntity clas.\"\"\"\n\n    something: str\n\n\nclass FooAggregate(Aggregate[Foo]):\n    \"\"\"Foo Aggregate class.\"\"\"\n\n    @staticmethod\n    async def create_foo(bar: str) -\u003e UUID:\n        \"\"\"Create a new Foo instance\n        \n        :param bar: The bar of the new instance.\n        :return: The identifier of the new instance.\n        \"\"\"\n        foo = await Foo.create(bar)\n\n        return foo.uuid\n\n    @staticmethod\n    async def update_foobar(uuid: UUID, foobar: Optional[Ref[FooBar]]) -\u003e None:\n        \"\"\"Update the foobar attribute of the ``Foo`` instance.\n        \n        :param uuid: The identifier of the ``Foo`` instance.\n        :param foobar: The foobar value to be set.\n        :return: This method does not return anything.\n        \"\"\"\n        foo = await Foo.get(uuid)\n        foo.foobar = foobar\n        await foo.save()\n\n\nclass FooCommandService(CommandService):\n    \"\"\"Foo Command Service class.\"\"\"\n\n\nclass FooQueryService(QueryService):\n    \"\"\"Foo Query Service class.\"\"\"\n\n\nif __name__ == '__main__':\n    launcher = EntrypointLauncher.from_config(Path(__file__).parent / \"config.yml\")\n    launcher.launch()\n```\n\n\u003c/details\u003e\n\n### Expose a Command\n\nHere is an example of the definition of a command to create `Foo` instances. To do that, it is necessary to define a `CommandService` that contains the handling function. It will handle both the broker messages sent to the `\"CreateFoo\"` topic and the rest calls to the `\"/foos\"` path with the `\"POST\"` method. In this case, the handling function unpacks the `Request`'s content and then calls the `create` method from the `Aggregate`, which stores the `Foo` instance following an event-driven strategy (it also publishes the `\"FooCreated\"` event). Finally, a `Response` is returned to be handled by the external caller (another microservice or the API-gateway).\n\n```python\n# foo/main.py\n\nfrom minos.cqrs import CommandService\nfrom minos.networks import enroute, Request, Response\n\n\nclass FooCommandService(CommandService):\n    \"\"\"Foo Command Service class.\"\"\"\n\n    @enroute.broker.command(\"CreateFoo\")\n    @enroute.rest.command(\"/foos\", \"POST\")\n    async def create_foo(self, request: Request) -\u003e Response:\n        \"\"\"Create a new Foo.\n\n        :param request: The ``Request`` that contains the ``bar`` attribute.\n        :return: A ``Response`` containing identifier of the already created instance.\n        \"\"\"\n        content = await request.content()\n        bar = content[\"bar\"]\n\n        uuid = await FooAggregate.create_foo(bar)\n\n        return Response({\"uuid\": uuid})\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show the full file\u003c/summary\u003e\n\n```python\n# foo/main.py\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\nfrom typing import Optional\nfrom uuid import UUID\n\nfrom minos.aggregate import Aggregate, RootEntity, ExternalEntity, Ref\nfrom minos.common import EntrypointLauncher\nfrom minos.cqrs import CommandService, QueryService\nfrom minos.networks import Request, Response, enroute\n\n\nclass Foo(RootEntity):\n    \"\"\"Foo RootEntity class.\"\"\"\n\n    bar: str\n    foobar: Optional[Ref[FooBar]]\n\n\nclass FooBar(ExternalEntity):\n    \"\"\"FooBar ExternalEntity clas.\"\"\"\n\n    something: str\n\n\nclass FooAggregate(Aggregate[Foo]):\n    \"\"\"Foo Aggregate class.\"\"\"\n\n    @staticmethod\n    async def create_foo(bar: str) -\u003e UUID:\n        \"\"\"Create a new Foo instance\n        \n        :param bar: The bar of the new instance.\n        :return: The identifier of the new instance.\n        \"\"\"\n        foo = await Foo.create(bar)\n\n        return foo.uuid\n\n    @staticmethod\n    async def update_foobar(uuid: UUID, foobar: Optional[Ref[FooBar]]) -\u003e None:\n        \"\"\"Update the foobar attribute of the ``Foo`` instance.\n        \n        :param uuid: The identifier of the ``Foo`` instance.\n        :param foobar: The foobar value to be set.\n        :return: This method does not return anything.\n        \"\"\"\n        foo = await Foo.get(uuid)\n        foo.foobar = foobar\n        await foo.save()\n\n\nclass FooCommandService(CommandService):\n    \"\"\"Foo Command Service class.\"\"\"\n\n    @enroute.broker.command(\"CreateFoo\")\n    @enroute.rest.command(\"/foos\", \"POST\")\n    async def create_foo(self, request: Request) -\u003e Response:\n        \"\"\"Create a new Foo.\n\n        :param request: The ``Request`` that contains the ``bar`` attribute.\n        :return: A ``Response`` containing identifier of the already created instance.\n        \"\"\"\n        content = await request.content()\n        bar = content[\"bar\"]\n\n        uuid = await FooAggregate.create_foo(bar)\n\n        return Response({\"uuid\": uuid})\n\n\nclass FooQueryService(QueryService):\n    \"\"\"Foo Query Service class.\"\"\"\n\n\nif __name__ == '__main__':\n    launcher = EntrypointLauncher.from_config(Path(__file__).parent / \"config.yml\")\n    launcher.launch()\n```\n\n\u003c/details\u003e\n\nExecute the following command to start the microservice:\n\n```shell\npython foo/main.py\n```\n\nTo check that everything works fine, execute the following command:\n\n```shell\ncurl --location --request POST 'http://localhost:4545/foos' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n    \"bar\": \"test\"\n}'\n```\n\nAnd the expected response will be similar to:\n\n```json\n{\n  \"uuid\": \"YOUR_UUID\"\n}\n```\n\n### Subscribe to an Event and Expose a Query\n\nHere is an example of the event and query handling. In this case, it must be defined on a `QueryService` class. In this case a `\"FooCreated\"` and `\"FooUpdated.foobar\"` events are handled (they will print the content on the microservice's logs). The event contents typically contains instances of `AggregateDiff` type, which is referred to the difference respect to the previously stored instance. The exposed query is connected to the calls that come from the `\"/foos/example\"` path and `\"GET\"` method and a naive string is returned.\n\n*Disclaimer*: A real `QueryService` implementation must populate a query-oriented database based on the events to which is subscribed to, and expose queries performed over that query-oriented database.\n\n```python\n# foo/main.py\n\nfrom minos.cqrs import QueryService\nfrom minos.networks import enroute, Request, Response\n\n\nclass FooQueryService(QueryService):\n    \"\"\"Foo Query Service class.\"\"\"\n\n    @enroute.broker.event(\"FooCreated\")\n    async def foo_created(self, request: Request) -\u003e None:\n        \"\"\"Handle the \"FooCreated\" event.\n\n        :param request: The ``Request`` that contains a ``Event``.\n        :return: This method does not return anything.\n        \"\"\"\n        event = await request.content()\n        print(f\"A Foo was created: {event}\")\n\n    @enroute.broker.event(\"FooUpdated.foobar\")\n    async def foo_foobar_updated(self, request: Request) -\u003e None:\n        \"\"\"Handle the \"FooUpdated.foobar\" event.\n\n        :param request: The ``Request`` that contains a ``Event``.\n        :return: This method does not return anything.\n        \"\"\"\n        event = await request.content()\n        print(f\"The 'foobar' field of a Foo was updated: {event}\")\n\n    @enroute.rest.query(\"/foos/example\", \"GET\")\n    async def example(self, request: Request) -\u003e Response:\n        \"\"\"Handle the example query.\n\n        :param request: The ``Request`` that contains the necessary information.\n        :return: A ``Response`` instance.\n        \"\"\"\n        return Response(\"This is an example response!\")\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show the full file\u003c/summary\u003e\n\n```python\n# foo/main.py\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\nfrom typing import Optional\nfrom uuid import UUID\n\nfrom minos.aggregate import Aggregate, RootEntity, ExternalEntity, Ref\nfrom minos.common import EntrypointLauncher\nfrom minos.cqrs import CommandService, QueryService\nfrom minos.networks import Request, Response, enroute\n\n\nclass Foo(RootEntity):\n    \"\"\"Foo RootEntity class.\"\"\"\n\n    bar: str\n    foobar: Optional[Ref[FooBar]]\n\n\nclass FooBar(ExternalEntity):\n    \"\"\"FooBar ExternalEntity clas.\"\"\"\n\n    something: str\n\n\nclass FooAggregate(Aggregate[Foo]):\n    \"\"\"Foo Aggregate class.\"\"\"\n\n    @staticmethod\n    async def create_foo(bar: str) -\u003e UUID:\n        \"\"\"Create a new Foo instance\n        \n        :param bar: The bar of the new instance.\n        :return: The identifier of the new instance.\n        \"\"\"\n        foo = await Foo.create(bar)\n\n        return foo.uuid\n\n    @staticmethod\n    async def update_foobar(uuid: UUID, foobar: Optional[Ref[FooBar]]) -\u003e None:\n        \"\"\"Update the foobar attribute of the ``Foo`` instance.\n        \n        :param uuid: The identifier of the ``Foo`` instance.\n        :param foobar: The foobar value to be set.\n        :return: This method does not return anything.\n        \"\"\"\n        foo = await Foo.get(uuid)\n        foo.foobar = foobar\n        await foo.save()\n\n\nclass FooCommandService(CommandService):\n    \"\"\"Foo Command Service class.\"\"\"\n\n    @enroute.broker.command(\"CreateFoo\")\n    @enroute.rest.command(\"/foos\", \"POST\")\n    async def create_foo(self, request: Request) -\u003e Response:\n        \"\"\"Create a new Foo.\n\n        :param request: The ``Request`` that contains the ``bar`` attribute.\n        :return: A ``Response`` containing identifier of the already created instance.\n        \"\"\"\n        content = await request.content()\n        bar = content[\"bar\"]\n\n        uuid = await FooAggregate.create_foo(bar)\n\n        return Response({\"uuid\": uuid})\n\n\nclass FooQueryService(QueryService):\n    \"\"\"Foo Query Service class.\"\"\"\n\n    @enroute.broker.event(\"FooCreated\")\n    async def foo_created(self, request: Request) -\u003e None:\n        \"\"\"Handle the \"FooCreated\" event.\n\n        :param request: The ``Request`` that contains a ``Event``.\n        :return: This method does not return anything.\n        \"\"\"\n        event = await request.content()\n        print(f\"A Foo was created: {event}\")\n\n    @enroute.broker.event(\"FooUpdated.foobar\")\n    async def foo_foobar_updated(self, request: Request) -\u003e None:\n        \"\"\"Handle the \"FooUpdated.foobar\" event.\n\n        :param request: The ``Request`` that contains a ``Event``.\n        :return: This method does not return anything.\n        \"\"\"\n        event = await request.content()\n        print(f\"The 'foobar' field of a Foo was updated: {event}\")\n\n    @enroute.rest.query(\"/foos/example\", \"GET\")\n    async def example(self, request: Request) -\u003e Response:\n        \"\"\"Handle the example query.\n\n        :param request: The ``Request`` that contains the necessary information.\n        :return: A ``Response`` instance.\n        \"\"\"\n        return Response(\"This is an example response!\")\n\n\nif __name__ == '__main__':\n    launcher = EntrypointLauncher.from_config(Path(__file__).parent / \"config.yml\")\n    launcher.launch()\n```\n\n\u003c/details\u003e\n\nExecute the following command to start the microservice:\n\n```shell\npython foo/main.py\n```\n\nNow, if a new instance is created (with a rest call, like in the [previous section](#expose-a-command)), the `FooCreated` event will be handled and the microservice's console will print something like:\n\n```\nA Foo was created: Event(...)\n```\n\nAlso, to check that everything is fine the example query can be executed with:\n\n```shell\ncurl --location --request GET 'http://localhost:4545/foos/example'\n```\n\nAnd the expected result should be something like:\n\n```\n\"This is an example response!\"\n```\n\n### Interact with another Microservice\n\nHere is an example of the interaction between two microservices through a SAGA pattern. In this case, the interaction starts with a call to the `\"/foos/add-foobar\"` path and the `\"POST\"` method, which performs a `SagaManager` run over the `ADD_FOOBAR_SAGA` saga. This saga has two steps, one remote that executes the `\"CreateFooBar\"` command (possibly defined on the supposed `\"foobar\"` microservice), and a local step that is executed on this microservice. The `CreateFooBarDTO` defines the structure of the request to be sent when the `\"CreateFooBar\"` command is executed.\n\n```python\n# foo/main.py\n\nfrom minos.common import ModelType\nfrom minos.cqrs import CommandService\nfrom minos.networks import enroute, Request\nfrom minos.saga import Saga, SagaContext, SagaRequest, SagaResponse\n\n\nclass FooCommandService(CommandService):\n    \"\"\"Foo Command Service class.\"\"\"\n\n    @enroute.rest.command(\"/foos/add-foobar\", \"POST\")\n    async def update_foo(self, request: Request) -\u003e None:\n        \"\"\"Run a saga example.\n\n        :param request: The ``Request`` that contains the initial saga's context.\n        :return: This method does not return anything.\n        \"\"\"\n        content = await request.content()\n\n        context = SagaContext(uuid=content[\"uuid\"], something=content[\"something\"])\n        await self.saga_manager.run(ADD_FOOBAR_SAGA, context)\n\n\ndef _create_foobar(context: SagaContext) -\u003e SagaRequest:\n    something = context[\"something\"]\n    content = CreateFooBarDTO(56, something)\n    return SagaRequest(\"CreateFooBar\", content)\n\n\nasync def _success_foobar(context: SagaContext, response: SagaResponse) -\u003e SagaContext:\n    context[\"foobar_uuid\"] = await response.content()\n    return context\n\n\nasync def _error_foobar(context: SagaContext, response: SagaResponse) -\u003e SagaContext:\n    raise ValueError(\"The foobar could not be created!\")\n\n\nasync def _update_foo(context: SagaContext) -\u003e None:\n    await FooAggregate.update_foobar(context[\"uuid\"], context[\"foobar_uuid\"])\n\n\nCreateFooBarDTO = ModelType.build(\"AnotherDTO\", {\"number\": int, \"text\": str})\n\nADD_FOOBAR_SAGA = (\n    Saga()\n        .remote_step()\n        .on_execute(_create_foobar)\n        .on_success(_success_foobar)\n        .on_error(_error_foobar)\n        .local_step()\n        .on_execute(_update_foo)\n        .commit()\n)\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show the full file\u003c/summary\u003e\n\n```python\n# foo/main.py\n\nfrom __future__ import annotations\n\nfrom pathlib import Path\nfrom typing import Optional\nfrom uuid import UUID\n\nfrom minos.aggregate import Aggregate, RootEntity, ExternalEntity, Ref\nfrom minos.common import ModelType, EntrypointLauncher\nfrom minos.cqrs import CommandService, QueryService\nfrom minos.networks import Request, Response, enroute\nfrom minos.saga import Saga, SagaContext, SagaRequest, SagaResponse\n\n\nclass Foo(RootEntity):\n    \"\"\"Foo RootEntity class.\"\"\"\n\n    bar: str\n    foobar: Optional[Ref[FooBar]]\n\n\nclass FooBar(ExternalEntity):\n    \"\"\"FooBar ExternalEntity clas.\"\"\"\n\n    something: str\n\n\nclass FooAggregate(Aggregate[Foo]):\n    \"\"\"Foo Aggregate class.\"\"\"\n\n    @staticmethod\n    async def create_foo(bar: str) -\u003e UUID:\n        \"\"\"Create a new Foo instance\n        \n        :param bar: The bar of the new instance.\n        :return: The identifier of the new instance.\n        \"\"\"\n        foo = await Foo.create(bar)\n\n        return foo.uuid\n\n    @staticmethod\n    async def update_foobar(uuid: UUID, foobar: Optional[Ref[FooBar]]) -\u003e None:\n        \"\"\"Update the foobar attribute of the ``Foo`` instance.\n        \n        :param uuid: The identifier of the ``Foo`` instance.\n        :param foobar: The foobar value to be set.\n        :return: This method does not return anything.\n        \"\"\"\n        foo = await Foo.get(uuid)\n        foo.foobar = foobar\n        await foo.save()\n\n\nclass FooCommandService(CommandService):\n    \"\"\"Foo Command Service class.\"\"\"\n\n    @enroute.broker.command(\"CreateFoo\")\n    @enroute.rest.command(\"/foos\", \"POST\")\n    async def create_foo(self, request: Request) -\u003e Response:\n        \"\"\"Create a new Foo.\n\n        :param request: The ``Request`` that contains the ``bar`` attribute.\n        :return: A ``Response`` containing identifier of the already created instance.\n        \"\"\"\n        content = await request.content()\n        bar = content[\"bar\"]\n\n        uuid = await FooAggregate.create_foo(bar)\n\n        return Response({\"uuid\": uuid})\n\n    @enroute.rest.command(\"/foos/add-foobar\", \"POST\")\n    async def update_foo(self, request: Request) -\u003e None:\n        \"\"\"Run a saga example.\n\n        :param request: The ``Request`` that contains the initial saga's context.\n        :return: This method does not return anything.\n        \"\"\"\n        content = await request.content()\n\n        context = SagaContext(uuid=content[\"uuid\"], something=content[\"something\"])\n        await self.saga_manager.run(ADD_FOOBAR_SAGA, context)\n\n\ndef _create_foobar(context: SagaContext) -\u003e SagaRequest:\n    something = context[\"something\"]\n    content = CreateFooBarDTO(56, something)\n    return SagaRequest(\"CreateFooBar\", content)\n\n\nasync def _success_foobar(context: SagaContext, response: SagaResponse) -\u003e SagaContext:\n    context[\"foobar_uuid\"] = await response.content()\n    return context\n\n\nasync def _error_foobar(context: SagaContext, response: SagaResponse) -\u003e SagaContext:\n    raise ValueError(\"The foobar could not be created!\")\n\n\nasync def _update_foo(context: SagaContext) -\u003e None:\n    await FooAggregate.update_foobar(context[\"uuid\"], context[\"foobar_uuid\"])\n\n\nCreateFooBarDTO = ModelType.build(\"AnotherDTO\", {\"number\": int, \"text\": str})\n\nADD_FOOBAR_SAGA = (\n    Saga()\n        .remote_step()\n        .on_execute(_create_foobar)\n        .on_success(_success_foobar)\n        .on_error(_error_foobar)\n        .local_step()\n        .on_execute(_update_foo)\n        .commit()\n)\n\n\nclass FooQueryService(QueryService):\n    \"\"\"Foo Query Service class.\"\"\"\n\n    @enroute.broker.event(\"FooCreated\")\n    async def foo_created(self, request: Request) -\u003e None:\n        \"\"\"Handle the \"FooCreated\" event.\n\n        :param request: The ``Request`` that contains a ``Event``.\n        :return: This method does not return anything.\n        \"\"\"\n        event = await request.content()\n        print(f\"A Foo was created: {event}\")\n\n    @enroute.broker.event(\"FooUpdated.foobar\")\n    async def foo_foobar_updated(self, request: Request) -\u003e None:\n        \"\"\"Handle the \"FooUpdated.foobar\" event.\n\n        :param request: The ``Request`` that contains a ``Event``.\n        :return: This method does not return anything.\n        \"\"\"\n        event = await request.content()\n        print(f\"The 'foobar' field of a Foo was updated: {event}\")\n\n    @enroute.rest.query(\"/foos/example\", \"GET\")\n    async def example(self, request: Request) -\u003e Response:\n        \"\"\"Handle the example query.\n\n        :param request: The ``Request`` that contains the necessary information.\n        :return: A ``Response`` instance.\n        \"\"\"\n        return Response(\"This is an example response!\")\n\n\nif __name__ == '__main__':\n    launcher = EntrypointLauncher.from_config(Path(__file__).parent / \"config.yml\")\n    launcher.launch()\n```\n\n\u003c/details\u003e\n\nExecute the following command to start the `foo` microservice:\n\n```shell\npython foo/main.py\n```\n\n**Disclaimer**: Note that in this case another microservice is needed to complete the saga.\n\n#### The `foobar` Microservice\n\nThe `foobar` microservice will simply have a `CreateFooBar` command to create new instances of its `FooBar` root entity.\n\nThe directory structure will become:\n\n```shell\n.\n├── foo\n│   ├── config.yml\n│   └── main.py\n└── foobar\n    ├── config.yml\n    └── main.py\n```\n\nHere is the `foobar/config.yml` config file:\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show the full file\u003c/summary\u003e\n\n```yaml\n# foobar/config.yml\n\nversion: 2\nname: foobar\naggregate:\n  entities:\n    - main.FooBar\n  repositories:\n    transaction: minos.aggregate.DatabaseTransactionRepository\n    event: minos.aggregate.DatabaseEventRepository\n    snapshot: minos.aggregate.DatabaseSnapshotRepository\ndatabases:\n  default:\n    client: minos.plugins.aiopg.AiopgDatabaseClient\n    database: foobar_db\n    user: minos\n    password: min0s\n  saga:\n    client: minos.plugins.lmdb.LmdbDatabaseClient\n    path: \"./foobar.lmdb\"\ninterfaces:\n  broker:\n    port: minos.networks.BrokerPort\n    publisher:\n      client: minos.plugins.kafka.KafkaBrokerPublisher\n      queue: minos.networks.DatabaseBrokerPublisherQueue\n    subscriber:\n      client: minos.plugins.kafka.KafkaBrokerSubscriber\n      queue: minos.networks.DatabaseBrokerSubscriberQueue\n      validator: minos.networks.DatabaseBrokerSubscriberDuplicateValidator\n  http:\n    port: minos.networks.HttpPort\n    connector:\n      client: minos.plugins.aiohttp.AioHttpConnector\n      port: 4546\n  periodic:\n    port: minos.networks.PeriodicPort\npools:\n  lock: minos.common.DatabaseLockPool\n  database: minos.common.DatabaseClientPool\n  broker: minos.networks.BrokerClientPool\nsaga:\n  manager: minos.saga.SagaManager\nrouters:\n  - minos.networks.BrokerRouter\n  - minos.networks.PeriodicRouter\n  - minos.networks.RestHttpRouter\nmiddleware:\n  - minos.saga.transactional_command\nservices:\n  - minos.networks.SystemService\n  - minos.aggregate.TransactionService\n  - minos.aggregate.SnapshotService\n  - minos.saga.SagaService\n  - main.FooBarCommandService\n```\n\n\u003c/details\u003e\n\nHere is the `foobar/main.py` source file:\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show the full file\u003c/summary\u003e\n\n```python\nfrom __future__ import annotations\n\nfrom pathlib import Path\nfrom uuid import UUID\n\nfrom minos.aggregate import Aggregate, RootEntity\nfrom minos.common import EntrypointLauncher\nfrom minos.cqrs import CommandService\nfrom minos.networks import Request, Response, enroute\n\n\nclass FooBar(RootEntity):\n    \"\"\"FooBar Root Entity clas.\"\"\"\n\n    something: str\n\n\nclass FooBarAggregate(Aggregate[FooBar]):\n    \"\"\"FooBar Aggregate class.\"\"\"\n\n    @staticmethod\n    async def create_foobar(something: str) -\u003e UUID:\n        \"\"\"Create a new ``FooBar`` instance.\n        \n        :param something: The something attribute.\n        :return: The identifier of the new instance.\n        \"\"\"\n        foobar = await FooBar.create(something)\n        return foobar.uuid\n\n\nclass FooBarCommandService(CommandService):\n    \"\"\"Foo Command Service class.\"\"\"\n\n    @enroute.broker.command(\"CreateFooBar\")\n    async def create_foobar(self, request: Request) -\u003e Response:\n        \"\"\"Create a new FooBar.\n\n        :param request: The ``Request`` that contains the ``something`` attribute.\n        :return: A ``Response`` containing identifier of the already created instance.\n        \"\"\"\n        content = await request.content()\n        something = content[\"text\"]\n\n        uuid = await FooBarAggregate.create_foobar(something)\n\n        return Response(uuid)\n\n\nif __name__ == '__main__':\n    launcher = EntrypointLauncher.from_config(Path(__file__).parent / \"config.yml\")\n    launcher.launch()\n\n```\n\n\u003c/details\u003e\n\nExecute the following command to start the `foobar` microservice:\n\n```shell\npython foobar/main.py\n```\n\nTo check that everything works fine, execute the following command:\n\n```shell\ncurl --location --request POST 'http://localhost:4545/foos/add-foobar' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n    \"uuid\": \"YOUR_UUID\",\n    \"something\": \"something\"\n}'\n```\n\nThis request will start a new Saga, that sends a command to the `foobar` microservice, retrieve the `FooBar` identifier and update the `Foo` instance. After that, the `FooQueryService` will handle the update event and print a message similar to this one on the console.\n\n```\nThe 'foobar' field of a Foo was updated: Event(...)\n```\n\n## Packages\n\nThis project follows a modular structure based on python packages.\n\n### Core\n\nThe core packages provide the base implementation of the framework.\n\n* [minos-microservice-aggregate](https://github.com/minos-framework/minos-python/tree/main/packages/core/minos-microservice-aggregate): The Aggregate pattern implementation.\n* [minos-microservice-common](https://github.com/minos-framework/minos-python/tree/main/packages/core/minos-microservice-common): The common core package.\n* [minos-microservice-cqrs](https://github.com/minos-framework/minos-python/tree/main/packages/core/minos-microservice-cqrs): The CQRS pattern implementation.\n* [minos-microservice-networks](https://github.com/minos-framework/minos-python/tree/main/packages/core/minos-microservice-networks): The networks core package.\n* [minos-microservice-saga](https://github.com/minos-framework/minos-python/tree/main/packages/core/minos-microservice-saga): The SAGA pattern implementation.\n\n### Plugins\n\nThe plugin packages provide connectors to external technologies like brokers, discovery services, databases, serializers and so on.\n\n* [minos-broker-kafka](https://github.com/minos-framework/minos-python/tree/main/packages/plugins/minos-broker-kafka): The `kafka` plugin package.\n* [minos-broker-rabbitmq](https://github.com/minos-framework/minos-python/tree/main/packages/plugins/minos-broker-rabbitmq): The `rabbitmq` plugin package.\n* [minos-database-aiopg](https://github.com/minos-framework/minos-python/tree/main/packages/plugins/minos-database-aiopg): The `aiopg` plugin package.\n* [minos-database-lmdb](https://github.com/minos-framework/minos-python/tree/main/packages/plugins/minos-database-lmdb): The `lmdb` plugin package.\n* [minos-discovery-kong](https://github.com/minos-framework/minos-python/tree/main/packages/plugins/minos-discovery-kong): The `kong` plugin package.\n* [minos-discovery-minos](https://github.com/minos-framework/minos-python/tree/main/packages/plugins/minos-discovery-minos): The `minos-discovery` plugin package.\n* [minos-http-aiohttp](https://github.com/minos-framework/minos-python/tree/main/packages/plugins/minos-http-aiohttp): The `aiohttp` plugin package.\n* [minos-router-graphql](https://github.com/minos-framework/minos-python/tree/main/packages/plugins/minos-router-graphql): The `grapqhl` plugin package.\n\n## Source Code\n\nThe source code of this project is hosted at this [GitHub Repository](https://github.com/minos-framework/minos-python).\n\n## Getting Help\n\nFor usage questions, the best place to go to is [StackOverflow](https://stackoverflow.com/questions/tagged/minos).\n\n## Discussion and Development\n\nMost development discussions take place over this [GitHub Issues](https://github.com/minos-framework/minos-python/issues). In addition, a [Gitter channel](https://gitter.im/minos-framework/community) is available for development-related questions.\n\n## How to contribute\n\nWe are looking forward to having your contributions. No matter whether it is a pull request with new features, or the creation of an issue related to a bug you have found.\n\nPlease consider these guidelines before you submit any modification.\n\n### Create an issue\n\n1. If you happen to find a bug, please file a new issue filling the 'Bug report' template.\n2. Set the appropriate labels, so we can categorise it easily.\n3. Wait for any core developer's feedback on it.\n\n### Submit a Pull Request\n\n1. Create an issue following the previous steps.\n2. Fork the project.\n3. Push your changes to a local branch.\n4. Run the tests!\n5. Submit a pull request from your fork's branch.\n\n## License\n\nThis project is distributed under the [MIT](https://raw.githubusercontent.com/minos-framework/minos-python/main/LICENSE) license.\n","funding_links":[],"categories":["Python"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fminos-framework%2Fminos-python","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fminos-framework%2Fminos-python","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fminos-framework%2Fminos-python/lists"}