Skip to main content

Book management CLI demonstrating hexagonal architecture

Project description

Book Manager — Hexagonal Architecture Example

A minimal example demonstrating the ports & adapters (hexagonal) pattern with a Typer CLI, mirroring the structure used in the smus package.

Structure

book_manager/
├── __init__.py          ← Factory (wires adapter into service)
├── __main__.py          ← Entry point: python -m book_manager
├── model.py             ← Data classes (Book, requests)
├── ports.py             ← Protocol (interface) — what the service expects
├── service.py           ← Business logic — only talks to the port
├── cli.py               ← Typer commands — parses args, calls service
└── adapters/
    └── json_store.py    ← Concrete implementation (stores books as JSON)

tests/
└── test_service.py      ← Unit tests with mocked port

How the layers connect

CLI (cli.py)
  │  parses user input, calls service
  ▼
Service (service.py)
  │  contains business rules, uses self._store (typed as BookStorePort)
  ▼
Port (ports.py)
  │  Protocol class — defines method signatures only
  ▼
Adapter (adapters/json_store.py)
     implements the port methods, talks to the filesystem

Install

# With uv (recommended)
uv sync --extra dev

# Or with pip
pip install -e ".[dev]"

Run it

# Add a book
uv run books add --isbn "978-0441172719" --title "Dune" --author "Frank Herbert"

# List all books
uv run books list

# Get a specific book
uv run books get --isbn "978-0441172719"

# Remove a book
uv run books remove --isbn "978-0441172719"

The store location defaults to books.json in the current directory. Override it with the BOOKS_FILE environment variable:

BOOKS_FILE=data/library.json uv run books list

Run tests

uv run pytest

Use it from another repo's GitHub Actions

The CLI is published on PyPI as aerkoc-bookstore, so consumers install it directly from there — no token, no repo access needed. Three entry points are available:

Entry point File Use case
Inline reusable workflow book-manager-inline.yml Turnkey job, all steps self-contained
Action-backed reusable workflow book-manager.yml Turnkey job, delegates to the composite action
Composite action .github/actions/book-manager Drop-in step inside your own job

Minimal example in a consumer repo (.github/workflows/manage-books.yml):

name: Manage bookstore
on:
  workflow_dispatch:
    inputs:
      command: { type: choice, options: [add, list, get, remove], required: true }
      isbn: { type: string, required: false }
      title: { type: string, required: false }
      author: { type: string, required: false }

jobs:
  books:
    uses: sly01/bookstore/.github/workflows/book-manager-inline.yml@v0.1.0
    with:
      command: ${{ github.event.inputs.command }}
      isbn: ${{ github.event.inputs.isbn }}
      title: ${{ github.event.inputs.title }}
      author: ${{ github.event.inputs.author }}
      books-file: books.json
      commit-changes: true
      # version: "0.1.0"   # pin to a specific PyPI release; omit for latest

No secrets: block needed. A full copy-paste example with all three options lives in examples/consumer-manage-books.yml.

Publishing a release

Pushing a version tag triggers the publish workflow automatically:

git tag v0.1.0
git push origin v0.1.0

This builds the wheel and uploads it to pypi.org/project/aerkoc-bookstore/ using Trusted Publisher (OIDC — no API token stored in secrets). See .github/workflows/publish.yml for the one-time PyPI setup instructions.

Key takeaway

The service never imports the adapter. It only knows about BookStorePort. This means you can:

  1. Swap JsonBookStore for a PostgresBookStore without changing the service
  2. In tests, inject a MagicMock(spec=BookStorePort) — no file I/O needed
  3. The CLI is just a thin shell that parses args and delegates to the service

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aerkoc_bookstore-0.1.0.tar.gz (17.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aerkoc_bookstore-0.1.0-py3-none-any.whl (7.1 kB view details)

Uploaded Python 3

File details

Details for the file aerkoc_bookstore-0.1.0.tar.gz.

File metadata

  • Download URL: aerkoc_bookstore-0.1.0.tar.gz
  • Upload date:
  • Size: 17.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for aerkoc_bookstore-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d3daeaf22bb05f826ec21c6a7b33594f7ed1d51c289c454350842358031430ae
MD5 a0e3da22744a0951a40e32dfee99806c
BLAKE2b-256 c6d08116c75912bc98b82dcb8b1f2a614adebce03309fb607a8630ea41ed72f9

See more details on using hashes here.

Provenance

The following attestation bundles were made for aerkoc_bookstore-0.1.0.tar.gz:

Publisher: publish.yml on sly01/bookstore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file aerkoc_bookstore-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for aerkoc_bookstore-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 07d76178e3cc8fe9bfb666b9b1700f46c7b36fb42fe95ea3b06d77c04686eda9
MD5 371c013ef1825d6b8a1b5ba7da95f15a
BLAKE2b-256 4746badfe26f567e131c020346f0ce19d7a8d76957b4aa7d044c3a1a7033de1f

See more details on using hashes here.

Provenance

The following attestation bundles were made for aerkoc_bookstore-0.1.0-py3-none-any.whl:

Publisher: publish.yml on sly01/bookstore

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page