Skip to content

refactor: move globalInterceptor to beanFactory#2510

Merged
predic8 merged 4 commits into
masterfrom
router-refactor-to-beanfactory
Dec 24, 2025
Merged

refactor: move globalInterceptor to beanFactory#2510
predic8 merged 4 commits into
masterfrom
router-refactor-to-beanfactory

Conversation

@predic8
Copy link
Copy Markdown
Member

@predic8 predic8 commented Dec 24, 2025

Summary by CodeRabbit

  • Refactor

    • Enhanced bean registry with single-bean lookup and explicit registration to improve component wiring.
    • Global interceptor now integrated via the registry rather than direct field access.
    • Updated metadata for the global interceptor element.
  • Tests

    • Added unit tests covering the bean registry behavior and updated tests to use the registry.
    • Introduced a test-scoped mocking dependency to support new tests.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 24, 2025

📝 Walkthrough

Walkthrough

Adds bean-based lookup and registration APIs to the BeanRegistry, updates implementations and tests, and switches Router/Transport to obtain GlobalInterceptor via the registry instead of a direct Router field.

Changes

Cohort / File(s) Summary
Build
annot/pom.xml
Added test-scoped dependency org.mockito:mockito-core; minor XML formatting tweaks.
Bean Registry API & Impl
annot/src/main/java/.../BeanRegistry.java, annot/src/main/java/.../BeanRegistryImplementation.java, annot/src/test/java/.../BeanRegistryImplementationTest.java
Added <T> Optional<T> getBean(Class<T>) and void register(String, Object) to the API; implemented single-bean lookup (throws on duplicates) and name-based registration storing UUID-backed BeanContainer; added tests for registration and getBean.
Router
core/src/main/java/com/predic8/membrane/core/Router.java
Removed globalInterceptor field and its getter; setGlobalInterceptor() now delegates registration via the registry.
Transport
core/src/main/java/com/predic8/membrane/core/transport/Transport.java
Replaced direct router.getGlobalInterceptor() usage with router.getRegistry().getBean(GlobalInterceptor.class).ifPresent(...) during init.
Tests / Test Stubs
core/src/test/java/.../GenericYamlParserTest.java, core/src/test/java/.../HttpTransportTest.java
Added registry method stubs (getBean, register) in TestRegistry; updated HttpTransportTest to mock router.getRegistry() and use a BeanRegistryImplementation instance.
YAML parser & Annotations
annot/src/main/java/.../GenericYamlParser.java, core/src/main/java/.../GlobalInterceptor.java
Minor exception message formatting change; @MCElement on GlobalInterceptor updated with component = false, topLevel = true (metadata change).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Transport
  participant Router
  participant Registry
  participant GlobalInterceptor as GI

  rect rgb(230, 245, 255)
    note over Transport,Router: Transport.init() (new flow)
  end

  Transport->>Router: getRegistry()
  Router->>Registry: return BeanRegistry
  Transport->>Registry: getBean(GlobalInterceptor.class)
  alt GlobalInterceptor present
    Registry-->>Transport: Optional.of(GI)
    Transport->>Transport: add GI to interceptors list
  else not present
    Registry-->>Transport: Optional.empty()
    Transport->>Transport: proceed without global interceptor
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • christiangoerdes
  • rrayst

Poem

🐰 I hopped through code and found a nest of beans,
Where globals now hide in registry greens.
I registered one, I fetched with care,
Optional treats and UUID flair.
Hooray — the garden’s growing clean! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main refactoring objective: moving globalInterceptor management from direct Router field storage to bean factory registration, which is the primary architectural change across the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch router-refactor-to-beanfactory

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@predic8 predic8 marked this pull request as ready for review December 24, 2025 11:49
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
core/src/test/java/com/predic8/membrane/core/transport/http/HttpTransportTest.java (1)

50-51: Test setup correctly adapted to new registry pattern.

The comment "Do not inline!" is appropriate since Mockito needs a real object reference to set up the stub. However, passing null for first and third constructor parameters may be fragile if BeanRegistryImplementation evolves to use those parameters.

Consider adding a test-specific factory method or builder for creating test BeanRegistryImplementation instances to reduce coupling to constructor signature.

annot/src/test/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementationTest.java (1)

35-53: Add test coverage for edge cases.

The existing tests cover happy paths, but consider adding tests for:

  1. getBean() with zero matching beans: Should return Optional.empty().
  2. getBean() with multiple matching beans: Should throw RuntimeException per the implementation (lines 179-183 in BeanRegistryImplementation.java).
🔎 Proposed additional tests
@Test
void getBeanReturnsEmptyWhenNoBeans() {
    Optional<A> result = registry.getBean(A.class);
    assertTrue(result.isEmpty());
}

@Test
void getBeanThrowsWhenMultipleBeans() {
    registry.register("bean1", new A("a1"));
    registry.register("bean2", new A("a2"));
    assertThrows(RuntimeException.class, () -> registry.getBean(A.class));
}
annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementation.java (1)

177-185: Improve error handling and logging level for multiple beans.

  1. Line 181: Using log.debug() before throwing an exception is unusual. Consider log.error() since this represents a configuration error.
  2. Lines 179-183: The error message could include which beans were found to aid debugging.
🔎 Proposed improvements
 public <T> Optional<T> getBean(Class<T> clazz) {
     var beans = getBeans(clazz);
     if (beans.size() > 1) {
-        var msg = "One bean was asked. But found %d beans of %s".formatted(beans.size(),clazz);
-        log.debug(msg);
+        var msg = "Expected single bean of type %s but found %d: %s".formatted(
+            clazz.getName(), beans.size(), beans);
+        log.error(msg);
         throw new RuntimeException(msg);
     }
     return beans.size() == 1 ? Optional.of(beans.getFirst()) : Optional.empty();
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c2fa18 and 1e2bede.

📒 Files selected for processing (8)
  • annot/pom.xml
  • annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistry.java
  • annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementation.java
  • annot/src/test/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementationTest.java
  • core/src/main/java/com/predic8/membrane/core/Router.java
  • core/src/main/java/com/predic8/membrane/core/transport/Transport.java
  • core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java
  • core/src/test/java/com/predic8/membrane/core/transport/http/HttpTransportTest.java
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-12-19T09:01:40.311Z
Learnt from: rrayst
Repo: membrane/api-gateway PR: 2441
File: annot/pom.xml:0-0
Timestamp: 2025-12-19T09:01:40.311Z
Learning: In annot/pom.xml, ensure log4j dependencies log4j-slf4j2-impl and log4j-core have <scope>test</scope>. In the distribution module (separate pom.xml), these dependencies should use the normal compile-scope. This guidance applies to all pom.xml files under the annot and distribution module structure where these dependencies appear; verify both modules are configured accordingly to reflect testing vs. runtime usage.

Applied to files:

  • annot/pom.xml
📚 Learning: 2025-09-22T19:49:29.473Z
Learnt from: predic8
Repo: membrane/api-gateway PR: 2156
File: pom.xml:0-0
Timestamp: 2025-09-22T19:49:29.473Z
Learning: In the Membrane API Gateway project, jackson-annotations is pinned to version "2.20" instead of using ${jackson.version} (2.20.0) because version 2.20.0 is not available in Maven repository yet for jackson-annotations, while it is available for jackson-core and jackson-databind. This version inconsistency is intentional and necessary due to different release schedules between Jackson modules.

Applied to files:

  • annot/pom.xml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (java)
🔇 Additional comments (2)
core/src/test/java/com/predic8/membrane/core/kubernetes/GenericYamlParserTest.java (1)

370-376: LGTM! Test registry stubs implement new BeanRegistry API.

The stub implementations returning Optional.empty() and no-op for register are appropriate for this test context, where bean resolution is handled via the refs map.

annot/pom.xml (1)

92-96: Mockito version is properly managed in the parent POM.

The parent POM (./pom.xml) defines mockito-core version 4.11.0 in dependencyManagement, and the annot/pom.xml module correctly omits the version to rely on this parent configuration. Mockito 4.11.0 is compatible with the JUnit 5.10.0 version specified in the parent POM.

Comment thread annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistry.java Outdated
Comment thread core/src/main/java/com/predic8/membrane/core/Router.java
@membrane-ci-server
Copy link
Copy Markdown

This pull request needs "/ok-to-test" from an authorized committer.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistry.java (1)

30-46: JavaDoc successfully added as requested.

The documentation now clearly describes both methods' behavior, including exception conditions and nullability constraints. This addresses the previous review feedback.

Optional enhancements you could consider:

  • Explicitly document thread-safety guarantees (or lack thereof) for both methods
  • Clarify behavior in register when beanName already exists (current implementation generates unique UUIDs, allowing duplicate names)
annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementation.java (1)

187-193: Critical bug from previous review has been fixed.

Line 191 now correctly stores the bean object (bean) rather than the BeanContainer (bc), ensuring getBeans(Class<T>) filtering operates on actual bean instances as intended.

Optional enhancement: Consider adding null validation for the bean parameter to enforce the "must not be null" contract documented in the interface JavaDoc.

🔎 Optional null validation
 public void register(String beanName, Object bean) {
+    Objects.requireNonNull(bean, "bean must not be null");
     var uuid = UUID.randomUUID().toString();
     BeanContainer bc = new BeanContainer(new BeanDefinition("component", beanName,null, uuid, null));
     bc.setSingleton(bean);
     singletonBeans.put(uuid,bean);
     bcs.put(uuid, bc);
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e2bede and e76ce7f.

📒 Files selected for processing (5)
  • annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistry.java
  • annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementation.java
  • annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java
  • core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java
  • core/src/main/java/com/predic8/membrane/core/interceptor/GlobalInterceptor.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (java)
🔇 Additional comments (4)
annot/src/main/java/com/predic8/membrane/annot/yaml/GenericYamlParser.java (1)

108-108: LGTM! Clean refactoring improves readability.

The change from string concatenation to String.formatted() makes the error message construction more concise and maintainable, while preserving identical functionality. This is consistent with the similar pattern used at lines 73-76.

annot/src/main/java/com/predic8/membrane/annot/beanregistry/BeanRegistryImplementation.java (1)

177-185: LGTM!

The implementation correctly delegates to getBeans(clazz), enforces single-bean semantics with a clear error message, and returns the appropriate Optional value. The logic aligns with the interface contract.

core/src/main/java/com/predic8/membrane/core/interceptor/GlobalInterceptor.java (1)

25-25: Annotation correctly configured for registry-based bean lookup.

The addition of component = false and topLevel = true appropriately supports the refactoring to move GlobalInterceptor from direct field storage in Router to registry-based bean lookup. Transport retrieves the interceptor via router.getRegistry().getBean(GlobalInterceptor.class) while Router's setGlobalInterceptor() method registers it in the registry, eliminating direct field dependencies.

core/src/main/java/com/predic8/membrane/core/cli/RouterCLI.java (1)

176-176: No changes needed. The inconsistency between Router and HttpRouter is intentional by design.

The change from HttpRouter to Router at line 176 in the YAML initialization path is correct. Router has all required methods: setBaseLocation(), getResolverMap(), applyConfiguration(), and setAsynchronousInitialization().

The difference in initialization paths is deliberate: HttpRouter provides pre-configured HTTP transport and interceptors for OpenAPI spec handling, while the YAML path uses the generic Router class to allow YAML configuration to define all transport and interceptor behavior. This is a valid design pattern, not an incomplete refactor.

Likely an incorrect or invalid review comment.

@predic8 predic8 merged commit 4160c3b into master Dec 24, 2025
2 of 3 checks passed
@predic8 predic8 deleted the router-refactor-to-beanfactory branch December 24, 2025 18:09
@coderabbitai coderabbitai Bot mentioned this pull request Jan 13, 2026
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.

3 participants