Reworked `Settings` class to replace the static singleton pattern with constructor injection of `SettingsProvider`, improving testability and modularity. Updated all affected classes, tests, and configurations to align with the new architecture.
4.7 KiB
4.7 KiB
sessionId
| sessionId |
|---|
| session-260909-154047-1h6n |
Requirements
Overview & Goals
Remove the singleton pattern from Settings so it becomes a plain instantiable class. All access should go through constructor injection of SettingsProvider, except for the single bootstrap site in App.main() which creates the instance.
Scope
In Scope:
- Remove
static INSTANCEfield andgetInstance()fromSettings - Remove static helper methods
getDiscordInvite()andgetVersion()fromSettings - Migrate all remaining
Settings.getInstance()call sites to use injectedSettingsProvider - Update the test to construct
Settingsdirectly
Out of Scope:
- Changing the
SettingsProviderinterface - Introducing a DI framework
- Changing how properties are loaded
Technical Design
Current Implementation
Settingsis a lazy singleton (getInstance()) implementingSettingsProviderSettingsProviderinterface already exists and is injected into most classes viaApplicationContextApp.buildContext()already registers the Settings instance asSettingsProviderin the context- Remaining direct singleton usages (9 call sites):
App.java—VERSIONstatic field (line 51),main()bootstrap (lines 61-62),startup()(line 89),buildContext()(line 112),initJDA()(line 190)CommandContext.java—isMessageFromCreator()(line 134)WebModule.java—createRequest()(line 289) viaSettings.getVersion()ReportBug.java—executeTask()(line 34) viaSettings.getDiscordInvite()SettingsTest.java—getMavenVersion()test (line 17)
Proposed Changes
1. Settings.java
- Make constructor
public - Remove
static INSTANCEfield,getInstance(),getDiscordInvite(),getVersion() - Keep
getMavenVersion()as an instance method - Add
getDiscordInvite()as an instance method onSettingsProvider(or keep it as a convenience onSettings)
2. App.java
- Create
Settingsvianew Settings()at the top ofmain(); pass it intoAppconstructor or store as a field - Replace
VERSIONstatic field with an instance field initialized from the settings instance - Thread the
SettingsProviderthroughstartup(),buildContext(),initJDA()
3. CommandContext.java
- Add a
SettingsProviderfield, accept it via constructor parameter - Update
isMessageFromCreator()to use the injected provider - Update subclasses (
MessageCommandContext,SlashCommandContext, etc.) to pass it through
4. WebModule.java
- Accept a version string or
SettingsProviderin theBuilder; replaceSettings.getVersion()call
5. ReportBug.java
- Accept
SettingsProvidervia constructor injection; replaceSettings.getDiscordInvite()withsettings.get("NETNEUTRALITY_BOT_INVITE", ...)
6. SettingsTest.java
- Use
new Settings()instead ofSettings.getInstance()
Risks
CommandContextis abstract with multiple subclasses — all constructors need updatingWebModule.Builderis used in many places including insideSettings.getGitVersion()itself — the version string approach (pass aStringrather than the full provider) keeps this simpler
Delivery Steps
✓ Step 1: Remove singleton from Settings class
Settings can be instantiated with new Settings() and no longer holds static state.
- Make the
Settingsconstructorpublic - Remove the
static INSTANCEfield andgetInstance()method - Remove static helpers
getDiscordInvite()andgetVersion() - Move
getDiscordInvite()logic to an instance method (or inline at call sites) - Keep
getMavenVersion()as-is (already instance method)
✓ Step 2: Migrate App.java bootstrap to explicit instantiation
App creates and threads a single Settings instance through its lifecycle methods.
- Create
new Settings()at the start ofmain()and store as a field onApp - Replace
Settings.getInstance()calls inmain(),startup(),buildContext(),initJDA()with the field - Replace the
VERSIONstatic field with an instance field or a local derived from the settings instance
✓ Step 3: Inject SettingsProvider into remaining call sites
CommandContext, WebModule, and ReportBug no longer reach for the singleton.
- Add
SettingsProviderconstructor parameter toCommandContext; updateisMessageFromCreator()to use it - Update
CommandContextsubclasses (MessageCommandContext,SlashCommandContext, etc.) to pass the provider through - In
WebModule.Builder, accept a version string parameter; replaceSettings.getVersion()increateRequest() - In
ReportBug, addSettingsProviderconstructor parameter; replaceSettings.getDiscordInvite()withsettings.get(...) - Update
SettingsTestto usenew Settings()directly