Microservice in Python+ FastAPI

Introduction to Microservices

Microservice is an approach to breaking a large monolithic application into individual applications that specialize in a specific service/function. This approach is often called service-oriented architecture or SOA.

IN monolithic architecture each business logic resides in one application. Application services such as user management, authentication, and other functions use the same database.

IN microservice architecture The application is split into several separate services that run in separate processes. There is a different database for different functions of the application and the services communicate with each other using HTTP, AMQP or a binary protocol such as TCP depending on the nature of each service. Inter-service communication can also be done using message queues such as RabbitMQ , Kafka or Redis .

Benefits of Microservice

Microservice architecture has many advantages. Some of these benefits:

  • A loosely coupled application means that different services can be created using the technologies that suit them best. This way, the development team is not limited by the choices made when starting the project.

  • Since services are responsible for specific functionality, which simplifies understanding and control over the application.

  • Scaling applications also becomes easier because if one of the services requires high GPU usage, then only the server on which that service needs to have a high GPU, while others can run on a regular server.

Disadvantages of microservice

Microservice architecture is not a panacea that will solve all your problems. She also has her shortcomings. Some of these disadvantages:

  • Because different services use different databases, transactions involving more than one service must use eventual consistency.

  • Perfect service separation is very difficult to achieve on the first try and needs to be repeated before achieving the best service separation.

  • Since services communicate with each other through network communication, it slows down the application due to network latency and slow service.

Why Microservice in Python

Python is an ideal tool for creating microservices because it has a great community, ease of learning, and many libraries.

Introduction to FastAPI

FastAPI is a modern, high-performance web framework that comes with a lot of cool features like OpenAPI-based automated documentation and a built-in serialization and validation library. Here you will find a list of all the cool FastAPI features.

Why FastAPI

Some of the reasons why I think FastAPI is a great choice for building microservices in Python are:

  • Auto documentation

  • Async/wait support

  • Built-in validation and serialization

  • The type is 100% annotated, so autocompletion works great.

Installing FastAPI

Before installing FastAPI, create a new directory movie_serviceand create a new virtual environment inside the newly created directory using virtualenv .
If you haven't installed it yet virtualenv:

pip install virtualenv

Now create a new virtual environment.

virtualenv env

If you are using Mac/Linux, you can activate the virtual environment using the command:

source ./env/bin/activate

Windows users can run this command instead:

.\env\Scripts\activate

Finally, you are ready to install FastAPI, run the following command:

pip install fastapi

Since FastAPI does not have a built-in service, uvicornto run it you need to install it. uvicorn– this is the server ASGI which allows us to use async/await functions.
Install uvicornusing the command

pip install uvicorn

Creating a Simple REST API Using FastAPI

Before we start building a microservice using FastAPI, let's learn the basics of FastAPI. Create a new directory appand a new file main.pyinside the newly created directory.

Add the following code to main.py.

#~/movie_service/app/main.py

from fastapi import FastAPI

app = FastAPI()


@app.get('/')
async def index():
    return {"Real": "Python"}

Here you first import and instantiate FastAPI and then register the root endpoint /which then returns the file JSON.

You can start the application server using uvicorn app.main:app --reload. Here app.mainindicates what you are using main.pyfile inside appcatalogue, and :appthe name of our FastAPIcopy.

You can access the application at http://127.0.0.1:8000 . To access interesting automated documentation, go to http://127.0.0.1:8000/docs . You can experiment and interact with your API from within the browser itself.

Let's add some CRUD functionality to our application.
Update your file main.pyso it looks like this:

#~/movie_service/app/main.py

from fastapi import FastAPI
from pydantic import BaseModel
from typing import List

app = FastAPI()

fake_movie_db = [
    {
        'name': 'Star Wars: Episode IX - The Rise of Skywalker',
        'plot': 'The surviving members of the resistance face the First Order once again.',
        'genres': ['Action', 'Adventure', 'Fantasy'],
        'casts': ['Daisy Ridley', 'Adam Driver']
    }
]

class Movie(BaseModel):
    name: str
    plot: str
    genres: List[str]
    casts: List[str]


@app.get('/', response_model=List[Movie])
async def index():
    return fake_movie_db

As you can see, you have created a new class Moviewhich is a continuation BaseModelpydantic.
Model Moviecontains title, photo, genres and cast. Joining Pydantic FastAPI is built in, making it easy to create models and validate requests.

If you go to the documentation site, you will see that the fields of our Movies model have already been mentioned in the example answer section. This is possible because you specified response_modelin our route definition.

Now let's add an endpoint to add the movie to our movie list.

Add a new endpoint definition for processing POSTrequest.

@app.post('/', status_code=201)
async def add_movie(payload: Movie):
    movie = payload.dict()
    fake_movie_db.append(movie)
    return {'id': len(fake_movie_db) - 1}

Now go to your browser and test the new API. Try adding a movie with an invalid field or no required fields and make sure that the validation is automatically performed by FastAPI.

Let's add a new endpoint to update the movie.

@app.put('/{id}')
async def update_movie(id: int, payload: Movie):
    movie = payload.dict()
    movies_length = len(fake_movie_db)
    if 0 <= id <= movies_length:
        fake_movie_db[id] = movie
        return None
    raise HTTPException(status_code=404, detail="Movie with given id not found")

Here idindex of our fake_movie_dblist.

Note. Don't forget to import HTTPExceptionfromfastapi

Now you can also add an endpoint for deleting a movie.

@app.delete('/{id}')
async def delete_movie(id: int):
    movies_length = len(fake_movie_db)
    if 0 <= id <= movies_length:
        del fake_movie_db[id]
        return None
    raise HTTPException(status_code=404, detail="Movie with given id not found")

Before moving further, let's structure our application better. apiCreate a new folder inside appand create a new file movies.pyinside the newly created folder. Move all route related codes from main.pyV movies.py. So, movies.pyshould look like this:

#~/movie-service/app/api/movies.py

from typing import List
from fastapi import Header, APIRouter

from app.api.models import Movie

fake_movie_db = [
    {
        'name': 'Star Wars: Episode IX - The Rise of Skywalker',
        'plot': 'The surviving members of the resistance face the First Order once again.',
        'genres': ['Action', 'Adventure', 'Fantasy'],
        'casts': ['Daisy Ridley', 'Adam Driver']
    }
]

movies = APIRouter()

@movies.get('/', response_model=List[Movie])
async def index():
    return fake_movie_db

@movies.post('/', status_code=201)
async def add_movie(payload: Movie):
    movie = payload.dict()
    fake_movie_db.append(movie)
    return {'id': len(fake_movie_db) - 1}

@movies.put('/{id}')
async def update_movie(id: int, payload: Movie):
    movie = payload.dict()
    movies_length = len(fake_movie_db)
    if 0 <= id <= movies_length:
        fake_movie_db[id] = movie
        return None
    raise HTTPException(status_code=404, detail="Movie with given id not found")

@movies.delete('/{id}')
async def delete_movie(id: int):
    movies_length = len(fake_movie_db)
    if 0 <= id <= movies_length:
        del fake_movie_db[id]
        return None
    raise HTTPException(status_code=404, detail="Movie with given id not found")

Here you have registered a new API route using APIRouter from FastAPI.

Also, create a new file, models.pyV apiwhere you will store our Pydantic models.

#~/movie-service/api/models.py

from typing import List
from pydantic import BaseModel

class Movie(BaseModel):
    name: str
    plot: str
    genres: List[str]
    casts: List[str]

Now register this new routes file inmain.py

#~/movie-service/app/main.py
from fastapi import FastAPI

from app.api.movies import movies

app = FastAPI()

app.include_router(movies)

Finally, our application's directory structure looks like this:

movie-service
├── app
│   ├── api
│   │   ├── models.py
│   │   ├── movies.py
│   |── main.py
└── env

Before moving forward, make sure your application is working correctly.

Using a PostgreSQL Database with FastAPI

Previously, you used a fake Python list to add movies, but now you are finally ready to use a real database for this purpose. For this purpose you are going to use PostgreSQL . Install PostgreSQL if you haven't already done so. After installing PostgreSQl create a new database, I'll call mine movie_db.

Are you going to use encoding/database to connect to the database asyncAnd awaither support. Find out more about async/awaitPython Here

Install the required library using:

pip install 'databases[postgresql]'

will be installed sqlalchemyAnd asyncpgnecessary for working with PostgreSQL.

Create a new file inside apiand name it db.py. This file will contain the actual database model for our REST API.

#~/movie-service/app/api/db.py

from sqlalchemy import (Column, Integer, MetaData, String, Table,
                        create_engine, ARRAY)

from databases import Database

DATABASE_URL = 'postgresql://movie_user:movie_password@localhost/movie_db'

engine = create_engine(DATABASE_URL)
metadata = MetaData()

movies = Table(
    'movies',
    metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(50)),
    Column('plot', String(250)),
    Column('genres', ARRAY(String)),
    Column('casts', ARRAY(String))
)

database = Database(DATABASE_URL)

Here DATABASE_URIThe URL used to connect to the PostgreSQL database. Here movie_userthe database user name is specified, movie_passworddatabase user password and movie_dbdatabase name.

Exactly the same as in SQLAlchemy You have created a table for a movie database.

Update main.pyto connect to the database. main.pyshould look like this:

#~/movie-service/app/main.py

from fastapi import FastAPI
from app.api.movies import movies
from app.api.db import metadata, database, engine

metadata.create_all(engine)

app = FastAPI()

@app.on_event("startup")
async def startup():
    await database.connect()

@app.on_event("shutdown")
async def shutdown():
    await database.disconnect()


app.include_router(movies)

FastAPI provides some event handlers that you can use to connect to our database when the application starts and disconnect when it terminates.

Update movies.pyso that it uses the database instead of the fake Python list.

#~/movie-service/app/api/movies.py


from typing import List
from fastapi import Header, APIRouter

from app.api.models import MovieIn, MovieOut
from app.api import db_manager

movies = APIRouter()

@movies.get('/', response_model=List[MovieOut])
async def index():
    return await db_manager.get_all_movies()

@movies.post('/', status_code=201)
async def add_movie(payload: MovieIn):
    movie_id = await db_manager.add_movie(payload)
    response = {
        'id': movie_id,
        **payload.dict()
    }

    return response

@movies.put('/{id}')
async def update_movie(id: int, payload: MovieIn):
    movie = payload.dict()
    fake_movie_db[id] = movie
    return None

@movies.put('/{id}')
async def update_movie(id: int, payload: MovieIn):
    movie = await db_manager.get_movie(id)
    if not movie:
        raise HTTPException(status_code=404, detail="Movie not found")

    update_data = payload.dict(exclude_unset=True)
    movie_in_db = MovieIn(**movie)

    updated_movie = movie_in_db.copy(update=update_data)

    return await db_manager.update_movie(id, updated_movie)

@movies.delete('/{id}')
async def delete_movie(id: int):
    movie = await db_manager.get_movie(id)
    if not movie:
        raise HTTPException(status_code=404, detail="Movie not found")
    return await db_manager.delete_movie(id)

Let's add db_manager.pythe ability to manipulate our database.

#~/movie-service/app/api/db_manager.py

from app.api.models import MovieIn, MovieOut, MovieUpdate
from app.api.db import movies, database


async def add_movie(payload: MovieIn):
    query = movies.insert().values(**payload.dict())

    return await database.execute(query=query)

async def get_all_movies():
    query = movies.select()
    return await database.fetch_all(query=query)

async def get_movie(id):
    query = movies.select(movies.c.id==id)
    return await database.fetch_one(query=query)

async def delete_movie(id: int):
    query = movies.delete().where(movies.c.id==id)
    return await database.execute(query=query)

async def update_movie(id: int, payload: MovieIn):
    query = (
        movies
        .update()
        .where(movies.c.id == id)
        .values(**payload.dict())
    )
    return await database.execute(query=query)

Let's update our system models.pyso you can use the Pydantic model with a sqlalchemy table.

#~/movie-service/app/api/models.py

from pydantic import BaseModel
from typing import List, Optional

class MovieIn(BaseModel):
    name: str
    plot: str
    genres: List[str]
    casts: List[str]


class MovieOut(MovieIn):
    id: int


class MovieUpdate(MovieIn):
    name: Optional[str] = None
    plot: Optional[str] = None
    genres: Optional[List[str]] = None
    casts: Optional[List[str]] = None

Here MovieInthe base model you use to add a movie to the database. You need to add idto this model, receiving it from the database, hence the model MovieOut. MovieUpdateThe model allows us to make the values ​​in the model optional so that when we update the movie, only the field that needs to be updated can be sent.

Now go to the browser documentation site and start experimenting with the API.

Microservices Data Management Patterns

Managing data in a microservice is one of the most challenging aspects of creating a microservice. Since different functions of the application are performed by different services, using the database can be difficult.

Here are some patterns you can use to control the flow of data in your application.

Service database

Using a per-service database is great if you want your microservices to be as loosely coupled as possible. Having a separate database for each service allows us to scale different services independently. Transactions involving multiple databases are performed through well-defined APIs. This has its drawback because implementing business transactions involving multiple services is not a simple task. Additionally, the additional network overhead makes it less efficient to use.

Shared Database

If there are many transactions involving multiple services, it is better to use a common database. This provides the benefits of a highly consistent application, but removes most of the benefits of a microservices architecture. Developers working on one service need to coordinate schema changes in other services.

API composition

In transactions involving multiple databases, the API composer acts as an API gateway and makes API calls to other microservices in the required order. Finally, the results of each microservice are returned to the client service after the in-memory connection is completed. The disadvantage of this approach is that it is inefficient to combine large data sets in memory.

Creating a Python microservice in Docker

The hassle of deploying a microservice can be greatly reduced by using Docker. Docker helps you encapsulate each service and scale it independently.

Installing Docker and Docker Compose

If you haven't yet installed docker into your system. Make sure docker is installed by running the command docker. After Docker installation is complete install Docker Compose . Docker Compose is used to define and run multiple Docker containers. This also helps facilitate interaction between them.

Creating a Movie Service

Since most of the work to create a movie service has already been done when you start working with FastAPI, you will have to reuse the code you've already written. Create a new folder, I'll call mine python-microservices. Move the code that you wrote earlier and which I called movie-service.
So, the folder structure will look like this:

python-microservices/
└── movie-service/
    ├── app/
    └── env/

First of all, let's create requirements.txtfile in which you will store all the dependencies that you are going to use in our movie-service. Create inside
new file and add the following to it:requirements.txtmovie-service

asyncpg==0.20.1
databases[postgresql]==0.2.6
fastapi==0.48.0
SQLAlchemy==1.3.13
uvicorn==0.11.2
httpx==0.11.1

Have you used all the libraries mentioned there except httpx which you are going to use when making an API call between services.

Create Dockerfileinner part movie-servicewith the following content:

FROM python:3.8-slim

WORKDIR /app

COPY ./requirements.txt /app/requirements.txt

RUN apt-get update \
    && apt-get install gcc -y \
    && apt-get clean

RUN pip install -r /app/requirements.txt \
    && rm -rf /root/.cache/pip

COPY . /app/

Here, first you determine which version of Python you want to use. Then install WORKDIRfolder appinside a Docker container. After that gccit installs what is required by the libraries you use in the application.
Finally, install all dependencies requirements.txtand copy all the files inside movie-service/app.

Update db.pyand replace

DATABASE_URI = 'postgresql://movie_user:movie_password@localhost/movie_db'

With

DATABASE_URI = os.getenv('DATABASE_URI')

Note. Don't forget to import osto the beginning of the file.

You need to do this so you can provide it later DATABASE_URIas an environment variable.

Also update main.pyand replace

app.include_router(movies)

With

app.include_router(movies, prefix='/api/v1/movies', tags=['movies'])

Here you added prefix /api/v1/moviesso that managing different API versions becomes easier. Additionally, tags make it easier to find APIs moviesin the FastAPI documentation.

In addition, you need to update our models so that they caststhe actor's ID was stored, not the actual name. So update the file, models.pyso it looks like this:

#~/python-microservices/movie-service/app/api/models.py

from pydantic import BaseModel
from typing import List, Optional

class MovieIn(BaseModel):
    name: str
    plot: str
    genres: List[str]
    casts_id: List[int]


class MovieOut(MovieIn):
    id: int


class MovieUpdate(MovieIn):
    name: Optional[str] = None
    plot: Optional[str] = None
    genres: Optional[List[str]] = None
    casts_id: Optional[List[int]] = None

Similarly, you need to update the database tables, let's update db.py:

#~/python-microservices/movie-service/app/api/db.py

import os

from sqlalchemy import (Column, DateTime, Integer, MetaData, String, Table,
                        create_engine, ARRAY)

from databases import Database

DATABASE_URL = os.getenv('DATABASE_URL')

engine = create_engine(DATABASE_URL)
metadata = MetaData()

movies = Table(
    'movies',
    metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(50)),
    Column('plot', String(250)),
    Column('genres', ARRAY(String)),
    Column('casts_id', ARRAY(Integer))
)

database = Database(DATABASE_URL)

Now update, movies.pyto check if a cast with a given ID is present in the casting service before adding a new movie or updating a movie.

#~/python-microservices/movie-service/app/api/movies.py

from typing import List
from fastapi import APIRouter, HTTPException

from app.api.models import MovieOut, MovieIn, MovieUpdate
from app.api import db_manager
from app.api.service import is_cast_present

movies = APIRouter()

@movies.post('/', response_model=MovieOut, status_code=201)
async def create_movie(payload: MovieIn):
    for cast_id in payload.casts_id:
        if not is_cast_present(cast_id):
            raise HTTPException(status_code=404, detail=f"Cast with id:{cast_id} not found")

    movie_id = await db_manager.add_movie(payload)
    response = {
        'id': movie_id,
        **payload.dict()
    }

    return response

@movies.get('/', response_model=List[MovieOut])
async def get_movies():
    return await db_manager.get_all_movies()

@movies.get('/{id}/', response_model=MovieOut)
async def get_movie(id: int):
    movie = await db_manager.get_movie(id)
    if not movie:
        raise HTTPException(status_code=404, detail="Movie not found")
    return movie

@movies.put('/{id}/', response_model=MovieOut)
async def update_movie(id: int, payload: MovieUpdate):
    movie = await db_manager.get_movie(id)
    if not movie:
        raise HTTPException(status_code=404, detail="Movie not found")

    update_data = payload.dict(exclude_unset=True)

    if 'casts_id' in update_data:
        for cast_id in payload.casts_id:
            if not is_cast_present(cast_id):
                raise HTTPException(status_code=404, detail=f"Cast with given id:{cast_id} not found")

    movie_in_db = MovieIn(**movie)

    updated_movie = movie_in_db.copy(update=update_data)

    return await db_manager.update_movie(id, updated_movie)

@movies.delete('/{id}', response_model=None)
async def delete_movie(id: int):
    movie = await db_manager.get_movie(id)
    if not movie:
        raise HTTPException(status_code=404, detail="Movie not found")
    return await db_manager.delete_movie(id)

Let's add a service to call the API for the broadcast service:

#~/python-microservices/movie-service/app/api/service.py

import os
import httpx

CAST_SERVICE_HOST_URL = 'http://localhost:8002/api/v1/casts/'
url = os.environ.get('CAST_SERVICE_HOST_URL') or CAST_SERVICE_HOST_URL

def is_cast_present(cast_id: int):
    r = httpx.get(f'{url}{cast_id}')
    return True if r.status_code == 200 else False

You make an API call to get a cast with a given ID, and return true if the cast exists, false otherwise.

Creating a Casts Service

As with the file movie-servicefor creating casts-serviceyou will be using a FastAPI and PostgreSQL database.

Create a folder structure similar to the following:

python-microservices/
.
├── cast_service/
│   ├── app/
│   │   ├── api/
│   │   │   ├── casts.py
│   │   │   ├── db_manager.py
│   │   │   ├── db.py
│   │   │   ├── models.py
│   │   ├── main.py
│   ├── Dockerfile
│   └── requirements.txt
├── movie_service/
...

Add the following to requirements.txt:

asyncpg==0.20.1
databases[postgresql]==0.2.6
fastapi==0.48.0
SQLAlchemy==1.3.13
uvicorn==0.11.2

Dockerfile:

FROM python:3.8-slim

WORKDIR /app

COPY ./requirements.txt /app/requirements.txt

RUN apt-get update \
    && apt-get install gcc -y \
    && apt-get clean

RUN pip install -r /app/requirements.txt \
    && rm -rf /root/.cache/pip

COPY . /app/

main.py

#~/python-microservices/cast-service/app/main.py

from fastapi import FastAPI
from app.api.casts import casts
from app.api.db import metadata, database, engine

metadata.create_all(engine)

app = FastAPI()

@app.on_event("startup")
async def startup():
    await database.connect()

@app.on_event("shutdown")
async def shutdown():
    await database.disconnect()

app.include_router(casts, prefix='/api/v1/casts', tags=['casts'])

You have added a prefix, /api/v1/caststo make API management easier. Moreover, adding simplifies tagssearch for documents related to castsFastAPI documents.

casts.py

#~/python-microservices/cast-service/app/api/casts.py

from fastapi import APIRouter, HTTPException
from typing import List

from app.api.models import CastOut, CastIn, CastUpdate
from app.api import db_manager

casts = APIRouter()

@casts.post('/', response_model=CastOut, status_code=201)
async def create_cast(payload: CastIn):
    cast_id = await db_manager.add_cast(payload)

    response = {
        'id': cast_id,
        **payload.dict()
    }

    return response

@casts.get('/{id}/', response_model=CastOut)
async def get_cast(id: int):
    cast = await db_manager.get_cast(id)
    if not cast:
        raise HTTPException(status_code=404, detail="Cast not found")
    return cast

db_manager.py

#~/python-microservices/cast-service/app/api/db_manager.py

from app.api.models import CastIn, CastOut, CastUpdate
from app.api.db import casts, database


async def add_cast(payload: CastIn):
    query = casts.insert().values(**payload.dict())

    return await database.execute(query=query)

async def get_cast(id):
    query = casts.select(casts.c.id==id)
    return await database.fetch_one(query=query)

db.py

#~/python-microservices/cast-service/app/api/db.py

import os

from sqlalchemy import (Column, Integer, MetaData, String, Table,
                        create_engine, ARRAY)

from databases import Database

DATABASE_URI = os.getenv('DATABASE_URI')

engine = create_engine(DATABASE_URI)
metadata = MetaData()

casts = Table(
    'casts',
    metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(50)),
    Column('nationality', String(20)),
)

database = Database(DATABASE_URI)

models.py

#~/python-microservices/cast-service/app/api/models.py

from pydantic import BaseModel
from typing import List, Optional

class CastIn(BaseModel):
    name: str
    nationality: Optional[str] = None


class CastOut(CastIn):
    id: int


class CastUpdate(CastIn):
    name: Optional[str] = None

Running a microservice using Docker Compose

To run microservices, create docker-compose.ymlfile and add the following to it:

version: '3.7'

services:
  movie_service:
    build: ./movie-service
    command: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
    volumes:
      - ./movie-service/:/app/
    ports:
      - 8001:8000
    environment:
      - DATABASE_URI=postgresql://movie_db_username:movie_db_password@movie_db/movie_db_dev
      - CAST_SERVICE_HOST_URL=http://cast_service:8000/api/v1/casts/

  movie_db:
    image: postgres:12.1-alpine
    volumes:
      - postgres_data_movie:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER=movie_db_username
      - POSTGRES_PASSWORD=movie_db_password
      - POSTGRES_DB=movie_db_dev

  cast_service:
    build: ./cast-service
    command: uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
    volumes:
      - ./cast-service/:/app/
    ports:
      - 8002:8000
    environment:
      - DATABASE_URI=postgresql://cast_db_username:cast_db_password@cast_db/cast_db_dev

  cast_db:
    image: postgres:12.1-alpine
    volumes:
      - postgres_data_cast:/var/lib/postgresql/data/
    environment:
      - POSTGRES_USER=cast_db_username
      - POSTGRES_PASSWORD=cast_db_password
      - POSTGRES_DB=cast_db_dev

volumes:
  postgres_data_movie:
  postgres_data_cast:

Here you have 4 different services: movie_service, database for Movie_service, cast_service and database for Cast service. you opened movie_serviceport 8001similarly cast_serviceport 8002.

You used volumes for the database so that the data is not destroyed when the Docker container is shut down.

Run docker-compose with the command:

docker-compose up -d

This creates a docker image if it doesn't already exist and runs it.

Go to address http://localhost:8002/docs to add a cast in the cast service. Likewise, http://localhost:8001/docs to add the movie to your movie service.

Using Nginx to access both services using the same host address

You've deployed microservices using Docker Compose, but there's one small problem. Each microservice must be accessed through a separate port. You can solve this problem using Nginx reverse proxy. Using Nginx, we can route the request by adding middleware that routes our requests to different services based on the API URL.

nginx_config.confAdd a new file inside python-microserviceswith the following content.


server {
  listen 8080;

  location /api/v1/movies {
    proxy_pass http://movie_service:8000/api/v1/movies;
  }

  location /api/v1/casts {
    proxy_pass http://cast_service:8000/api/v1/casts;
  }

}

Here you run Nginx on the port 8080and direct requests to the movie service if the endpoint begins with /api/v1/moviesand similar to the broadcast service if the endpoint begins with/api/v1/casts

Now you need to add the nginx service to our docker-compose-yml. Add the following service after cast_dbservices:

nginx:
    image: nginx:latest
    ports:
      - "8080:8080"
    volumes:
      - ./nginx_config.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - cast_service
      - movie_service

Now close the containers with the command:

docker-compose down

And run it again with:

docker-compose up -d

You can now access both the movie service and the streaming service through the port 8080.
Go to address http://localhost:8080/api/v1/movies/, to get a list of movies.

Now you might be wondering how to access service documentation. To do this, update main.pymovie service and replace

app = FastAPI()

With

app = FastAPI(openapi_url="/api/v1/movies/openapi.json", docs_url="/api/v1/movies/docs")

Likewise, for the cast service, replace it with

app = FastAPI(openapi_url="/api/v1/casts/openapi.json", docs_url="/api/v1/casts/docs")

openapi.jsonHere you have changed the endpoint and from where the documents are served.

You can now access the documents

http://localhost:8080/api/v1/movies/docs

http://localhost:8080/api/v1/casts/docs

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *