AnnouncementsIntroducing Arkoren

    Menu

    Common Pitfalls

    Recognize unsafe assumptions about ownership, configuration, HTTP behavior, data access, and deployment.

    What's on this page
    1. Avoid late cleanup, unchecked connectivity, and overly broad service dependencies.
    2. Diagnose callback precedence and missing event or telemetry configuration.
    3. Recognize middleware, recovery, validation, SQL dialect, and pagination mistakes.
    4. Protect operator endpoints and align backend state and shutdown behavior with deployment requirements.
    On this page

    When a service starts but behaves differently than expected, first check the boundary involved: who owns the resource, when configuration takes effect, and which guarantees the selected backend provides. These mistakes can leave code that compiles but does not do what its caller assumes.

    Resource Ownership

    Late Cleanup

    If composition acquires a resource and then fails while constructing a consumer, cleanup registered afterward is never reached. Register release as soon as you own the resource, before the next fallible operation.

    Application cleanup becomes active immediately and runs in reverse registration order, even if startup never runs. A registered stop function must therefore tolerate an unstarted resource. Keep that ownership in one place using Lifecycle and Ownership, rather than duplicating lifecycle wiring in every feature.

    Unchecked Connectivity

    A constructed driver is not necessarily connected. For example, Redis cache’s constructor creates its client without network I/O; Start checks connectivity. Omitting startup moves that failure to the first operation instead of rejecting startup.

    Follow the selected driver’s setup, including its available startup, cleanup, and readiness methods. Do not assume every driver has all three: some are stateless, and some memory drivers need no startup. Register lifecycle work with either your existing host/container or Arkoren’s application, not both.

    Broad Dependencies

    Passing *application.Application into business services gives them host control and runtime state they do not need. Passing a composer also permits hidden lifecycle registration. Keep those objects at the composition boundary; pass typed services, a logger, or an event bus explicitly.

    An event subscription is also an owned resource. Register its unsubscribe function for release instead of leaving listeners attached across repeated composition or tests; Own Subscriptions shows the focused pattern.

    Construction Policies

    Override Order

    Construction callbacks run in argument order on one draft. When a setting replaces an earlier value, put shared defaults first and the local override last. With an existing cache.Driver named driver and a shared cache.BuilderFunc named defaults, import arkoren.dev/core/service/cache:

    store := cache.New(driver, defaults, func(store *cache.Builder) {
    	store.Name("reports")
    })

    The final name is reports; reversing the callbacks lets defaults replace it. This is construction-time configuration, not a runtime settings API. Changing a retained draft cannot reconfigure the published facade. See Configuration for reusable policies and validation boundaries.

    Missing Observations

    Registering an event listener does not connect every facade to that bus. Components that expose an Events setting publish only when given a bus; a database listener on the application’s bus sees nothing if the database facade uses no bus. Application lifecycle events are separate from facade events.

    Likewise, a facade with no explicit telemetry follows telemetry.Default(); the initial process default records nothing. Setting a default does not start its exporters. Check both the component’s construction and the selected telemetry’s lifecycle before diagnosing a missing metric. Use Observe Outcomes for lifecycle reporting and Capture Setup for diagnostic capture. A failed diagnostic-tool declaration does not necessarily undo capture attachment.

    HTTP Boundaries

    Middleware Registration

    Router middleware is applied when routes are registered. Calling Use after registering a route through that router or one derived from it panics; existing routes would otherwise silently miss the new policy. Add shared middleware before routes, and choose a group or With for narrower policies as described in Choose Scope.

    Explicit Recovery

    router.New does not install panic recovery. For a buffered handler, install recovery before registration so an ordinary panic can become a 500 Problem Details response. Import arkoren.dev/core/http/router; status below is your existing router.Handler:

    routes := router.New()
    routes.Use(router.Recover())
    routes.Get("/status", status)

    Recovery protects only the middleware and handlers inside it. Keep request logging outside recovery so it observes the converted error; Order Policies explains the order. Once a response is committed, recovery cannot replace bytes already sent with a fresh error document.

    Hidden Errors

    Writing an error-shaped JSON body and returning nil tells outer middleware that the handler succeeded. It also bypasses central Problem Details rendering and its request-instance handling. Return the error instead of manually writing its representation, using Error Handling. Streams have a separate commitment boundary; follow Streaming Responses rather than trying to render a second response after streaming starts.

    Optional Zero Values

    Go-value validation treats scalar 0, "", and false as absent. Ordinary range and format rules skip absent values, so an integer schema with only Min(1) accepts zero. Add Required when zero means missing. If zero is a real supplied value that must be checked, use a pointer schema so a non-nil pointer is present and its pointee is validated.

    Decoding alone does not apply these rules. Follow Validate Input to run the schema at the request boundary, and Validation for presence semantics. Exact encoded JSON validation has different presence rules from validation of Go zero values.

    Data Assumptions

    Dialect Selection

    query.Query.Build() uses ANSI SQL with ? placeholders. Sending that SQL directly to PostgreSQL does not select PostgreSQL’s $1 placeholders; MySQL also has different identifier quoting. Pass published queries to the database facade so it compiles for the configured driver’s dialect. If you need SQL text, use BuildFor with the intended dialect, as shown in Query Builder.

    Stable Pagination

    Cursor pagination needs a total order. Ordering only by a non-unique timestamp can skip or repeat rows at page boundaries. For a reports table with a unique id, import arkoren.dev/foundation/query and end the order with that key:

    reports := query.Table("reports", func(statement *query.Builder) {
    	statement.OrderByDesc("created_at")
    	statement.OrderByDesc("id")
    })

    Rows sharing created_at now have a deterministic tie-breaker. Use Pagination for cursor construction and Batching and Streaming for traversing larger result sets; ordering alone does not choose a traversal strategy.

    Deployment Limits

    Process-Local State

    Memory drivers are useful for tests, development, and intentionally ephemeral state. Separate replicas do not share their contents, and a restart loses them. Before deploying state that must survive or be shared, select a suitable backend and verify its persistence configuration using Choosing a Driver.

    Operator Exposure

    InfoHandler, verbose readiness, and diagnostic tools can reveal internal metadata. They do not become protected merely because they are mounted on a second port. Add an actual network or authorization boundary before exposing them. Mount Probes distinguishes minimal probes from detailed reports.

    Shutdown Budgets

    If the platform kills a process before draining and cleanup finish, connections and in-flight work can be cut off. Keep the HTTP drain timeout inside the application budget and allow additional platform time for cleanup and telemetry finalization. Follow Shutdown Budgets instead of copying one timeout to every layer.

    Shutdown does not automatically cancel request contexts already in progress. A long poll or server-sent event handler must also observe server.ShuttingDown(ctx) and return cooperatively; otherwise it can hold the drain open until the timeout. Streaming Responses shows that cancellation boundary.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive