Skip to content

Commit 052fe3d

Browse files
author
Sergio García Prado
committed
ISSUE #123
* Add `Installation` and `Usage` sections.
1 parent c1627eb commit 052fe3d

File tree

1 file changed

+174
-14
lines changed

1 file changed

+174
-14
lines changed

README.md

Lines changed: 174 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,188 @@
1-
21
<p align="center">
32
<a href="http://minos.run" target="_blank"><img src="https://raw.githubusercontent.com/minos-framework/.github/main/images/logo.png" alt="Minos logo"></a>
43
</p>
54

6-
7-
85
# minos-python: The framework which helps you create reactive microservices in Python
96

10-
[![PyPI Latest Release](https://img.shields.io/pypi/v/minos-microservice-aggregate.svg?label=minos-microservice-aggregate)](https://pypi.org/project/minos-microservice-aggregate/)
11-
[![PyPI Latest Release](https://img.shields.io/pypi/v/minos-microservice-common.svg?label=minos-microservice-common)](https://pypi.org/project/minos-microservice-common/)
12-
[![PyPI Latest Release](https://img.shields.io/pypi/v/minos-microservice-cqrs.svg?label=minos-microservice-cqrs)](https://pypi.org/project/minos-microservice-cqrs/)
13-
[![PyPI Latest Release](https://img.shields.io/pypi/v/minos-microservice-networks.svg?label=minos-microservice-networks)](https://pypi.org/project/minos-microservice-networks/)
14-
[![PyPI Latest Release](https://img.shields.io/pypi/v/minos-microservice-saga.svg?label=minos-microservice-saga)](https://pypi.org/project/minos-microservice-saga/)
7+
[![PyPI Latest Release](https://img.shields.io/pypi/v/minos-microservice-common.svg)](https://pypi.org/project/minos-microservice-common/)
158
[![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)
169
[![License](https://img.shields.io/github/license/minos-framework/minos-python.svg)](https://github.com/minos-framework/minos-python/blob/main/LICENSE)
1710
[![Coverage](https://codecov.io/github/minos-framework/minos-python/coverage.svg?branch=main)](https://codecov.io/gh/minos-framework/minos-python)
1811
[![Stack Overflow](https://img.shields.io/badge/Stack%20Overflow-Ask%20a%20question-green)](https://stackoverflow.com/questions/tagged/minos)
1912
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/minos-framework/community)
20-
[![](https://tokei.rs/b1/github/minos-framework/minos-python?category=code)](https://github.com/minos-framework/minos-python)
13+
[![Tokei](https://tokei.rs/b1/github/minos-framework/minos-python?category=code)](https://github.com/minos-framework/minos-python)
2114

2215
## Summary
2316

24-
Minos is a framework which helps you create [reactive](https://www.reactivemanifesto.org/) microservices in Python.
25-
Internally, it leverages Event Sourcing, CQRS and a message driven architecture to fulfil the commitments of an
26-
asynchronous environment.
17+
Minos 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.
18+
19+
## Installation
20+
21+
### Guided installation
22+
23+
The easiest way to use `minos` is with the help of the [`minos-cli`](https://github.com/minos-framework/minos-cli), 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.)
24+
25+
### Manual installation
26+
27+
If you want to directly use `minos` without the command-line utility, the following command will install the needed packages:
28+
29+
```shell
30+
pip install \
31+
minos-microservice-aggregate \
32+
minos-microservice-common \
33+
minos-microservice-cqrs \
34+
minos-microservice-networks \
35+
minos-microservice-saga
36+
```
37+
38+
## Usage
39+
40+
This section includes a set of minimal examples of how-to-work with `minos`, so that anyone can get the gist of the framework.
41+
42+
### Create an Aggregate
43+
44+
Here 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.
45+
46+
```python
47+
from __future__ import annotations
48+
from typing import Optional
49+
from minos.aggregate import Aggregate, AggregateRef, ModelRef
50+
51+
52+
class Foo(Aggregate):
53+
"""Foo Aggregate class."""
54+
55+
bar: str
56+
foobar: Optional[ModelRef[FooBar]]
57+
58+
59+
class FooBar(AggregateRef):
60+
"""FooBar AggregateRef clas."""
61+
62+
something: str
63+
```
64+
65+
### Expose a Command
66+
67+
Here 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).
68+
69+
```python
70+
from minos.cqrs import CommandService
71+
from minos.networks import enroute, Request, Response
72+
73+
74+
class FooCommandService(CommandService):
75+
"""Foo Command Service class."""
76+
77+
@enroute.broker.command("CreateFoo")
78+
@enroute.rest.command("/foos", "POST")
79+
async def create_foo(self, request: Request) -> Response:
80+
"""Create a new Foo.
81+
82+
:param request: The ``Request`` that contains the ``bar`` attribute.
83+
:return: A ``Response`` containing identifier of the already created instance.
84+
"""
85+
content = await request.content()
86+
bar = content["bar"]
87+
88+
foo = await Foo.create(bar)
89+
90+
return Response({"uuid": foo.uuid})
91+
```
92+
93+
### Subscribe to an Event and Expose a Query
94+
95+
Here 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"` event is handled (and only a `print` of the content is performed). 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.
96+
97+
*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.
98+
99+
```python
100+
from minos.cqrs import QueryService
101+
from minos.networks import enroute, Request, Response
102+
103+
104+
class FooQueryService(QueryService):
105+
"""Foo Query Service class."""
106+
107+
@enroute.broker.event("FooCreated")
108+
async def foo_created(self, request: Request) -> None:
109+
"""Handle the "FooCreated" event.
110+
111+
:param request: The ``Request`` that contains the ``bar`` attribute.
112+
:return: This method does not return anything.
113+
"""
114+
diff = await request.content()
115+
print(f"A Foo was created: {diff}")
116+
117+
@enroute.rest.query("/foos/example", "GET")
118+
async def example(self, request: Request) -> Response:
119+
"""Handle the example query.
120+
121+
:param request: The ``Request`` that contains the necessary information.
122+
:return: A ``Response`` instance.
123+
"""
124+
return Response("This is an example response!")
125+
```
126+
127+
### Interact with another Microservice
128+
129+
Here is an example of the interaction between two microservices through a SAGA pattern. In this case, the interaction starts with a call to the `"/foo/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.
130+
131+
```python
132+
from minos.common import ModelType
133+
from minos.cqrs import CommandService
134+
from minos.networks import enroute, Request
135+
from minos.saga import Saga, SagaContext, SagaRequest, SagaResponse
136+
137+
CreateFooBarDTO = ModelType.build("AnotherDTO", {"number": int, "text": str})
138+
139+
140+
def _create_foobar(context: SagaContext) -> SagaRequest:
141+
something = context["something"]
142+
content = CreateFooBarDTO(56, something)
143+
return SagaRequest("CreateFooBar", content)
144+
145+
146+
async def _success_foobar(context: SagaContext, response: SagaResponse) -> SagaContext:
147+
context["foobar_uuid"] = await response.content()
148+
return context
149+
150+
151+
async def _error_foobar(context: SagaContext, response: SagaResponse) -> SagaContext:
152+
raise ValueError("The foobar could not be created!")
153+
154+
155+
async def _update_foo(context: SagaContext) -> None:
156+
foo = await Foo.get(context["uuid"])
157+
foo.foobar = context["foobar_uuid"]
158+
await foo.save()
159+
160+
161+
ADD_FOOBAR_SAGA = (
162+
Saga()
163+
.remote_step().on_execute(_create_foobar).on_success(_success_foobar).on_error(_error_foobar)
164+
.local_step().on_execute(_update_foo)
165+
.commit()
166+
)
167+
168+
169+
class FooCommandService(CommandService):
170+
"""Foo Command Service class."""
171+
172+
@enroute.rest.command("/foo/add-foobar", "POST")
173+
async def update_foo(self, request: Request) -> None:
174+
"""Run a saga example.
175+
176+
:param request: The ``Request`` that contains the initial saga's context.
177+
:return: This method does not return anything.
178+
"""
179+
content = await request.content()
180+
181+
await self.saga_manager.run(
182+
ADD_FOOBAR_SAGA, {"uuid": content["uuid"], "something": content["something"]}
183+
)
184+
185+
```
27186

28187
## Documentation
29188

@@ -45,17 +204,18 @@ The core packages provide the base implementation of the framework.
45204

46205
### Plugins
47206

48-
The plugin packages provide connectors to external technologies like brokers, discovery services, databases, serializers and so on.
207+
The plugin packages provide connectors to external technologies like brokers, discovery services, databases, serializers and so on.
49208

50209
## Source Code
51210

52-
The source code of this project is hosted at [GitHub](https://github.com/minos-framework/minos-python).
211+
The source code of this project is hosted at [GitHub](https://github.com/minos-framework/minos-python).
53212

54213
## Getting Help
55214

56215
For usage questions, the best place to go to is [StackOverflow](https://stackoverflow.com/questions/tagged/minos).
57216

58217
## Discussion and Development
218+
59219
Most development discussions take place over the [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.
60220

61221
## How to contribute

0 commit comments

Comments
 (0)