Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 152 additions & 102 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,105 +1,155 @@
# E-commerce Microservices (Python, Flask, MySQL, SQS)

Services

- order_service (A): REST + MySQL + publishes SQS order events
- product_service (B): REST + MySQL (catalog)
- user_service (C): REST + MySQL (users)

Infra

- 3x MySQL containers
- LocalStack (SQS)
- Docker Compose to orchestrate

Quick start

- docker compose up -d --build
- docker compose logs -f order_service product_service user_service
- docker compose down -v

APIs

- OpenAPI specs in each service under openapi.yaml

# E-commerce Microservices Architecture

This repo contains three Python (Flask) microservices orchestrated with Docker Compose, each with its own MySQL database, plus LocalStack SQS for messaging.

## Architecture (single diagram)

```mermaid
flowchart LR
client([Client / Postman])

subgraph "Order Service :8080"
order_svc([Order Service])
o1["POST /api/v1/orders"]
o2["GET /api/v1/orders"]
o3["GET /api/v1/orders/{id}"]
o4["GET /api/v1/orders/{id}/details"]
o5["POST /api/v1/orders/{id}/pay"]
o6["POST /api/v1/orders/{id}/cancel"]
o7["GET /health"]
order_svc --- o1
order_svc --- o2
order_svc --- o3
order_svc --- o4
order_svc --- o5
order_svc --- o6
order_svc --- o7
end

subgraph "Product Service :8081"
product_svc([Product Service])
p1["CRUD /api/v1/products"]
p2["POST /api/v1/products/{id}/reserve"]
p3["POST /api/v1/products/{id}/release"]
p4["GET /api/v1/products/search"]
p5["GET /health"]
product_svc --- p1
product_svc --- p2
product_svc --- p3
product_svc --- p4
product_svc --- p5
end

subgraph "User Service :8082"
user_svc([User Service])
u1["POST /api/v1/users"]
u2["GET /api/v1/users/{id}"]
u3["POST /api/v1/login"]
u4["GET /health"]
user_svc --- u1
user_svc --- u2
user_svc --- u3
user_svc --- u4
end

subgraph Datastores
dborders[(MySQL order_db)]
dbproducts[(MySQL product_db)]
dbusers[(MySQL user_db)]
end

sqs[(LocalStack SQS: order-events)]

client --> order_svc
client --> product_svc
client --> user_svc

order_svc --> dborders
product_svc --> dbproducts
user_svc --> dbusers

order_svc --> user_svc
order_svc --> product_svc

order_svc --> sqs
# E-Commerce Microservices

[![Python 3.11](https://img.shields.io/badge/Python-3.11-3776AB?logo=python&logoColor=white)](https://python.org)
[![Flask](https://img.shields.io/badge/Flask-3.0-000000?logo=flask&logoColor=white)](https://flask.palletsprojects.com)
[![Docker](https://img.shields.io/badge/Docker-Compose-2496ED?logo=docker&logoColor=white)](https://docker.com)
[![Keploy](https://img.shields.io/badge/Tested_with-Keploy-7C3AED)](https://keploy.io)

A Python/Flask e-commerce backend using microservices, each with its own MySQL database, an API Gateway, and SQS event messaging via LocalStack - all wired together with Docker Compose.

---

## Services

| Service | Port | Responsibility |
|---|---|---|
| API Gateway | `8083` | Single entry point - proxies all client requests |
| Order Service | `8080` | Order lifecycle, stock reservation, SQS events |
| Product Service | `8081` | Product catalog, inventory management |
| User Service | `8082` | Auth, JWT issuance, user accounts & addresses |
| LocalStack (SQS) | `4566` | Local AWS SQS - `order-events` queue |

---

## Quick Start

**Requires:** Docker Desktop (includes Compose)

```bash
git clone https://github.com/keploy/ecommerce_sample_app.git
cd ecommerce_sample_app
docker compose up -d --build
```

That is it. On first boot the stack will:
- Run all DB migrations automatically
- Seed an admin user: `admin` / `admin123`
- Seed sample products (Laptop, Mouse)
- Create the `order-events` SQS queue

**Health check:**
```bash
curl http://localhost:8083/health
Comment on lines +40 to +42
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curl http://localhost:8083/health won’t work as written: the API gateway implementation doesn’t expose a /health route (and none of the Flask apps define it). Update the README to point to an actual unauthenticated endpoint (e.g. POST /api/v1/login) or add /health handlers in code (and keep docs consistent).

Suggested change
**Health check:**
```bash
curl http://localhost:8083/health
**Quick auth check (via API Gateway):**
```bash
curl -X POST http://localhost:8083/api/v1/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin123"}'

Copilot uses AI. Check for mistakes.
```

**Logs:**
```bash
docker compose logs -f
```

**Teardown:**
```bash
docker compose down -v # -v removes data volumes
```

---

## API

All requests go through the gateway at `http://localhost:8083`. Every endpoint except `/api/v1/login` and `/health` requires `Authorization: Bearer <token>`.

Comment on lines +59 to +60
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence excludes /health from auth, but /health isn’t currently implemented by the gateway/services. Either remove /health here or add the endpoint so the auth guidance matches reality.

Copilot uses AI. Check for mistakes.
**Get a token:**
```bash
curl -s -X POST http://localhost:8083/api/v1/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin123"}'
```

| Service | Endpoints |
|---|---|
| **User** | `POST /api/v1/login` , `POST /api/v1/users` , `GET /api/v1/users/{id}` , `GET/POST /api/v1/users/{id}/addresses` |
| **Product** | `GET/POST /api/v1/products` , `GET/PUT/DELETE /api/v1/products/{id}` , `GET /api/v1/products/search` , `POST /api/v1/products/{id}/reserve` , `POST /api/v1/products/{id}/release` |
| **Order** | `GET/POST /api/v1/orders` , `GET /api/v1/orders/{id}` , `GET /api/v1/orders/{id}/details` , `POST /api/v1/orders/{id}/pay` , `POST /api/v1/orders/{id}/cancel` |

Full OpenAPI specs: each service has an `openapi.yaml`. Import `postman/collection.gateway.json` + `postman/environment.local.json` for a ready-to-run Postman setup.

---

## Architecture

```
Client --> API Gateway (:8083)
|-- /users/* --> User Service (:8082) --> mysql-users (:3307)
|-- /products/* --> Product Service (:8081) --> mysql-products (:3308)
|-- /orders/* --> Order Service (:8080) --> mysql-orders (:3309)
Comment on lines +81 to +84
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The architecture diagram paths (/users/*, /products/*, /orders/*) don’t match the actual gateway routes (/api/v1/users/*, etc.). Consider updating the diagram to include the /api/v1 prefix to avoid confusion when trying the curl examples.

Copilot uses AI. Check for mistakes.
|
|-- calls User Service (validate user)
|-- calls Product Service (reserve / release stock)
+-- publishes --> SQS order-events (LocalStack :4566)
```

**Key behaviours:**
- `POST /orders` validates the user, reserves product stock, persists the order, then emits an `ORDER_CREATED` event to SQS.
- Supports `Idempotency-Key` header on order creation to prevent duplicates.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README claims idempotency “prevents duplicates”, but the current behavior is enforced via a DB unique constraint on idempotency_key; duplicate submissions will fail the insert and the API returns a 500 rather than returning the original order. Either document that behavior, or implement true idempotency by looking up and returning the existing order when the same Idempotency-Key is reused.

Suggested change
- Supports `Idempotency-Key` header on order creation to prevent duplicates.
- `POST /orders` accepts an `Idempotency-Key` header; this is stored with a DB unique constraint on `idempotency_key`, so reusing the same key currently triggers a 500 error from the unique-constraint violation rather than returning the original order.

Copilot uses AI. Check for mistakes.
- Cancelling an order releases reserved stock and emits `ORDER_CANCELLED`.
Comment on lines +92 to +94
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These ‘Key behaviours’ bullets don’t match the implementation: the order endpoint is /api/v1/orders (not /orders), and the SQS event types emitted by order_service are order_created / order_cancelled (lowercase), not ORDER_CREATED / ORDER_CANCELLED. Align the README wording with the actual routes/event payloads.

Suggested change
- `POST /orders` validates the user, reserves product stock, persists the order, then emits an `ORDER_CREATED` event to SQS.
- Supports `Idempotency-Key` header on order creation to prevent duplicates.
- Cancelling an order releases reserved stock and emits `ORDER_CANCELLED`.
- `POST /api/v1/orders` validates the user, reserves product stock, persists the order, then emits an `order_created` event to SQS.
- Supports `Idempotency-Key` header on order creation to prevent duplicates.
- Cancelling an order releases reserved stock and emits `order_cancelled`.

Copilot uses AI. Check for mistakes.
- JWT (`HS256`) is issued by the User Service and verified by all services via a shared `JWT_SECRET`.

---

## Testing

Tests live in `<service>/keploy/` - two suites per service:

| Suite | Description |
|---|---|
| `ai/` | AI-generated tests recorded from live traffic (e.g. 36 tests for `order_service`) |
| `manual/` | Hand-crafted test scenarios (e.g. 14 tests for `order_service`) |

**Run tests (requires [Keploy CLI](https://keploy.io/docs)):**
```bash
docker compose up -d --build
keploy test -c "docker compose up" --container-name order_service
```

Key behaviors
Coverage reports are written to `coverage/` during test runs.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“Coverage reports are written to coverage/” is incomplete/misleading: the coverage data files are written under the top-level coverage/ volume, but the HTML output is generated under each service’s */coverage/htmlcov/ directory. Consider clarifying where to find the HTML report.

Suggested change
Coverage reports are written to `coverage/` during test runs.
Coverage data is written to the shared `coverage/` volume during test runs, while per-service HTML reports are generated under each service’s `*/coverage/htmlcov/` directory (for example, `order_service/coverage/htmlcov/index.html`).

Copilot uses AI. Check for mistakes.

---

## Project Layout

```
ecommerce_sample_app/
|-- docker-compose.yml
|-- apigateway/ # Flask reverse proxy
|-- order_service/
| |-- app.py
| |-- migrations/ # Versioned SQL files - auto-applied at startup
| +-- keploy/ai|manual/ # Keploy test suites
|-- product_service/ # Same structure as order_service
|-- user_service/ # Same structure as order_service
|-- localstack/ # Queue init scripts (runs on LocalStack ready)
+-- postman/ # Import-ready Postman collections
```

---

## Configuration

Set in `docker-compose.yml`. Key variables:

| Variable | Default | Note |
|---|---|---|
| `JWT_SECRET` | `dev-secret-change-me` | **Must change in production** |
| `DB_HOST / DB_USER / DB_PASSWORD / DB_NAME` | per-service | Each service owns its own DB |
| `AWS_ENDPOINT` | `http://localstack:4566` | Routes SQS traffic to LocalStack |
| `ADMIN_PASSWORD` | `admin123` | Seeded admin credentials |
Comment on lines +138 to +145
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section says variables are “Set in docker-compose.yml”, but JWT_SECRET is not actually set in docker-compose.yml (services fall back to the in-code default), and the seeded admin password comes from the SQL migration (hard-coded admin/admin123), not the ADMIN_PASSWORD env var. Update the table/wording to reflect the real defaults and which env vars are actually consumed.

Suggested change
Set in `docker-compose.yml`. Key variables:
| Variable | Default | Note |
|---|---|---|
| `JWT_SECRET` | `dev-secret-change-me` | **Must change in production** |
| `DB_HOST / DB_USER / DB_PASSWORD / DB_NAME` | per-service | Each service owns its own DB |
| `AWS_ENDPOINT` | `http://localstack:4566` | Routes SQS traffic to LocalStack |
| `ADMIN_PASSWORD` | `admin123` | Seeded admin credentials |
Configuration & defaults (from env, code, and migrations):
| Variable | Default | Note |
|---|---|---|
| `JWT_SECRET` | `dev-secret-change-me` (in-code) | Used by the User Service for JWT signing; override via `JWT_SECRET` env var (**must change in production**) |
| `DB_HOST / DB_USER / DB_PASSWORD / DB_NAME` | per-service | Each service owns its own DB; values typically set via `docker-compose.yml` |
| `AWS_ENDPOINT` | `http://localstack:4566` | Routes SQS traffic to LocalStack (e.g. `http://localstack:4566` in Docker) |
| Admin credentials | `admin / admin123` (SQL migration) | Initial admin user is seeded by migration; current seeding does not read `ADMIN_PASSWORD` env var |

Copilot uses AI. Check for mistakes.

---

## Contributing

- Order creation validates user and products, reserves stock, persists order + items, emits SQS event.
- Idempotency: POST /orders supports Idempotency-Key to avoid duplicate orders.
- Status transitions: PENDING → PAID or CANCELLED (cancel releases stock).
1. Fork and create a branch (`feat/your-feature`, `fix/issue`, `docs/update`)
2. Make changes - keep services decoupled, update `openapi.yaml` for API changes, add new migration files for schema changes (never edit existing ones)
3. Test: `docker compose up -d --build` then run Keploy tests
4. Commit using [Conventional Commits](https://www.conventionalcommits.org) (`feat:`, `fix:`, `docs:`, `test:`, `refactor:`)
5. Open a Pull Request and reference the related issue