Skip to content

Refactor endpoint_utils into platform/endpointer #37192

@getvictor

Description

@getvictor
User story
As a Fleet backend engineer,
I want to refactor endpoint_utils into a generic package without business domain dependencies and move it to a new "platform" directory
so that I can reuse this infrastructure code across bounded contexts.

Context

As part of the modular monolith transition, we need to extract generic infrastructure code that can be shared across bounded contexts. The endpoint_utils package currently has dependencies on Fleet business domain types, which creates several problems:

  1. Unnecessary coupling - Importing endpoint_utils today pulls in transitive dependencies on fleet.Host, fleet.User, fleet.LicenseInfo, and other business types that many HTTP endpoints don't need

  2. Cognitive overhead - Engineers working on a new bounded context shouldn't need to understand unrelated domain concepts just to set up HTTP endpoints

  3. Slower compilation - More transitive dependencies means longer build and test times, especially as the codebase grows

  4. Inverted dependencies - Infrastructure code (HTTP utilities) should not depend on business domain types; dependencies should flow from domain toward infrastructure, not the reverse

  5. Circular dependency risk - Bounded contexts that need HTTP utilities would also import unrelated business types, creating potential import cycles as the codebase grows

The platform directory

Many industry sources agree that: "It is not reasonable to repeatedly re-implement the infrastructure requirements in every module of a modular application."

In modular monolith architecture, we distinguish between two types of code:

Type Description Location
Domain code Business logic unique to Fleet (hosts, policies, software, etc.) bounded context directories
Platform code Generic infrastructure that contains no Fleet business concepts server/platform/

Platform code includes utilities that:

  • Provide common infrastructure (HTTP handling, logging, database utilities, etc.)
  • Don't describe business logic unique to Fleet
  • Could theoretically be replaced by off-the-shelf solutions without impacting business logic
  • Serve as foundational building blocks for all bounded contexts

Examples of platform code

  • HTTP endpoint utilities (platform/endpointer)
  • Error handling primitives
  • Rate limiting infrastructure
  • Database utilities
  • Logging and instrumentation

Why a separate directory?

The server/platform/ directory makes it immediately clear which code is:

  1. Safe to import from any bounded context without pulling in business dependencies
  2. Stable - platform code changes less frequently than domain code
  3. Reusable - designed for use across all bounded contexts
  4. Standardized - ensures consistent patterns across modules

What is endpoint_utils used for?

The endpoint_utils package provides the core HTTP infrastructure for Fleet's API endpoints:

  1. Request decoding - JSON unmarshaling, URL/query parameter extraction
  2. Response encoding - JSON, HTML, cookies, status codes
  3. Error handling - Error-to-HTTP-status mapping
  4. Endpoint registration - Fluent API for route setup with versioning/middleware
  5. Utilities - IP extraction, security headers, tag parsing

Why rename to endpointer?

The name endpointer is:

  • Idiomatic Go - no underscores, follows Go naming conventions
  • Descriptive - clearly indicates it's something that creates/handles endpoints (like http.Handler)
  • Concise - an alternative like endpointbuilder was considered but is longer

Changes

Diagram of current endpoint_utils Fleet dependencies

Based on the POC implementation, the refactoring uses two main techniques:

  1. Extract types to platform package - Move generic types out of server/fleet/ into server/platform/http/
  2. Dependency injection via interfaces - Replace direct imports with interfaces that domain packages implement

Packages affected

Package Changes
server/platform/http/ New package. Contains extracted error types (ErrorWithUUID, ErrWithInternal, ErrWithRetryAfter, etc.), Cause() function, OrderDirection constants, and CheckMissing auth error
server/fleet/errors.go Types replaced with aliases pointing to platform_http (backward compatible)
server/contexts/ctxerr/ Created interfaces (ErrorAttributeProvider, TelemetryAttributeProvider) and provider registration functions. Removed direct imports of host and viewer packages
server/contexts/license/ Created LicenseChecker interface so packages can check license without importing fleet.LicenseInfo
server/contexts/logging/ Created UserEmailer interface to decouple from viewer package
server/contexts/host/ Added HostAttributeProvider struct implementing the ctxerr interfaces
server/contexts/viewer/ Added GetErrorAttributes() and GetTelemetryAttributes() methods
server/authz/ CheckMissing became an alias to platform_http.CheckMissing
server/service/ Updated imports; uses dependency injection for license checking

Example: License checking

Before (direct dependency):

import "github.com/fleetdm/fleet/v4/server/fleet"

func IsPremium(ctx context.Context) bool {
    lic, ok := ctx.Value(licenseKey).(*fleet.LicenseInfo)
    return ok && lic.IsPremium()
}

After (interface-based):

// In contexts/license/license.go
type LicenseChecker interface {
    IsPremium() bool
    GetTier() string
}

func IsPremium(ctx context.Context) bool {
    lic, ok := ctx.Value(licenseKey).(LicenseChecker)
    return ok && lic.IsPremium()
}

The fleet.LicenseInfo type implements LicenseChecker, so existing code continues to work.

QA

Smoke test existing features to ensure no regressions.

Confirmation

  1. Engineer: Added comment to user story confirming successful completion of test plan.
  2. QA: Added comment to user story confirming successful completion of test plan.

Sub-issues

Metadata

Metadata

Assignees

Labels

#g-security-complianceSecurity & Compliance product groupstoryA user story defining an entire feature~engineering-initiatedEngineering-initiated story, such as a bug, refactor, or contributor experience improvement.

Type

No type

Projects

Status

🐣 In progress

Status

✔️Awaiting QA

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions