Skip to main content

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

Metadata

Release files for aerkoc-bookstore 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aerkoc-bookstore 0.1.0
File Size Uploaded Provenance
aerkoc_bookstore-0.1.0.tar.gz 17.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for aerkoc-bookstore 0.1.0
File Interpreter ABI Platform
aerkoc_bookstore-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 25.0 kB

Release files / aerkoc_bookstore-0.1.0.tar.gz

Download URL aerkoc_bookstore-0.1.0.tar.gz
Size 17.8 kB
Tags Source
SHA-256 checksum
How to use checksums
d3daeaf22bb05f826ec21c6a7b33594f7ed1d51c289c454350842358031430ae
BLAKE2b-256 checksum
How to use checksums
c6d08116c75912bc98b82dcb8b1f2a614adebce03309fb607a8630ea41ed72f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Release files / aerkoc_bookstore-0.1.0-py3-none-any.whl

Download URL aerkoc_bookstore-0.1.0-py3-none-any.whl
Size 7.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
07d76178e3cc8fe9bfb666b9b1700f46c7b36fb42fe95ea3b06d77c04686eda9
BLAKE2b-256 checksum
How to use checksums
4746badfe26f567e131c020346f0ce19d7a8d76957b4aa7d044c3a1a7033de1f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

Security for aerkoc-bookstore 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for aerkoc_bookstore-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page