Skip to content
Merged
Show file tree
Hide file tree
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
18 changes: 12 additions & 6 deletions .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,17 +145,23 @@ jobs:
dir: ${{ fromJSON(needs.changes.outputs.python) }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
# uv pinned to the version the Containerfiles install. Legs do not use the setup-uv cache: the
# cache policy below (restore everywhere, write only on main) has no entry for it, and every
# dependency set here is small.
- uses: astral-sh/setup-uv@v10.3.0
with:
version: "0.13.0"
python-version: "3.14"
enable-cache: false
- name: Install and byte-compile
working-directory: ${{ matrix.dir }}
env:
UV_PROJECT_ENVIRONMENT: ${{ runner.temp }}/venv
run: |
test -f requirements.txt || { echo "::error::no requirements.txt in ${{ matrix.dir }}"; exit 1; }
python -m venv "$RUNNER_TEMP/venv"
. "$RUNNER_TEMP/venv/bin/activate"
pip install -r requirements.txt
python -m compileall -q .
test -f pyproject.toml || { echo "::error::no pyproject.toml in ${{ matrix.dir }}"; exit 1; }
test -f uv.lock || { echo "::error::no uv.lock in ${{ matrix.dir }}"; exit 1; }
uv sync --locked --no-dev
uv run --no-sync python -m compileall -q .

# Cache policy. The repository gets 10 GB of Actions cache, shared by every branch. The rule:
# every run RESTORES, only a push to main WRITES, and every ecosystem keeps a small, fixed
Expand Down
2 changes: 1 addition & 1 deletion _docs/01-cloud-native-principles.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ app.MapGet("/readyz", async (NpgsqlDataSource db) => // readiness: SELE
```

```python
# main.py (excerpt); requirements.txt pins every package with ==
# main.py (excerpt); pyproject.toml pins every direct dependency with ==, uv.lock the rest
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic_settings import BaseSettings
Expand Down
9 changes: 6 additions & 3 deletions _docs/12-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,20 +257,23 @@ app.MapPost("/valet-key", (HttpContext ctx) =>

```python
# main.py (excerpt), examples/12-security/python: an HMAC-SHA256 valet key
VALET_SECRET = os.environ.get("VALET_SECRET", "demo-secret-do-not-use-in-prod")
class Settings(BaseSettings): # VALET_SECRET from the environment
valet_secret: str = "demo-secret-do-not-use-in-prod"

settings = Settings()

def _mint_valet(resource: str, operation: str, ttl: int = 300) -> dict:
expires = int(time.time()) + ttl # time-bound
payload = f"{resource}:{operation}:{expires}" # one object, one operation
sig = hmac.new(VALET_SECRET.encode(), payload.encode(), hashlib.sha256).hexdigest()
sig = hmac.new(settings.valet_secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
return {"resource": resource, "operation": operation, "expires": expires, "token": sig}

def _verify_valet(resource: str, operation: str, expires: int, token: str) -> bool:
if time.time() > expires:
return False # expired: worthless
payload = f"{resource}:{operation}:{expires}"
expected = hmac.new(
VALET_SECRET.encode(), payload.encode(), hashlib.sha256
settings.valet_secret.encode(), payload.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(token, expected) # constant-time compare

Expand Down
2 changes: 1 addition & 1 deletion _docs/27-appendix-n-feature-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public class CheckoutController : ControllerBase
```

```python
# pip install openfeature-sdk openfeature-provider-flagd
# uv add openfeature-sdk openfeature-provider-flagd
from openfeature import api
from openfeature.contrib.provider.flagd import FlagdProvider
from openfeature.evaluation_context import EvaluationContext
Expand Down
2 changes: 1 addition & 1 deletion _example_pages/prerequisites.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ container.
| Java (Spring Boot, Quarkus) | [SDKMAN](https://sdkman.io/): `curl -s "https://get.sdkman.io" \| bash`, then `sdk install java 25-tem` and `sdk install maven`; optional CLIs: `sdk install quarkus 3.40.1` and `sdk install springboot 4.1.1` |
| Go 1.27.2 | Official tarball from [go.dev/dl](https://go.dev/dl/): `curl -fsSLO https://go.dev/dl/go1.27.2.linux-amd64.tar.gz && echo "ecbadb99091a3f46e31f5f934b068b1864eafa7995211b39eaddf76996045fe5 go1.27.2.linux-amd64.tar.gz" \| sha256sum -c - && sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.27.2.linux-amd64.tar.gz`, then add `/usr/local/go/bin` to `PATH`. Every `go.mod` requires 1.27.2; an older `go` downloads it on demand with `GOTOOLCHAIN=go1.27.2` |
| .NET | `sudo dnf install -y dotnet-sdk-10.0` (from the Fedora or RHEL repositories) |
| Python | `sudo dnf install -y python3.14` and [uv](https://docs.astral.sh/uv/): `curl -LsSf https://astral.sh/uv/install.sh \| sh` |
| Python | `sudo dnf install -y python3.14` and [uv](https://docs.astral.sh/uv/): `curl -LsSf https://astral.sh/uv/0.13.0/install.sh \| sh` |
| C++ (C++23) | **Fedora 44:** `sudo dnf install -y gcc-c++ cmake ninja-build git make python3` (GCC 16.2, which supports C++23; Fedora has no `gcc-toolset-15`, and ninja-build 1.13.2 matches the pinned Ninja). Fedora's cmake is 4.3.0, which satisfies the examples' 3.31 minimum; the upstream CMake 4.4.4 binary is optional there and only matches the container toolchain exactly. **RHEL 10:** `sudo dnf install -y gcc-toolset-15 git make python3`, then `source /opt/rh/gcc-toolset-15/enable` (GCC 15.2). RHEL ships cmake 3.31 and ninja 1.11, so take CMake 4.4.4 and Ninja 1.13.2 from the upstream release binaries: `curl -fsSLO https://github.com/Kitware/CMake/releases/download/v4.4.4/cmake-4.4.4-linux-x86_64.tar.gz && echo "e5bb807f7728cb60cd8b27ebc97a2edb469b68655f21e844a600c3575b76f5bb cmake-4.4.4-linux-x86_64.tar.gz" \| sha256sum -c - && sudo tar -C /opt -xzf cmake-4.4.4-linux-x86_64.tar.gz`, `curl -fsSLO https://github.com/ninja-build/ninja/releases/download/v1.13.2/ninja-linux.zip && echo "5749cbc4e668273514150a80e387a957f933c6ed3f5f11e03fb30955e2bbead6 ninja-linux.zip" \| sha256sum -c - && sudo unzip -d /opt/cmake-4.4.4-linux-x86_64/bin ninja-linux.zip`, then add `/opt/cmake-4.4.4-linux-x86_64/bin` to `PATH`. **Both:** Conan 2.33.0 in a venv: `python3 -m venv ~/.venvs/conan && ~/.venvs/conan/bin/pip install conan==2.33.0` (or `pipx install conan==2.33.0`) |

---
Expand Down
3 changes: 2 additions & 1 deletion _plans/modernization-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ Each step gets its own branch, a PR, green CI and a squash-merge before the next
| S10 | feature/openshift-local-appendix | 8 | done (PR #68, 48e7736) |
| S11 | docs/professional-voice | 9 | done (PR #69, 2d8c2a8) |
| S12 | docs/deck-refresh | 10 (expanded 2026-10-11) | done (plan: _plans/s12-deck-refresh-plan.md; examples 01, 21, 25 fixed and verified 6/6) |
| S13 | refactor/idiomatic-persistence | 11 (added 2026-10-12) | next |
| S13 | refactor/idiomatic-examples (5 PRs) | 11 (added 2026-10-12) | in progress (plan: _plans/s13-idiomatic-plan.md) |
| S14 | refactor/resilience-libraries | 12 (added 2026-10-12) | next: circuit breaker, bulkhead, Spring gRPC via libraries |


- S13 (user, 2026-10-12): examples use each framework's idiomatic persistence: Hibernate ORM with Panache in every Quarkus example that touches a database (01, 04, 25 and any others), Spring Data JPA in the Spring Boot examples, EF Core in the .NET examples. Python (asyncpg), Go (pgx) and C++ (libpq) keep their drivers. Every changed example re-runs `verify.sh` green on Docker Engine; slides (via deck edit files) and site codetabs follow the examples, including the caching, outbox, health and write-back slides; CLAUDE.md's locked stacks name the ORMs.
Expand Down
138 changes: 138 additions & 0 deletions _plans/s13-idiomatic-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# S13 plan — idiomatic examples

Planned by Opus 2026-10-12 from main c5d10c2 (S12 merged). Rule (user, 2026-10-12): "always prefer the idiomatic
approach to a language". Review tool: `lgtm-idiomatic-review` (lgtm-skills 3079c03: Quarkus, Spring Boot, .NET,
Python/FastAPI, Go, C++, Kubernetes checklists and `scan.sh`).

## User decisions (2026-10-12)

- Persistence: Hibernate ORM with Panache in every Quarkus example that uses a database; Spring Data JPA in Spring Boot;
EF Core in .NET. Python (asyncpg), Go (pgx), C++ (libpq) keep their drivers.
- D1 contract: relax the shared health-body patterns in `verify.sh` to accept `ok|UP`, `ready|UP`, `down|DOWN` and
whitespace; status codes (200/503) and paths (`/healthz`, `/readyz`) unchanged; .NET keeps the exact old bodies.
Example 21 accepts JVM exit code 0 or 143 if the Step 1 spike confirms 143.
- D3 scope: tiers 1–2 here (persistence, health, shutdown, outbox, uv, REST params, HTTP clients, config, messaging
serdes, ProblemDetails, workers, logging). Tier 3 (circuit breaker in 26, bulkhead in 12, Spring gRPC instead of manual
Netty servers) is S14, planned right after, with the chapter prose rewritten to teach the pattern through the library.
- D6: sequential PRs, each merged green before the next: (1) shared contract + Python uv + CI, (2) Quarkus, (3) Spring,
(4) .NET, (5) Go and C++. Each carries its site tabs and deck edit file.
- Recommendations adopted as defaults: D2 schema stays owned by `db/init` (Hibernate `validate`, fallback `none` with
the reason recorded; EF Core without migrations/EnsureCreated); D4 framework cache abstraction for read-through only,
the other five strategies keep explicit Redis clients (they report `"source"` and survive a Redis outage); D5
pydantic-settings for Python and IConfiguration/Options for .NET; D7 no minikube re-run of 05 unless memory grows
past the k8s limits; D8 Spring 21 drain delay via a `SmartLifecycle` bean + `AvailabilityChangeEvent` (compose has no
preStop); D9 Quarkus outbox as a plain Panache `OutboxEvent` entity (the debezium-quarkus-outbox extension would own
its own schema and break the shared connector).

## Inventory (baseline)

`scan.sh examples` with the new checklists: 343 lines, 35 ban-tier. Kept on purpose: the 19 CLI adapters' stdout
(Quarkus/Spring `System.out`, Python `print`, .NET `Console.Write` in `CliPlaceOrder`, Go `fmt.Print`) and the
deliberately bad `12-security/policy/bad-deploy.yaml` (`:latest`).

| Mechanism | Quarkus (31) | Spring Boot (31) | .NET (32) | Python (32) | Go / C++ |
|---|---|---|---|---|---|
| Persistence | Agroal + raw JDBC in 14 (01, 02, 03, 04, 05×3, 06, 11×2, 17, 19, 21, 25) | JdbcClient/JdbcTemplate, same 14 | NpgsqlDataSource/Command in 15 (+30) | asyncpg, keep | pgx / libpq, keep |
| Health | hand-written JAX-RS in 23 | hand-written `@GetMapping` in 23 | `MapGet` in 24 | keep | keep |
| Shutdown (21) | `sun.misc.Signal` + `Quarkus.asyncExit` | `sun.misc.Signal` + `System.exit`; 18 `addShutdownHook` | `ManualLifetime` + `PosixSignalRegistration`; console consumers (05×2, 11, 06) and 25 flusher hand-rolled | keep | keep |
| Messaging | JSON by hand via `new ObjectMapper` (8) | JSON by hand; 06 raw `KafkaProducer` | consumers → `BackgroundService` | keep | keep |
| Config | `System.getenv` in 19 CLI, 21 | `System.getenv` in 18, 19 CLI, 21 | `Environment.GetEnvironmentVariable` in 28 | `os.environ` in 30 files → pydantic-settings | keep |
| HTTP client | `java.net.http` in 03, 24×2, 26 | RestClient, ok | `new HttpClient` in 03, 24, 26 | httpx | keep |
| Errors (18) | check `@ServerExceptionMapper` | mutable ErrorResponse → `ProblemDetail` | hand-built IResult → `AddProblemDetails` | ok | keep |
| Packaging/logging | REST params: raw JAX-RS in 12 files | — | `Console.Write` logging in 6 services → ILogger | requirements.txt → uv + pyproject + uv.lock (32) | C++ `std::cerr`/printf logging in 21 files → spdlog; Go one `interface{}` → `any` |

Shared contracts to keep: `/healthz` and `/readyz` paths and codes; 01 ready/down + 503; 21 single-SIGTERM drain
(`shutting_down`, 503 "shutting down", `/healthz` 200 while draining, exits within 30 s, log lines "SIGTERM received"
and "shutting down HTTP server"); 25 `"source":"db"|"cache"` and cache-aside surviving a Redis stop; 05 idempotent
consumers; 18 error fields `code`, `traceId`, `retryable`, `retryAfter`; `db/init` schemas unchanged; HTTP responses
stay DTOs, never serialized entities (field names, numeric scale, timestamps unchanged); Kafka payloads byte-compatible.

## Approach (per framework)

- Quarkus: `quarkus-hibernate-orm-panache` (active-record entities over the existing tables; JSONB via
`@JdbcTypeCode(SqlTypes.JSON)`; 17 `FOR UPDATE` via `findById(id, PESSIMISTIC_WRITE)`; 25 upsert via Hibernate
`StatelessSession.upsert`); `quarkus-smallrye-health` with `liveness-path=/healthz`, `readiness-path=/readyz`
(fallback: non-application root `/`); 21 via `quarkus.shutdown.delay-enabled`, `quarkus.shutdown.delay`,
`quarkus.shutdown.timeout=20s`, a `ShutdownDelayInitiatedEvent` observer and a `@Readiness` check;
`@RestPath/@RestQuery/@RestHeader`; REST Client instead of `java.net.http`; typed messaging payloads with the
framework-configured mapper; `@ServerExceptionMapper`.
- Spring Boot: `spring-boot-starter-data-jpa` + `JpaRepository` (`@Lock(PESSIMISTIC_WRITE)`, `@Modifying` native
`ON CONFLICT` for 25), `spring.jpa.open-in-view=false`; actuator health groups with `additional-path=server:/healthz`
and `server:/readyz`; 21 via `server.shutdown=graceful`, `spring.lifecycle.timeout-per-shutdown-phase=20s`, a
`SmartLifecycle` drain bean and a readiness `HealthIndicator`; Spring Kafka JSON serdes and a `ProducerFactory` from
`spring.kafka.*`; `ProblemDetail`.
- .NET: EF Core `DbContext` per service mapped to the snake_case columns (no migrations), `FromSql(... FOR UPDATE)`,
`ExecuteSqlAsync` upsert; `AddHealthChecks().AddDbContextCheck<T>(tags: ["ready"])` + `MapHealthChecks` with a
`ResponseWriter` emitting today's bodies; 21 via `IHostedLifecycleService.StoppingAsync` and
`HostOptions.ShutdownTimeout`; Worker SDK `BackgroundService` for consumers, the stream processor and the 25 flusher;
IConfiguration/Options; typed `AddHttpClient`; `AddProblemDetails` + `TypedResults.Problem`; ILogger everywhere.
- Python: uv + `pyproject.toml` + committed `uv.lock`, Containerfiles on the lgtm-python template, pydantic-settings;
CI marker becomes `pyproject.toml` (`scripts/ci_changed_projects.py`, `examples.yml` python job with setup-uv).
- Go: `any` for `interface{}`. C++: spdlog instead of `std::cerr`/`printf` in service code (CLI and test-runner output
stays).
- Caching 25: read-through through Quarkus `Cache.get(key, loader)` (quarkus-redis-cache), Spring
`Cache.get(key, Callable)` (`RedisCacheManager`), .NET `HybridCache.GetOrCreateAsync`; a flag set in the loader
reports `"source"`.

Rejected: keeping hand-written JVM health (contradicts the rule); faking `"ok"` with a custom Spring status/aggregator;
debezium-quarkus-outbox extension (own schema); EF Core migrations or Hibernate `update` (second schema owner);
annotations for cache-aside (lose `"source"` and outage tolerance); Xabaril AspNetCore.HealthChecks (built-ins plus
`Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore` cover it); one mega-PR.

## Pins (re-check at Step 0)

Quarkus extensions from BOM 3.40.1 (no versions). Spring starters from parent 4.1.1. NuGet exact:
`Npgsql.EntityFrameworkCore.PostgreSQL` 10.0.3, `Microsoft.EntityFrameworkCore` and `.Relational` 10.0.12,
`Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore` 10.0.12, `Microsoft.Extensions.Hosting` 10.0.12,
`Microsoft.Extensions.Caching.StackExchangeRedis` 10.0.12, `Microsoft.Extensions.Caching.Hybrid` 10.10.0 (check
compatibility). Python: uv 0.13.0, `astral-sh/setup-uv` v10.3.0; package versions exactly as today's requirements.

## Steps

| # | Step | PR | Status |
|---|---|---|---|
| 0 | Preflight: pins, checklists read, scanner baseline (this file), Docker idle, S12 venv | 1 | done (baseline above) |
| 1 | Spikes on 01 and 21 (Quarkus, Spring, .NET): absolute health paths and exact JSON; JVM exit code after framework drain; 21 log lines and "shutting down" body during the delay; Hibernate `validate` on TEXT/JSONB/TIMESTAMPTZ; Spring lifecycle delay keeps actuator serving. Output: exact verify.sh pattern changes | 1 | done |
| 2 | Shared verify.sh pattern relaxations (D1) + Python uv/pydantic-settings (32 projects) + CI marker + Python deck slides 10, 14 and site; matrix for every example whose verify.sh changed | 1 | done; matrix 2026-10-12: 109/109 targets PASS (Python 18, other languages 91) |
| 3 | Quarkus batch (about 26 projects) + site tabs + `Quarkus.fix5.json` | 2 | committed on `refactor/s13-quarkus`, matrix pending |
| 4 | Spring batch (about 26) + site tabs + `Spring.fix6.json` | 3 | committed on `refactor/s13-spring`, matrix pending |
| 5 | .NET batch (about 28) + site tabs + `DotNet.fix6.json` | 4 | committed on `refactor/s13-dotnet`, matrix pending |
| 6 | Go (`any`) and C++ (spdlog) + any deck/site follow-ups | 5 | pending |
| 7 | Final full matrix (111 targets, one at a time), footers with verified counts | 5 | pending |
| 8 | CLAUDE.md locked stacks (ORMs, health, shutdown, uv, schema ownership), modernization plan | 5 | pending |
| 9 | Opus validation per PR (re-scan, checklist sample, `db/init` unchanged, three CI checks green) | each | pending |

Order inside a batch: 01, 21, 04, 05, 17, 19, 25, then the health-only examples. Run `scripts/verify-matrix.sh NN-x/<lang>`
after each example (Docker runs strictly one at a time). Decks follow the S12 method (edit files applied once,
`deck_text.py --write`, render + `deck_fit` 0, colour/caps audits 0). 19 CLI adapters: Quarkus `@QuarkusMain` with the
HTTP server off, Spring `ApplicationRunner` with `web-application-type=none`, .NET host without Kestrel, all reusing
the ORM adapter.

## Acceptance criteria

1. `scan.sh examples`: 0 `requirements.txt`, 0 raw JAX-RS params, 0 hand-written health endpoints (Spring, .NET),
0 `sun.misc.Signal`/`PosixSignalRegistration`, 0 `Console.Write` / `std::cerr` service logging, 0 hand-built Npgsql
data sources, 0 `JdbcTemplate`; ban-tier survivors only the recorded CLI/fixture lines.
2. No `JdbcClient`, `AgroalDataSource`, `javax.sql.DataSource`, `NpgsqlCommand`, `java.net.http`, `new HttpClient`,
`Environment.GetEnvironmentVariable`, `Quarkus.asyncExit`, `ManualLifetime`, `System.exit` (except 19 CLI) in Quarkus,
Spring and .NET sources.
3. Every Quarkus HTTP service has `quarkus-smallrye-health`; every Spring HTTP service the actuator; every .NET service
`MapHealthChecks`.
4. Hibernate `validate` (or `none` with the reason recorded); no EF migrations or `EnsureCreated`; `git diff main --
'examples/*/db/init'` empty.
5. Every `PackageReference` exact; every Python project has `pyproject.toml` + `uv.lock`.
6. `verify.sh` diffs touch only the D1 patterns; check counts unchanged per example.
7. Final `verify-matrix.sh` 111/111 PASS; per-target runs green in each batch; `examples-ok`, `content-ok`, `site` green
on every PR.
8. Changed decks: `deck_text.py --check` ok, `deck_fit` 0, audits 0, every changed slide rendered and read; changed
codetabs and slides match their example files.
9. `validate_site.py` 0 errors, voice scan 0 ban tier; CLAUDE.md and `modernization-plan.md` updated; footers changed
only after green runs.

## Risks

JVM exit 143 (D1b); Quarkus absolute health paths; ordering of the Quarkus shutdown events against the 21 log/body
asserts; Spring lifecycle phase versus `WebServerGracefulShutdownLifecycle`; Hibernate `validate` on TEXT/JSONB; JSON
and Kafka payload drift (DTOs only, compare with kcat); idempotent consumers under JPA/EF rollback; Hibernate/EF memory
and start time against compose `--wait` and the 05 k8s limits; deck `expect` counts (measure on committed decks);
Python lock drift (diff `uv export` against the old pins); long matrix runs.
Loading
Loading