feat(backend): serve gRPC ItemService with event streaming - #161
Conversation
grpc.aio server on :50051 in the same process as FastAPI (ADR-0002: backend serves, never dials): ListItems snapshot, GetItemStats, and WatchItemEvents backed by an in-process emit-after-commit broadcaster. Per-subscriber bounded queues; a slow consumer is disconnected rather than blocking the request path, and reconnect recovers state via the ListItems snapshot -- at-most-once transport eventing, the documented gap the NATS capstone later closes. gRPC Health Checking Protocol served; container healthcheck now verifies HTTP and gRPC together. RED metrics via server interceptor on the existing registry. Codegen is generate-in-build through a single grpc_tools.protoc run (python + pyi + grpc stubs); buf.gen.yaml removed -- its protoc_builtin plugins require a host protoc binary, which clean CI and Docker builds do not have (masked locally by a Homebrew protoc). Generated code is never committed (gitignored, dockerignored, CI gate). Serving is pinned to a single uvicorn worker: with multiple workers the broadcaster and gRPC listener fork per process behind SO_REUSEPORT and stream subscribers silently miss most events.
|
The latest Buf updates on your PR. Results from workflow Proto / lint-proto (pull_request).
|
|
The latest Buf updates on your PR. Results from workflow Proto / breaking-proto (pull_request).
|
|
Warning Review limit reached
Next review available in: 34 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
WalkthroughThe backend now generates protobuf/gRPC stubs during development, CI, and Docker builds. It adds an async gRPC ItemService with health checks, event streaming, metrics, lifecycle integration, container support, and corresponding tests and documentation. ChangesgRPC backend integration
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant ItemServiceServicer
participant broadcaster
participant CRUD
participant Database
Client->>ItemServiceServicer: WatchItemEvents
ItemServiceServicer->>broadcaster: subscribe
Client->>CRUD: create or delete item
CRUD->>Database: commit item change
CRUD->>broadcaster: publish ItemEventPayload
broadcaster->>ItemServiceServicer: deliver queued event
ItemServiceServicer-->>Client: stream ItemEvent
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
docs/ci.md (1)
75-80: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdjust terminology for accuracy regarding
git ls-files.Since
git ls-filesonly checks the current git index (files that are currently tracked) rather than searching through the entire commit history, the phrase "was ever committed" is technically inaccurate. Consider updating it to "is currently tracked" or "is committed" to better reflect what the gate actually evaluates.📝 Proposed wording update
- **No committed generated code (backend):** a `git ls-files 'services/backend/app/proto_gen/*'` check fails the pipeline if - anything under the generate-in-build gRPC stub directory was ever - committed (RFC-0001 D8, ADR-0002 -- Hard rule 1). `make generate` then + anything under the generate-in-build gRPC stub directory is + tracked (RFC-0001 D8, ADR-0002 -- Hard rule 1). `make generate` then runs before tests, since the backend imports the generated stubs at module load time.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/ci.md` around lines 75 - 80, Update the “No committed generated code (backend)” documentation to replace “was ever committed” with wording that accurately describes the git ls-files check, such as “is currently tracked” or “is committed.” Preserve the existing explanation of the generate-in-build directory and pipeline behavior.Makefile (1)
104-113: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winRegenerate into a clean
proto_gendirectory.
mkdir -pdoesn't clear previously generated files, so stale modules from a priormake generaterun could linger if the proto surface changes (e.g. a renamed/removed message or service). Cheap to close off now before more.protofiles are added.♻️ Proposed fix
generate: ## Generate gRPC/protobuf Python stubs into services/backend/app/proto_gen (never committed -- RFC-0001 D8, ADR-0002) $(PRINT_TARGET) - mkdir -p services/backend/app/proto_gen + rm -rf services/backend/app/proto_gen + mkdir -p services/backend/app/proto_gen python -m grpc_tools.protoc -I proto \🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Makefile` around lines 104 - 113, Update the generate target to remove the existing services/backend/app/proto_gen directory before recreating it, then keep the protoc generation commands unchanged so each run starts from a clean output directory.services/backend/app/main.py (1)
28-41: 🩺 Stability & Availability | 🔵 TrivialStartup coupling is intentional but worth keeping in mind operationally.
A gRPC bind/start failure now prevents the HTTP interface from starting at all, since both live in the same
lifespan. This matches the PR's single-worker/consistent-state design and the combined health check, so no change requested — just flagging for awareness if HTTP-only availability is ever desired during a gRPC-specific outage.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@services/backend/app/main.py` around lines 28 - 41, No code changes are requested. Preserve the intentional coupling in lifespan: start gRPC before yielding from lifespan and keep HTTP startup blocked if start_grpc_server fails, while retaining graceful shutdown through grpc_server.stop(grace=5).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/images.yml:
- Line 41: Update the actions/checkout step to set persist-credentials to false,
preventing the GitHub token from being stored in local git configuration while
preserving the existing pinned action reference.
In `@services/backend/app/crud.py`:
- Around line 37-52: Update delete_item to perform the existence check and
deletion atomically using the delete statement’s RETURNING result, rather than
calling db.get first. Derive the deleted item’s name from the returned row,
return False without publishing when no row is returned, and preserve the
existing commit, rollback, and ItemEvent publishing behavior for successful
deletion.
- Around line 22-34: Update create_item so it calls db.flush() before
db.commit() to populate the persisted item attributes needed by
ItemEventPayload, then remove the post-commit db.refresh(item) dependency.
Publish the event immediately after a successful commit using the flushed
item.id and item.name, while retaining rollback handling for commit/flush
IntegrityError failures.
In `@services/backend/app/healthcheck.py`:
- Around line 18-29: Reduce the per-check timeouts in main() for both
urllib.request.urlopen and stub.Check so their sequential worst-case duration
remains safely below the Docker healthcheck’s 10-second limit. Preserve the
existing failure handling and SERVING-status validation.
In `@services/backend/README.md`:
- Around line 114-120: Update the activation command in the documented setup
sequence to reference the backend virtual environment from the repository root,
consistent with the path created by make venv-install. Keep the remaining
root-level commands unchanged.
---
Nitpick comments:
In `@docs/ci.md`:
- Around line 75-80: Update the “No committed generated code (backend)”
documentation to replace “was ever committed” with wording that accurately
describes the git ls-files check, such as “is currently tracked” or “is
committed.” Preserve the existing explanation of the generate-in-build directory
and pipeline behavior.
In `@Makefile`:
- Around line 104-113: Update the generate target to remove the existing
services/backend/app/proto_gen directory before recreating it, then keep the
protoc generation commands unchanged so each run starts from a clean output
directory.
In `@services/backend/app/main.py`:
- Around line 28-41: No code changes are requested. Preserve the intentional
coupling in lifespan: start gRPC before yielding from lifespan and keep HTTP
startup blocked if start_grpc_server fails, while retaining graceful shutdown
through grpc_server.stop(grace=5).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 62241cfe-750e-4eb0-89bb-37ed5361a2ed
📒 Files selected for processing (26)
.github/workflows/backend.yml.github/workflows/images.yml.gitignoreAGENTS.mdMakefileREADME.mddeploy/compose/docker-compose.ymldocs/architecture.mddocs/ci.mddocs/exercises/02-grpc-contract.mddocs/prerequisites.mddocs/running-tests.mdproto/buf.gen.yamlservices/backend/.dockerignoreservices/backend/.ruff.tomlservices/backend/Dockerfileservices/backend/README.mdservices/backend/app/crud.pyservices/backend/app/events.pyservices/backend/app/grpc_server.pyservices/backend/app/healthcheck.pyservices/backend/app/main.pyservices/backend/app/metrics.pyservices/backend/pyproject.tomlservices/backend/tests/test_events.pyservices/backend/tests/test_grpc.py
💤 Files with no reviewable changes (1)
- proto/buf.gen.yaml
grpc.aio server on :50051 in the same process as FastAPI (ADR-0002: backend serves, never dials): ListItems snapshot, GetItemStats, and WatchItemEvents backed by an in-process emit-after-commit broadcaster. Per-subscriber bounded queues; a slow consumer is disconnected rather than blocking the request path, and reconnect recovers state via the ListItems snapshot -- at-most-once transport eventing, the documented gap the NATS capstone later closes. gRPC Health Checking Protocol served; container healthcheck now verifies HTTP and gRPC together. RED metrics via server interceptor on the existing registry.
Codegen is generate-in-build through a single grpc_tools.protoc run (python + pyi + grpc stubs); buf.gen.yaml removed -- its protoc_builtin plugins require a host protoc binary, which clean CI and Docker builds do not have (masked locally by a Homebrew protoc). Generated code is never committed (gitignored, dockerignored, CI gate). Serving is pinned to a single uvicorn worker: with multiple workers the broadcaster and gRPC listener fork per process behind SO_REUSEPORT and stream subscribers silently miss most events.
Summary by CodeRabbit
New Features
Documentation
Build & Quality