Skip to content

Repository files navigation

Delivery Service

A microservice responsible for delivery management — assigning orders to couriers, tracking courier movement, and coordinating delivery lifecycle.

Built with Domain-Driven Design (DDD) tactical patterns and Clean Architecture to keep business logic isolated, testable, and framework-agnostic.


Context

The Delivery service is part of a larger system and communicates with:

  • Geo Service — provides geographic data (coordinates by address) via gRPC
  • Basket Service — publishes events when a new basket/order is created (consumed via Kafka)
  • Other consumers — Delivery publishes domain events (order assigned, order completed) to Kafka

The main goal of this project is applying DDD tactical patterns:

  • AggregatesCourier, Order with enforced invariants
  • Entities — with identity and lifecycle
  • Value ObjectsLocation (immutable, equality by value)
  • Domain EventsOrderAssigned, OrderCompleted
  • Domain Services — logic that spans multiple aggregates
  • Repository pattern — persistence abstraction via ports
  • Unit of Work — transactional consistency
image

Architecture

The project follows layered Clean Architecture with strict dependency direction (outer → inner):

┌─────────────────────────────────────┐
│  Adapters (HTTP, Kafka, gRPC, DB)   │
└──────────────────┬──────────────────┘
                   │ depends on
                   ▼
┌─────────────────────────────────────┐
│  Application (Use Cases, Commands)  │
└──────────────────┬──────────────────┘
                   │ depends on
                   ▼
┌─────────────────────────────────────┐
│  Domain (Entities, Value Objects,   │
│          Aggregates, Domain Events) │
└─────────────────────────────────────┘

Technology Stack

Layer Technology
Language Python ≥ 3.13 (strict type hints)
Web Framework Litestar
DI Container Dishka
Message Broker Kafka via FastStream
Database PostgreSQL (async, psycopg)
ORM SQLAlchemy 2.0
Migrations Alembic
Scheduling APScheduler
RPC gRPC (geo service communication)
Serialization Pydantic v2, Protobuf
Lint/Format Ruff
Package Manager uv
Testing Polyfactory (test data factories)
Infrastructure Docker, Docker Compose

Service Contracts

We treat contracts as borders between services. Code is generated from formal specifications — never hand-written:

OpenAPI (HTTP)

REST API schemas are generated from contract.yaml using datamodel-codegen:

datamodel-codegen \
  --input src/delivery_app/adapters/input/http/openapi/contract.yaml \
  --input-file-type openapi \
  --output src/delivery_app/adapters/input/http/openapi/schemas \
  --module-split-mode single \
  --all-exports-scope recursive \
  --output-model-type pydantic_v2.BaseModel \
  --target-python-version 3.13 \
  --reuse-model \
  --use-exact-imports \
  --disable-future-imports \
  --use-standard-collections \
  --use-operation-id-as-name \
  --field-constraints \
  --snake-case-field \
  --formatters ruff-check ruff-format

gRPC (Geo Service)

gRPC client stubs are generated from .proto definitions using grpc_tools:

cd src/delivery_app/adapters/output/grpc/geo

python -m grpc_tools.protoc \
  --python_out=./pb \
  --grpc_python_out=./pb \
  --pyi_out=./pb \
  --proto_path=./protos \
  ./protos/*

Project Structure

src/
├── delivery_app/
│   ├── core/
│   │   ├── domain/          # Entities, Value Objects, Aggregates, Domain Events, Errors
│   │   ├── application/     # Use Cases (Commands/Queries), Application Services
│   │   └── ports/           # Interfaces (Repositories, UoW, External Services)
│   ├── adapters/
│   │   ├── input/
│   │   │   ├── http/        # Litestar controllers, OpenAPI contract & schemas
│   │   │   └── kafka/       # Kafka consumers (basket events)
│   │   └── output/
│   │       ├── postgres/    # SQLAlchemy repositories, UoW, Alembic migrations
│   │       ├── kafka/       # Kafka producers (integration events)
│   │       ├── grpc/        # gRPC client (geo service)
│   │       └── scheduler/   # APScheduler jobs (assign orders, move couriers, outbox)
│   └── di/                  # Dishka DI container providers
├── config/                  # App settings (12-factor, env-based)
└── main.py                  # Application entrypoint

Running

docker compose up -d    # Start PostgreSQL
uv run alembic upgrade head   # Apply migrations
uv run python src/main.py     # Start the service

Testing

uv run pytest

Tests are organized by layer:

  • tests/unit/core/domain/ — pure domain logic
  • tests/unit/core/application/ — use cases with fakes/mocks
  • tests/integration/ — adapters with real infrastructure

About

Domain Driven Design and Clean Architecture course

Resources

Stars

Watchers

Forks

Contributors

Languages