In the last projects that I’ve been involved with I’ve playing, in one way or another, with microservices, queues and things like that. I’m always facing the same tasks: Building RPCs, Workers, API gateways, … Because of that I’ve searching one framework to help me with those kind of stuff. Finally I discover Nameko. Basically Nameko is the Python tool that I’ve been looking for. In this post I will create a simple proof of concept to learn how to integrate Nameko within my projects. Let start.
The POC is a simple API gateway that gives me the localtime in iso format. I can create a simple Python script to do it
import datetime import time print(datetime.datetime.fromtimestamp(time()).isoformat())
We also can create a simple Flask API server to consume this information. The idea is create a rpc worker to generate this information and also generate another worker to send the localtime, but taken from a PostgreSQL database (yes I know it not very useful but it’s just an excuse to use a PG database in the microservice)
We’re going to create two rpc workers. One giving the local time:
from nameko.rpc import rpc from time import time import datetime class TimeService: name = "local_time_service" @rpc def local(self): return datetime.datetime.fromtimestamp(time()).isoformat()
And another one with the date from PostgreSQL:
from nameko.rpc import rpc from dotenv import load_dotenv import os from ext.pg import PgService current_dir = os.path.dirname(os.path.abspath(__file__)) load_dotenv(dotenv_path="{}/.env".format(current_dir)) class TimeService: name = "db_time_service" conn = PgService(os.getenv('DSN')) @rpc def db(self): with self.conn: with self.conn.cursor() as cur: cur.execute("select localtimestamp") timestamp = cur.fetchone() return timestamp[0]
I’ve created a service called PgService only to learn how to create dependency providers in nameko
from nameko.extensions import DependencyProvider import psycopg2 class PgService(DependencyProvider): def __init__(self, dsn): self.dsn = dsn def get_dependency(self, worker_ctx): return psycopg2.connect(self.dsn)
Now we only need to setup the api gateway. With Nameko we can create http entrypoint also (in the same way than we create rpc) but I want to use it with Flask
from flask import Flask from nameko.standalone.rpc import ServiceRpcProxy from dotenv import load_dotenv import os current_dir = os.path.dirname(os.path.abspath(__file__)) load_dotenv(dotenv_path="{}/.env".format(current_dir)) app = Flask(__name__) def rpc_proxy(service): config = {'AMQP_URI': os.getenv('AMQP_URI')} return ServiceRpcProxy(service, config) @app.route('/') def hello(): return "Hello" @app.route('/local') def local_time(): with rpc_proxy('local_time_service') as rpc: time = rpc.local() return time @app.route('/db') def db_time(): with rpc_proxy('db_time_service') as rpc: time = rpc.db() return time if __name__ == '__main__': app.run()
As well as I wanna run my POC with docker, here the docker-compose file to set up the project
version: '3.4' services: api: image: nameko/api container_name: nameko.api hostname: api ports: - "8080:8080" restart: always links: - rabbit - db.worker - local.worker environment: - ENV=1 - FLASK_APP=app.py - FLASK_DEBUG=1 build: context: ./api dockerfile: .docker/Dockerfile-api #volumes: #- ./api:/usr/src/app:ro command: flask run --host=0.0.0.0 --port 8080 db.worker: container_name: nameko.db.worker image: nameko/db.worker restart: always build: context: ./workers/db.worker dockerfile: .docker/Dockerfile-worker command: /bin/bash run.sh local.worker: container_name: nameko.local.worker image: nameko/local.worker restart: always build: context: ./workers/local.worker dockerfile: .docker/Dockerfile-worker command: /bin/bash run.sh rabbit: container_name: nameko.rabbit image: rabbitmq:3-management restart: always ports: - "15672:15672" - "5672:5672" environment: RABBITMQ_ERLANG_COOKIE: RABBITMQ_DEFAULT_VHOST: / RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER} RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS} pg: container_name: nameko.pg image: nameko/pg restart: always build: context: ./pg dockerfile: .docker/Dockerfile-pg #ports: #- "5432:5432" environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_DB: ${POSTGRES_DB} PGDATA: /var/lib/postgresql/data/pgdata
And that’s all. Two nameko rpc services working together behind a api gateway
Code available in my github
Thanks a ton
Thanks a ton, I was scratching my head, and was about to rewrite the entire thing using spring, this is way better 🙂