Context
Problem Statement
HDIM uses shared modules (authentication, audit, persistence) to provide cross-cutting concerns. These modules use @AutoConfiguration with @EntityScan and @ComponentScan that force-register entities and beans in every consuming service. When a service has its own database that doesn't contain the tables those entities expect (e.g., tenants, users, api_key_allowed_ips, audit tables), Hibernate validation fails at startup.
This creates cascading startup failures — a chain of 5 distinct errors that only surface one at a time as you fix each layer:
DuplicateKeyException before Spring context loads@Bean definitions (e.g., objectMapper) from shared and service configssqlFile paths, missing liquibase-schema: public@EntityScan pulling in auth/audit entities whose tables don't exist in this DBAIAuditEventPublisher)This pattern was discovered fixing notification-service (commits f212b4327, e8a2cc88b, 46ea65b9b) and identically reproduced in prior-auth-service (commit 8c6918ea4). It will recur as new services onboard shared modules.
Options Considered
Option 1: Implicit Opt-In (Current Broken State)
Description: Shared modules auto-configure everything via classpath scanning; services inherit all entities and beans.
Pros:
Cons:
validate mode fails on missing tablesRisk Level: High
Option 2: Explicit Opt-In with Guards (Chosen)
Description: Services must explicitly opt in to shared module features. Shared modules guard optional components with @ConditionalOnBean/@ConditionalOnProperty. Services narrow their entity/repository scans.
Pros:
Cons:
Risk Level: Low
Decision
We chose Option 2 (Explicit Opt-In with Guards) because:
Consequences
Positive
Negative
Implementation
8-Step Service Onboarding Checklist
When a service uses shared modules (authentication, audit, persistence), apply these steps:
#### Step 1: YAML Structure Audit
Check application.yml for duplicate top-level keys (spring:, management:, feign:, etc.). YAML silently drops the first block when keys are duplicated; merge into one.
Find duplicate top-level keys
grep -n "^[a-z]" src/main/resources/application.yml | sort -t: -k2 | uniq -f1 -d
#### Step 2: Auth Auto-Configuration Exclusion If the service's database does NOT have `users`/`tenants`/`api_keys` tables, exclude authentication auto-configurations:
@SpringBootApplication(exclude = {
AuthenticationAutoConfiguration.class,
AuthenticationControllerAutoConfiguration.class
})
Narrow `scanBasePackages` to only the auth sub-packages actually needed (filters, security). Do NOT include root `com.healthdata.authentication`. #### Step 3: Narrow Entity and Repository Scans Restrict `@EntityScan` and `@EnableJpaRepositories` to service-specific packages only:
@EntityScan("com.healthdata.yourservice.persistence")
@EnableJpaRepositories("com.healthdata.yourservice.persistence")
Never include `com.healthdata.authentication.domain` or `com.healthdata.authentication.entity` unless the service's DB has those tables. #### Step 4: Unique Liquibase Changelog Filename Every service must have a service-named master changelog to avoid classpath collision:
Good
yourservice-changelog-master.xml
Bad (collides with shared JARs)
db.changelog-master.xml
#### Step 5: Liquibase Schema Configuration When using a custom `default-schema` (e.g., `prior_auth`), add `liquibase-schema: public` so Liquibase tracking tables (`databasechangelog`, `databasechangeloglock`) are created in `public` before the first migration creates the custom schema:
spring:
liquibase:
enabled: true
liquibase-schema: public
#### Step 6: Correct Liquibase SQL File Paths When using `relativeToChangelogFile="true"`, paths must be relative to the changelog XML, not the classpath root:
<!-- Good (relative to changelog XML) -->
<sqlFile path="sql/0001-create-schema.sql" relativeToChangelogFile="true"/>
<!-- Bad (includes the parent directory that's already the context) -->
<sqlFile path="db/changelog/sql/0001-create-schema.sql" relativeToChangelogFile="true"/>
#### Step 7: Guard Optional Dependencies Any component requiring beans from optional modules must be guarded:
// Guard the entire class
@ConditionalOnBean(AIAuditEventPublisher.class)
public class MyAuditIntegration { ... }
// Or make injection optional
@Autowired(required = false)
private MyAuditIntegration auditIntegration;
// Then null-check at call sites
if (auditIntegration != null) {
auditIntegration.publishEvent(event);
}
#### Step 8: Resilience4j Retry Configuration Never combine `.waitDuration()` and `.intervalFunction()` in the same `RetryConfig.Builder`. Use only `intervalFunction` for exponential backoff:
// Good
RetryConfig.custom()
.intervalFunction(IntervalFunction.ofExponentialBackoff(1000, 2))
.build();
// Bad — throws IllegalArgumentException
RetryConfig.custom()
.waitDuration(Duration.ofMillis(1000))
.intervalFunction(IntervalFunction.ofExponentialBackoff(1000, 2))
.build();
Docker Compose Environment
Verify docker-compose.yml environment variables match intended config. Common issues:
SPRING_LIQUIBASE_ENABLED: "false" when it should be "true"SPRING_LIQUIBASE_CHANGE_LOG overrideaudit.kafka.consumer.enabled not set to false when Kafka audit is unusedAffected Services
All 51+ services using shared modules are potentially affected. Priority services:
| Service | Status | Commit |
|---------|--------|--------|
| notification-service | Fixed | f212b4327, e8a2cc88b, 46ea65b9b |
| prior-auth-service | Fixed | 8c6918ea4 |
| All others | Apply checklist when issues arise | — |
Monitoring & Validation
Pre-Build Validation
Run entity-migration validation
./gradlew :modules:services:YOUR-SERVICE:test --tests "*EntityMigrationValidationTest"
Run comprehensive pre-Docker validation
./scripts/validate-before-docker-build.sh
Startup Health Check
docker compose up -d YOUR-SERVICE docker compose logs -f YOUR-SERVICE | head -100 curl http://localhost:PORT/actuator/health
References
f212b4327, e8a2cc88b, 46ea65b9b8c6918ea4Footer
ADR #: 011
Version: 1.0
Status: Active and Validated
Pattern: Explicit Opt-In for Shared Module Integration
_Decision Date: March 2026_
_Validated on: notification-service, prior-auth-service_