Skip to main content
Version: main

Architecture

The fastest way to understand this SDK is to follow one request all the way through it. This page does exactly that, then maps what you saw onto the repository so you know where to make your change.

First, the shape of the thing: the SDK is hand-written on Kiota. Microsoft's Kiota runtime supplies the machinery — URI templates, auth plumbing, serialization registries, retry middleware — and this repo hand-writes the ServiceNow-specific surface on top, deliberately mimicking the conventions of Kiota-generated SDKs like msgraph-sdk-go (why?). Keep that in mind throughout: whenever a component seems to exist "just because," the answer is usually "because that's how Kiota SDKs work, and familiarity is the feature."

The life of a request

Say a user writes:

record, err := client.Now().Table("incident").ByID(sysID).Get(ctx, config)

1. The chain builds a URL, lazily. Each call — Now(), Table(...), ByID(...) — constructs a child request builder: it clones the parent's path parameters, adds its own, and carries the same request adapter. Nothing has been sent; a builder is just an address. This is why builders are cheap to create and safe to store.

2. The verb method turns the address into a request. Get first runs the nil-guard prologue (returning shared sentinels like snerrors.ErrNilRequestBuilderwhy sentinels?), then builds a Kiota RequestInformation: HTTP method, the builder's URI template, and the caller's RequestConfiguration applied as headers and query parameters. Body-carrying verbs also serialize the model here via SetContentFromParsable.

3. The adapter executes it. The request adapter is the engine: it expands the URI template, asks the authentication provider (from credentials/) to stamp the Authorization header — every request, which is how token refresh stays invisible — and pushes the request through the middleware pipeline (retries with backoff, redirects, compression) to the instance.

4. The response becomes typed data — or a typed error. On a failure status, the error mapping passed by every verb method (core.DefaultErrorMapping()) turns the body into a typed *core.ServiceNowError subtype. On success, the body deserializes into backed models — property data lives in a change-tracking backing store, not struct fields (why?) — wrapped in a response envelope the user unwraps with GetResult().

That's the whole machine. Every API module is this same loop with different URLs and models.

Where things live

PathPurposeWhen you touch it
*api/ (for example, tableapi/)One package per ServiceNow API; request builders, models, per-verb configurationsAdding or changing an API surface — see the playbook
core/The shared skeleton: BaseRequestBuilder, BaseModel, response envelopes, error mapping, page iteratorRarely — changes here ripple through every module
internal/Implementation helpers (nil-checks, store accessors, serialization generators, query AST) — never imported by consumersWhen a pattern repeats across modules and deserves a helper
credentials/Authentication providers (Basic + the OAuth2 flows)Auth features and fixes
errors/The shared sentinel errors (snerrors)Almost never — reuse, don't add
tests/integration/, tests/e2e/Godog BDD suites and live-instance testsSee the testing guide
website/This documentation site, including compiled Go samples in website/snippets/Any PR that changes what users see
website/docs/contributing/blueprints/Per-module design blueprints for new API surfacesNew modules

Two rules of thumb fall out of this layout:

  • Compose, don't reinvent. If you're writing a nil-check, a property accessor, or serialization plumbing inside a module, stop — internal/ almost certainly has the helper, and using it keeps modules identical.
  • tableapi/ is the reference implementation. Fullest verb coverage, paging, generics. When you're unsure what a new file should look like, look there first (policyapi/ is the minimal counterpart).

Design patterns you'll be held to

  • Fluent interface — discoverability through chaining and IDE completion.
  • GenericsRequestBuilder[T model.ServiceNowItem] gives compile-time safety for response types.
  • Per-verb configuration types — invalid options are uncompilable, not runtime surprises.
  • Dependency injection — the HTTP client, middleware, and auth provider are all swappable, which is also what makes everything mockable in tests.

The why behind these lives in the design decisions — each one backed by an ADR:

Was this page helpful?