3.3 KiB
name, description
| name | description |
|---|---|
| add-rpc | Scaffold a new gRPC RPC end-to-end (proto, buf generate, hexagonal ports/app/adapter layers, tests, README) following this repo's established pattern across ha-gateway, ai-gateway, and discord-bot. |
Add a gRPC RPC
Use this whenever a new RPC or field is being added to ha-gateway, ai-gateway, or a new
gRPC client call is being added to discord-bot. All three services share the same
hexagonal layering, so follow this order — do not skip layers or reach across them.
Steps
-
Identify the target service and proto package.
ha-gateway→proto/ha/v1/*.proto(packages:entity,light,switch,event,common)ai-gateway→proto/ai/v1/ai.protodiscord-bothas no proto of its own; it only consumesha.v1/ai.v1clients.
-
Edit the
.protofile underproto/<pkg>/v1/. Add the message/RPC there first — it is the source of truth;gen/is derived from it. -
Regenerate generated code:
buf generateThis writes into
gen/<pkg>/v1/. Never hand-edit files undergen/. -
Extend the port interface in
internal/core/ports/:ports/driving/*.go— the interface the gRPC adapter calls into (app-facing, inbound).ports/driven/*.go— the interface the app calls out to (HA REST, Ollama, ha-gateway client, etc.), if the new RPC needs a new external call.core/domain/*.go— add/extend domain types here, not in adapter or proto types.
-
Implement orchestration in
internal/app/. This is where business logic and validation live — it depends only oncore/domainandcore/ports, never onadapters/*types directly. Look at an existing file (e.g.ha-gateway/internal/app/light.go) for the shape: a struct holding driven-port dependencies, constructed viaNewXApp(...). -
Wire the primary adapter in
internal/adapters/primary/grpc/<feature>.go. Implement the generated server interface method, translate between proto types and domain types (seemapping.goin ha-gateway for the existing translation pattern), and call the app layer. Keep proto <-> domain conversion here, not inapp. -
If the RPC needs a new outbound call, implement it in the relevant
internal/adapters/secondary/<name>/client.go, implementing thedrivenport interface from step 4. -
Add a test using the repo's convention: plain
testingpackage (no testify), with a hand-written mock struct that implements the driven port interface using function fields (seeha-gateway/internal/app/entity_test.goorai-gateway/internal/app/query_test.gofor the pattern). Table-drivent.Runsubtests are the norm. -
Update the service's
README.md:- Move the RPC between "Implemented" and "Stubbed" in the gRPC API section if its status changed.
- Add any new environment variables to the Configuration table (and to
.env.example). - Add any new package to the Package Map section if you created one.
-
Verify:
cd <service> && go vet ./... && go test ./...
Layering rule to enforce
Dependencies point inward only: adapters → app → core. Never import
internal/adapters/* from internal/app or internal/core. Only cmd/<entrypoint>/main.go
is allowed to import across all layers to wire dependencies together.