Building and Deploying Microservices with FastAPI, Docker, Pytest, and CI

Microservices provide a robust architectural style for creating modular and independently deployable services. In this article, we explore building a microservices-based application with FastAPI, containerizing it with Docker, testing it with Pytest, and automating continuous integration with GitHub Actions. The repository we will reference contains all the code and configurations discussed: [codecorner-lab/microservice-apis](https://github.com/codecorner-lab/microservice-apis/tree/main/ordering_microservice).

______________________________________________________________________

1. Overview of the Application
The application consists of two microservices:
1. Order Service: Handles customer orders by communicating with the payment service to process payments.
2. Payment Service: Simulates payment processing for orders.

These services communicate over HTTP APIs and are designed to be independent, lightweight, and scalable.

______________________________________________________________________


2. API Design

Order Service API
- Endpoint: `/create-order/`
- Method: `POST`
- Payload:
 
  {
    "product": "Book",
    "quantity": 2,
    "price": 15.00
  }
 
- Logic:
  1. Calculates the total price (`quantity * price`).
  2. Sends the payment amount to the Payment Service.
  3. Returns success if payment is successful, or failure otherwise.

Payment Service API
- Endpoint: `/pay/`
- Method: `POST`
- Payload:
 
  {
    "amount": 30.00
  }
 
- Logic:
  Simulates payment success if the amount is valid.

______________________________________________________________________

3. Project Structure

ordering_microservice/
├── order_service.py                
├── payment_service.py           
├── Dockerfile-order-service        
├── Dockerfile-payment-service      
├── Dockerfile-tests    
├── docker-compose.yml  
├── tests/  
│   ├── test_unit_order_service.py
│   ├── test_integration_order_service.py
├── .github/workflows/ci.yml 
└── requirements.txt 


______________________________________________________________________


4. Containerizing with Docker

We use Docker to containerize the microservices and tests for consistent deployment and testing environments.

Dockerfile for Order Service

FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY order_service.py .
CMD ["uvicorn", "order_service:app", "--host", "0.0.0.0", "--port", "8000"]


Docker Compose
The `docker-compose.yml` defines and orchestrates the services:

version: '3.8'

services:
  order_service:
    build:
      context: .
      dockerfile: Dockerfile-order-service
    ports:
      - "8000:8000"
    depends_on:
      - payment_service

  payment_service:
    build:
      context: .
      dockerfile: Dockerfile-payment-service
    ports:
      - "8001:8001"

  tests:
    build:
      context: .
      dockerfile: Dockerfile-tests
    depends_on:
      - order_service
      - payment_service


______________________________________________________________________

5. Testing with Pytest

Unit Tests
Unit tests validate the functionality of individual components:
- File: `tests/test_unit_order_service.py`

from order_service import app, Order
from fastapi.testclient import TestClient
import pytest

@pytest.fixture
def client():
    return TestClient(app)

def test_create_order_success(client, mocker):
    mocker.patch("order_service.requests.post", return_value=mocker.Mock(status_code=200))
    response = client.post(
        "/create-order/", json={"product": "Book", "quantity": 2, "price": 15.0}
    )
    assert response.status_code == 200
    assert response.json()["status"] == "Order placed successfully"


Integration Tests
Integration tests validate the interaction between the Order and Payment Services:
- File: `tests/test_integration_order_service.py`

import pytest
from fastapi.testclient import TestClient
from order_service import app

@pytest.fixture
def client():
    return TestClient(app)

def test_order_service_with_payment_service(client):
    response = client.post(
        "/create-order/", json={"product": "Pen", "quantity": 3, "price": 5.0}
    )
    assert response.status_code == 200
    assert response.json()["status"] == "Order placed successfully"


______________________________________________________________________

6. Continuous Integration (CI) with GitHub Actions

The workflow automatically builds, tests, and validates code on every push or pull request.

Workflow File: `.github/workflows/ci.yml`

name: CI for Microservices

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Set up Docker
      uses: docker/setup-buildx-action@v2

    - name: Install docker-compose
      run: |
        sudo curl -L "https://github.com/docker/compose/releases/download/v2.22.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
        sudo chmod +x /usr/local/bin/docker-compose
        docker-compose --version

    - name: Build and test with Docker Compose
      run: |
        docker-compose up --build --abort-on-container-exit

    - name: Cleanup
      if: always()
      run: docker-compose down


______________________________________________________________________

7. How to Use This Repository

1. Clone the Repository:
   git clone https://github.com/codecorner-lab/microservice-apis.git
   cd ordering_microservice


2. Run the Services Locally:
   docker-compose up --build
   

3. Run Tests Locally:
   docker-compose up --build --abort-on-container-exit
   

4. Push Code to Trigger CI:
   Push changes to the `main` branch or open a pull request to trigger the GitHub Actions workflow.

Repository: [codecorner-lab/microservice-apis](https://github.com/codecorner-lab/microservice-apis/tree/main/ordering_microservice)

Feel free to explore the repository and adapt this setup for your own projects! 🚀