Skip to content

#47 Build Element injectors with Guice Stage.PRODUCTION - #73

Open
krh372 wants to merge 2 commits into
mainfrom
feature/47-guice-stage-evaluation
Open

#47 Build Element injectors with Guice Stage.PRODUCTION#73
krh372 wants to merge 2 commits into
mainfrom
feature/47-guice-stage-evaluation

Conversation

@krh372

@krh372 krh372 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • GuiceElementLoader.load() built every Element injector under Guice's default Stage.DEVELOPMENT. Switched to Stage.PRODUCTION so @Singleton-scoped bindings are constructed eagerly at Element-load time and every binding is validated up front (fail-fast at load time instead of first use).
  • Fixes Evaluate Guice loading-mode/performance flags for Element injectors #47

Why this is safe

Verified against issue #40's eager-singleton/Datastore ordering risk before making this change:

  • GuiceSpiModule.bindAndExposeService() binds each Element service with no scope annotation — scope is entirely author-controlled, so Stage.PRODUCTION's extra eagerness only affects service classes an Element author explicitly marks @Singleton.
  • Repo-wide, explicit .asEagerSingleton() is already the dominant pattern (194 call sites) vs. plain @Singleton (9 classes), so Stage.PRODUCTION changes very little that isn't already eager today.
  • The two @Singleton classes that inject Datastore directly (MongoUserDao, MongoConcurrentUtils) are safe: Datastore is bound as LiveDatastore, a live-delegating proxy over the mutable AtomicReference<Datastore>, not a frozen snapshot — this is the architectural fix for Mongo DAO insert throws Field-reflection IllegalArgumentException when called from a JAX-RS thread crossing an Element boundary, but not from a background thread #40's failure mode, already applied repo-wide. No other raw-Datastore-into-singleton pattern exists in sdk-spi-guice, sdk-guice, service-guice, mongo-guice, or sdk-mongo.
  • Residual risk is limited to third-party Elements binding their own service @Singleton and injecting a shared mutable dependency directly, bypassing a proxy/Provider — outside this repo's control; documented as author guidance in the accompanying manual PR (Document eager singleton construction (Stage.PRODUCTION) for Element injectors elements-manual#6).

Test plan

  • mvn -pl sdk-spi-guice -am install -DskipTests — compiles cleanly
  • mvn -pl sdk-spi-guice test — 3/3 tests pass
  • mvn -pl sdk-test-element,sdk-test-element-a,sdk-test-element-b,sdk-test -am install -DskipTests && mvn -pl sdk-test test — 27/27 tests pass, exercising the full isolated-classloader Element-loading pipeline (services, produced/consumed events, default/required attributes) under Stage.PRODUCTION
  • Manual smoke test against a running jetty-ws instance

GuiceElementLoader.load() previously called Guice.createInjector() with no
explicit Stage, so every Element injector ran under Stage.DEVELOPMENT. Switched
to Stage.PRODUCTION for eager singleton construction and upfront binding
validation at Element-load time.

Verified safe against issue #40's eager-singleton/Datastore ordering risk:
GuiceSpiModule's service bindings are unscoped by default (scope is
author-controlled), the repo's own @singleton usages (MongoUserDao,
MongoConcurrentUtils) inject Datastore only through the LiveDatastore
live-delegating proxy over the shared AtomicReference<Datastore>, and no other
raw-Datastore-into-singleton pattern exists in sdk-spi-guice, sdk-guice,
service-guice, mongo-guice, or sdk-mongo. Confirmed via the full sdk-test
(27 tests) and sdk-spi-guice (3 tests) suites, unchanged under the new stage.
public Element load(final MutableElementRegistry parent) {

final var injector = Guice.createInjector(
Stage.PRODUCTION,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know there's more than one place we call createInjector. We should always make sure that we have a system define or environment variable to control this instead of forcing it. We should make the default the slow DEVELOPMENT mode and only turn that on in server deployments.

Reviewer feedback on the previous commit: don't hardcode Stage.PRODUCTION for
Element injectors, and audit every Guice.createInjector() call site rather
than treating GuiceElementLoader in isolation.

Adds GuiceStages.get() (sdk-guice) which resolves the Stage from the
dev.getelements.elements.guice.stage system property, falling back to the
ELEMENTS_GUICE_STAGE env var, defaulting to Stage.DEVELOPMENT if neither is
set or the value doesn't match a known stage. Wires it into all 8
createInjector call sites in the repo: GuiceElementLoader (per-Element
injector), MongoTransactionProvider (per-transaction injector, previously
also hardcoded PRODUCTION), and the top-level injectors for ElementsMain,
Migrate, Setup, ApplicationNode, StatusCheck, and MavenElementsLocalBuilder
(all previously implicit DEVELOPMENT with no way to opt into PRODUCTION).

Net effect: default behavior everywhere reverts to pre-#47 Stage.DEVELOPMENT;
PRODUCTION is now strictly opt-in for real server deployments.
@krh372
krh372 requested a review from ptwohig August 27, 2026 20:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Evaluate Guice loading-mode/performance flags for Element injectors

2 participants