Building a microservice
Building blocks for a service class and microservice entrypoint
import tomodachiand create a class that extends
tomodachi.Service– it can be called anything or just
Serviceto keep it simple and clean.
- Add a
nameattribute to the class and give it a string value. Having a
nameattribute 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 by
- 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 a
200 OKwith 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,
- By using
@tomodachi.httpan HTTP server backed by
aiohttpwill be started on service init.
tomodachiwill 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 <path to .py file with service class code> # 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 over 2 years ago