Repository navigation
[PHEE-417] Bind configuration through typed records - #4
Merged
tdaly61 merged 1 commit intoOct 7, 2026
Merged
Conversation
Eighteen of the twenty-one @value fields, reading ten keys in two classes, become two records in org.apache.fineract.config.properties: fineract.datasource and token. DataSourcePerTenantService and TenantDatabaseUpgradeService now take them through their constructor. Every record is @validated, every component is @NotNull, and the database port is an Integer. None has a @DefaultValue: none of the eighteen replaced @value had a default. A missing key stops startup, as before, and the error names the property. An empty number stops startup, an empty String is accepted. The two token validities stay strings, as they were: they are passed on as text to the tenant schema migrations. spring-boot-starter-validation is added because nothing on the classpath provided Bean Validation; without it @validated is ignored. The version comes from the BOM. Three @value stay: - spring.security.filter.order. It is Spring Boot's own key, and Spring Boot gives it a default of -100. A bare @value keeps it required, so a configuration without it fails at startup instead of silently running the security filter before the tenant filter. - fineract.datasource.core.auto-update, read with a default of true. A missing key means true, but an empty one stops startup. In a record an empty value binds to null and a default turns it into true. - tenants, a Spring expression, which only @value evaluates. ShippedConfigBindsTest binds the two records from the real application.properties, checks that each one on its own refuses to start when nothing is configured, that a key missing inside a nested group stops startup, and pins the empty values: an empty number stops startup, an empty String is accepted.
tdaly61
approved these changes
Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Eighteen of the twenty-one
@Valuefields, reading ten keys in two classes, become two typed records inorg.apache.fineract.config.properties. The configuration this service needs is now written down in one place, and a missing or wrong value stops startup with a message that names the property. Three@Valuestay, each for a reason given below.What changes
FineractDatasourcePropertiesfineract.datasourcecore.host,core.port,core.schema,core.username,core.password,common.protocol,common.subprotocol,common.driverclass_nameDataSourcePerTenantService,TenantDatabaseUpgradeServiceTokenPropertiestokenaccess.validity-seconds,refresh.validity-secondsTenantDatabaseUpgradeServiceEvery record is
@Validated, every component is@NotNull, and the database port is anInteger, because@NotNullcannot fire on a primitive. No record has a@DefaultValue: none of the eighteen replaced@Valuehad a default.FineractDatasourcePropertieshas the same shape as the record of the same name inpaymenthub-ee-bff.The two token validities stay strings, as they were:
TenantDatabaseUpgradeServicepasses them on as text to the tenant schema migrations.The two services take the records through their constructor. No property name changes, and nothing about tokens, users or tenants changes.
spring-boot-starter-validationis added, with the version from the BOM. Nothing on the classpath provided Bean Validation, and without it@Validateddoes nothing.Three
@Valuethat stayspring.security.filter.orderinServerApplication. This is Spring Boot's own key, and Spring Boot gives it a default of-100. With that default, Spring Security runs before the tenant filter, so the token audience check runs with no tenant and every valid token is refused with 401 (see the comment on this key inapplication.properties). A bare@Valuekeeps the key required, so a configuration without it fails at startup instead of running with-100.fineract.datasource.core.auto-update, read as@Value("${fineract.datasource.core.auto-update:true}"). A missing key meanstrue, but an empty one stops startup. A record cannot do both: an empty value binds tonull, and a default would then turn it intotrue.tenants, a Spring expression (#{'${tenants}'.split(',')}), which only@Valueevaluates.Same result as before
Stringis accepted, as before.How it was checked
Build.
./gradlew clean buildwith JDK 21.0.10: green, 28 tests (5 new, plus the 23 inPemUtilsTest,ResourceServerConfigValidatorTestandTokenControllerTest). This repo has no spotless, checkstyle or Error Prone.The new test.
ShippedConfigBindsTestbinds the two records from the realapplication.propertiesand checks the values, includingdriverclass_name, which binds todriverclassName. It checks that each record, on its own and with nothing configured, refuses to start and names its prefix. It checks that a key missing inside a nested group (core.password) stops startup, that an emptycore.portstops startup, and that an emptytoken.access.validity-secondsis accepted. To check that the test can fail, it was run against three broken copies of the code, one at a time:@ValidatedonTokenPropertieseveryRecordRefusesToStartWhenItsSectionIsMissing@ValidoncoreaMissingKeyInsideAGroupStopsStartup,aValueSetToNothingOnANumberFieldStopsStartup@NotNullonportaValueSetToNothingOnANumberFieldStopsStartupWhat a deployment passes. This service is not deployed on gazelle3, and there is no CR for it in GAZ-348, so no deployment environment exists to compare with. Its configuration comes from
application.propertiesand thebb,medandlargeprofiles. None of them uses a${...}placeholder.Runtime, against a throwaway MySQL. The service applies database migrations at startup, so it was not pointed at any shared database. A separate MySQL 5.7 pod was started on gazelle3 with temporary storage and empty schemas, and then deleted. The service pods ran with profile
large(tenantstn01,tn02) and with only the database host and password changed to reach that MySQL. Every pod was deleted afterwards.tenantsand 28 to each oftn01andtn02, then started in 19.9s, no errordevFINERACT_DATASOURCE_CORE_PORT=""Property: fineract.datasource.core.port,Reason: must not be null, exit 1devFINERACT_DATASOURCE_CORE_PORT=""NumberFormatException: For input string: "", exit 1The HTTP checks done for the migration (token grants,
/api/v1, tenant isolation) were not repeated here. This PR changes how the database settings and token validities are read, not how they are used.Ticket: PHEE-417.