Building a microservice
Building blocks for a service class and microservice entrypoint
import tomodachi
and create a class that extendstomodachi.Service
– it can be called anything or justService
to keep it simple and clean.- Add a
name
attribute to the class and give it a string value. Having aname
attribute isn't required, but good practice and can be included in your log output. - Define an awaitable function (using
async def
) in the service class – in this example we'll use it as an entrypoint to trigger code in the service by decorating it with one of the available invoker decorators. Note that a service class must have at least one decorated function available, for it to even be recognized as a service bytomodachi run
. - Decide on how to trigger the function – for example using HTTP, pub/sub or on a timed interval, then decorate your function with one of these trigger / subscription decorators, which also invokes what capabilities the service initially has.
Read more about how each of the built-in invoker decorators work and which keywords and parameters you can use to change their behaviour.
Note: Publishing and subscribing to events and messages may require user credentials or hosting configuration to be able to access queues and topics.
For simplicity, let's do HTTP:
- On each POST request to
/sheep
, the service will wait for up to one whole second (pretend that it's performing I/O – waiting for a response to a slow sheep counting database modification, for example) and then issue a200 OK
with some data. - It's also possible to query the amount of times the POST tasks has run by doing a GET request to the same url,
/sheep
. - By using
@tomodachi.http
an HTTP server backed byaiohttp
will be started on service init.tomodachi
will act as a middleware to route requests to the correct handlers, upgrade websocket connections and later also gracefully await active connections with still executing tasks, when the service is asked to stop – up until a configurable amount of time has passed. So called graceful termination.
import asyncio
import random
import tomodachi
class Service(tomodachi.Service):
name = "sleepy-sheep-counter"
_sheep_count = 0
@tomodachi.http("POST", r"/sheep")
async def add_to_sheep_count(self, request):
await asyncio.sleep(random.random())
self._sheep_count += 1
return 200, str(self._sheep_count)
@tomodachi.http("GET", r"/sheep")
async def return_sheep_count(self, request):
return 200, str(self._sheep_count)
# generic execution of services
app/ $ tomodachi run <service.py ...>
# example: if our service class exists in service/app.py
app/ $ tomodachi run service/app.py
Beside the currently existing built-in ways of interfacing with a service, it's also possible to build custom function decorators for the use-cases one may have.
To give a few possible examples / ideas of functionality that could be coded to call functions with data in similar ways:
- Using Redis as a task queue with configurable keys to push or pop onto.
- Subscribing to Kinesis or Kafka event streams and act on the data received.
- An abstraction around otherwise complex functionality or to unify API design.
- As an example to above sentence; GraphQL resolver functionality with built-in tracability and authentication management, with a unified API to application devs.
Updated 9 months ago
Related sections