Generated service clients
Generate Go HTTP clients from each service's OpenAPI contract and use them from other services.
Every service listed in SERVICES in the Makefile publishes a generated Go client at
services/<service>/client/client.gen.go. The input is that service's openapi.yaml,
exported by Huma from its handler types. The merged platform spec is not the input for these clients.
Services without an OpenAPI contract are not included.
Generate clients
From the repository root:
make generateThis first generates clients from the committed specs so service entrypoints can compile even if generated clients have been removed. It then exports current specs and generates the clients again. Commit changes to the specs and generated clients together. Do not edit generated files.
To generate only clients from existing specs, without exporting new specs or running other generators:
make generate-clients
make generate-clients SERVICES=accountingThe second command limits generation to Accounting. Use make generate after changing handler types;
generate-clients alone does not update a stale spec. Ordinary go build compiles committed clients
and does not run generators.
The generator is pinned as a Go tool in go.mod. All services use the same
tools/oapi-codegen.yaml configuration: models and HTTP client methods, with
no generated server. The implementation type is named APIClient to leave Client available as
an API schema name. The generated ClientWithResponses provides typed response bodies.
Source definitions belong with the server's handler types. A service must not define its API using types imported from its own generated client: that would make generation depend on its own output.
Construct a client at startup
Import the provider's client package, read its URL from the consuming service's configuration, and pass it to the generated constructor. Reuse the client across requests.
import (
"time"
"dev.azure.com/lifeinflow/Flow/flow-technology/common/httpapi"
accountingclient "dev.azure.com/lifeinflow/Flow/flow-technology/services/accounting/client"
)
// accountingURL comes from the caller's configuration.
c, err := accountingclient.NewClientWithResponses(accountingURL,
accountingclient.WithHTTPClient(httpapi.NewClient(10 * time.Second)),
)
if err != nil {
return err
}The client accepts a local address or a Kubernetes service URL. No deployment URL is baked into
generated code. The shared HTTP client propagates trace context and the current request ID; see
HTTP tracing. Supply authentication through WithRequestEditorFn
or an explicitly configured transport. The generator does not acquire credentials or retry failed commands.
Methods ending in WithResponse decode documented response bodies and close the HTTP response body.
Check both the HTTP status and the corresponding typed body: a non-success status does not by itself
produce a Go error. Methods without that suffix return *http.Response; the caller closes its body.
Changes and compilation
Building a consumer also compiles the generated clients it imports, at the same repository revision.
For example, go build ./services/billing/cmd/api checks Billing's use of Accounting's client.
Removing a method or changing a referenced field's Go type can therefore break Billing's build.
Use go build ./... to check all consumers in the module.
Compilation does not prove compatibility with older deployed binaries or detect every change to JSON or business behavior. OpenAPI compatibility checks and release coordination remain separate work.
Pull request validation
.azure/go-checks.yml runs make check-generated-ci when a generator input changed, then builds the services in the diff.
Per-service specs, clients, and bus topology are regenerated for the services whose inputs changed.
A change under common/, cmd/, tools/, go.mod, or sqlc.yaml regenerates every service.
sqlc and the merged root spec always cover the whole repository. A test file does not trigger
generation. Frontend application source, docs/, and bruno/ do not run this check.
A pipeline file or k8s values file checks the devops catalog only. The action catalog is checked by its own step, so the CI target does not rewrite it.
The check exports the specs from the current handlers, regenerates the clients, and fails
if tracked files differ from the commit, any non-ignored untracked files exist, or an expected client
is not tracked (even if ignored). This includes new output from the other generators, such as sqlc.
It catches a handler change even when both the committed spec and client were left stale.
The build is ./services/<name>/... for each service with a changed file, plus any service that imports a changed client.
A change under common/, cmd/, or go.mod builds the whole module with go build ./....
Run the same generation check from a clean, committed checkout:
make check-generatedThat target also rewrites the action catalog. The pipeline uses make check-generated-ci, which
does not, because the catalog has its own check. To regenerate one service's spec and client,
run make check-generated-ci GEN_SERVICES="billing" from a clean checkout. The check regenerates
files in place. If it fails, run make generate, review the changes, commit
the specs and generated files, and push again. make check-static includes make check-generated
along with the existing static checks. No Git hook is required.
Before generation, the pipeline fetches the pull request's target branch for comparison.
Only that fetch step receives System.AccessToken, through an environment variable used by Git's
command-scoped authorization header. Checkout does not persist credentials in Git config.
Require the check in Azure DevOps
The YAML alone does not block merges. In Azure DevOps, register the Go checks pipeline using
.azure/go-checks.yml if it is not already registered. Under Repos > Branches > Branch policies,
add or update its Build validation policy for each of main and develop:
- Select the Go checks pipeline.
- Set the trigger to Automatic and the policy requirement to Required.
- Leave the path filter empty so every pull request is checked.
- Set build expiration to Immediately when the target branch is updated.
Make the YAML available on the target branches and verify a successful pipeline run before requiring
it. These settings live in Azure DevOps, separately from the repository. Azure Repos uses branch
policies to run pull request validation; adding a YAML pr trigger does not configure the policy.
Contributors should not have Bypass policies when pushing or Bypass policies when completing
pull requests permissions. Those permissions allow a user to skip an otherwise required check.
See Azure's branch policy documentation.
Adoption
Billing's scheduled-charge posting uses Accounting's generated client. See Billing's Accounting integration. Other handwritten integrations remain in place until they are migrated separately.
For a new service, keep handler types independent of generated clients, provide the existing
cmd/api -openapi export, and add the service to SERVICES. The shared target creates its client
directory automatically; no per-service generator configuration is needed.