-
Notifications
You must be signed in to change notification settings - Fork 750
Description
| 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:
-
Unnecessary coupling - Importing
endpoint_utilstoday pulls in transitive dependencies onfleet.Host,fleet.User,fleet.LicenseInfo, and other business types that many HTTP endpoints don't need -
Cognitive overhead - Engineers working on a new bounded context shouldn't need to understand unrelated domain concepts just to set up HTTP endpoints
-
Slower compilation - More transitive dependencies means longer build and test times, especially as the codebase grows
-
Inverted dependencies - Infrastructure code (HTTP utilities) should not depend on business domain types; dependencies should flow from domain toward infrastructure, not the reverse
-
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:
- Safe to import from any bounded context without pulling in business dependencies
- Stable - platform code changes less frequently than domain code
- Reusable - designed for use across all bounded contexts
- 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:
- Request decoding - JSON unmarshaling, URL/query parameter extraction
- Response encoding - JSON, HTML, cookies, status codes
- Error handling - Error-to-HTTP-status mapping
- Endpoint registration - Fluent API for route setup with versioning/middleware
- 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
endpointbuilderwas considered but is longer
Changes
Diagram of current endpoint_utils Fleet dependencies
Based on the POC implementation, the refactoring uses two main techniques:
- Extract types to platform package - Move generic types out of
server/fleet/intoserver/platform/http/ - 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
- Engineer: Added comment to user story confirming successful completion of test plan.
- QA: Added comment to user story confirming successful completion of test plan.
Sub-issues
Metadata
Metadata
Assignees
Labels
Type
Projects
Status
Status