Jules feat: Migrate Hypernova client from PHP to Java 17 and Spring Boot - #3
Jules feat: Migrate Hypernova client from PHP to Java 17 and Spring Boot#3gitrey wants to merge 2 commits into
Conversation
This commit introduces a complete rewrite of the Hypernova PHP client into a Java 17 and Spring Boot (v3.3.0) application. The new Java client replicates the core functionality of the original PHP version, providing a means for your Java applications to interact with a Hypernova service for server-side rendering of JavaScript components.
Key features and changes:
- Core Rendering Logic: I've implemented `HypernovaRenderer.java` which handles batching of rendering jobs, communication with the Hypernova service via HTTP, and processing of responses.
- Data Structures: I created Java POJOs (`HypernovaJob`, `HypernovaJobResult`, `HypernovaResponse`) equivalent to the PHP data structures, with Jackson annotations for JSON serialization/deserialization.
- Plugin System:
- I defined `HypernovaPlugin.java` interface and `BaseHypernovaPlugin.java` abstract class to allow extensible behavior modification through lifecycle hooks (e.g., `getViewData`, `prepareRequest`, `shouldSendRequest`, `willSendRequest`, `onSuccess`, `onError`, `afterResponse`).
- I implemented `DevModePlugin.java` to provide enhanced error visibility in development environments, similar to its PHP counterpart.
- Configuration: The Hypernova service URL is configurable via Spring Boot properties (`hypernova.service.url`).
- Unit Tests: I added comprehensive unit tests using JUnit 5 and Mockito for `HypernovaRenderer`, `DevModePlugin`, and POJO serialization/deserialization, ensuring code quality and correctness. All tests pass.
- Documentation:
- `README.md`: Provides a comprehensive guide on how to use the library, including setup, configuration, basic usage, and plugin development.
- `DEPLOY_CLOUD_RUN.md`: Includes detailed instructions for packaging the Spring Boot application and deploying it to Google Cloud Run, including Dockerfile examples and `gcloud` commands.
- Refactoring and Finalization: I reviewed the codebase for consistency, error handling (including robust plugin interaction), and clarity. Javadoc comments have been added to public APIs.
The project is built with Maven and requires Java 17.
This migration enables your Java-based backends to leverage Hypernova for server-side rendering, aligning with modern Java ecosystems and providing a tested and documented client library.
There was a problem hiding this comment.
Hello @gitrey, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
Summary of Changes
Hello team,
gemini-code-assist here to provide a summary of this pull request. This PR represents a significant feature addition: a complete rewrite of the Hypernova client, migrating it from PHP to a modern Java 17 and Spring Boot application. The goal is to provide Java applications with a robust, pluggable, and well-documented client for server-side rendering via a Hypernova service.
The core functionality mirrors the original PHP client, focusing on batching rendering jobs, communicating with the Hypernova service over HTTP, and processing the results. A key aspect of this new client is its extensible plugin system, allowing developers to hook into various stages of the rendering lifecycle. Comprehensive unit tests have been added to ensure the correctness of the new implementation, and detailed documentation is included to guide users on setup, usage, and deployment, particularly to Google Cloud Run.
Highlights
- Language & Framework Migration: The entire Hypernova client has been rewritten from PHP to Java 17, leveraging the Spring Boot framework (v3.4.6) for dependency injection, configuration, and application structure.
- Core Rendering Logic: A new
HypernovaRendererclass handles the main client responsibilities: adding jobs, orchestrating plugin calls, sending batched requests to the Hypernova service usingRestTemplate, and processing the JSON response usingObjectMapper. - Plugin System: An extensible plugin architecture has been introduced with the
HypernovaPlugininterface andBaseHypernovaPluginabstract class. This allows custom logic to be injected at various lifecycle points (e.g.,getViewData,prepareRequest,shouldSendRequest,willSendRequest,onSuccess,onError,afterResponse). - Included Plugins: A
DevModePluginis included, which enhances error visibility during development by prepending formatted error messages and stack traces to the rendered HTML output when a job fails. - Configuration: The Hypernova service URL is configurable via Spring Boot properties (
hypernova.service.url), defaulting tohttp://localhost:3030/batch. - Documentation & Deployment Guide: The
README.mdhas been updated with detailed instructions for the Java client, and a newDEPLOY_CLOUD_RUN.mdprovides a step-by-step guide for deploying the client application to Google Cloud Run. - Testing: Comprehensive unit tests using JUnit 5 and Mockito have been added for the core
HypernovaRenderer, POJOs, and theDevModePlugin.
Changelog
Click here to see the changelog
- .gitattributes
- Added line ending rules for Maven wrapper scripts (
mvnw,*.cmd). (Lines 1-2)
- Added line ending rules for Maven wrapper scripts (
- .gitignore
- Updated ignore rules to exclude Java/Maven build artifacts (
target/) and common IDE files (.idea,.vscode, etc.). (Lines 1-33) - Removed PHP-specific ignore rules (
vendor/,coverage/). (Lines 1-3)
- Updated ignore rules to exclude Java/Maven build artifacts (
- .mvn/wrapper/maven-wrapper.properties
- Added Maven wrapper configuration, specifying Maven version 3.9.9 and wrapper version 3.3.2. (Lines 1-19)
- DEPLOY_CLOUD_RUN.md
- Added a new file containing a detailed guide for deploying the Spring Boot Hypernova client application to Google Cloud Run. (Lines 1-178)
- README.md
- Completely replaced the PHP client documentation with documentation for the new Java client. (Lines 1-265)
- Includes sections on overview, features, prerequisites, installation (Maven), configuration (Spring Beans, service URL), basic usage, plugin system, included plugins, error handling, contributing, and license.
- mvnw
- Added the Maven wrapper script for Unix-like systems. (Lines 1-259)
- mvnw.cmd
- Added the Maven wrapper script for Windows systems. (Lines 1-149)
- pom.xml
- Added the Maven project configuration file. (Lines 1-79)
- Configures project metadata, Java 17, Spring Boot 3.4.6 parent, and dependencies (web, lombok, test).
- Includes build plugins for compilation and Spring Boot packaging.
- src/main/java/com/example/demo/DemoApplication.java
- Added the main Spring Boot application class. (Lines 1-19)
- Includes
@SpringBootApplicationand defines aRestTemplatebean.
- src/main/java/com/example/demo/hypernova/HypernovaJob.java
- Added Java POJO representing a Hypernova rendering job. (Lines 1-36)
- Includes fields for
name,data, andmetadatawith Lombok annotations and Jackson's@JsonInclude.
- src/main/java/com/example/demo/hypernova/HypernovaJobResult.java
- Added Java POJO representing the result of a single Hypernova job. (Lines 1-49)
- Includes fields for
html,error,success,originalJob, andmetawith Lombok and Jackson's@JsonIgnoreProperties.
- src/main/java/com/example/demo/hypernova/HypernovaRenderer.java
- Added the core
HypernovaRendererservice class. (Lines 1-409) - Implements job batching, plugin lifecycle management, HTTP communication with Hypernova using
RestTemplate, JSON serialization/deserialization usingObjectMapper, and fallback HTML generation. - Handles top-level and per-job errors and invokes plugin
onErrormethods.
- Added the core
- src/main/java/com/example/demo/hypernova/HypernovaResponse.java
- Added Java POJO representing the overall response from a Hypernova batch request. (Lines 1-32)
- Includes fields for
results(map of job results) and a top-levelerrorobject with Lombok and Jackson's@JsonIgnoreProperties.
- src/main/java/com/example/demo/hypernova/plugin/BaseHypernovaPlugin.java
- Added an abstract base class for Hypernova plugins. (Lines 1-98)
- Provides default no-op or pass-through implementations for all
HypernovaPluginmethods.
- src/main/java/com/example/demo/hypernova/plugin/DevModePlugin.java
- Added a concrete
DevModePluginimplementation. (Lines 1-86) - Overrides
afterResponseto format and prepend error details to the HTML output for failed jobs in development mode.
- Added a concrete
- src/main/java/com/example/demo/hypernova/plugin/HypernovaPlugin.java
- Added the
HypernovaPlugininterface. (Lines 1-94) - Defines the lifecycle methods that plugins can implement to customize the rendering process.
- Added the
- src/main/resources/application.properties
- Added a basic Spring Boot application properties file. (Lines 1-1)
- src/test/java/com/example/demo/DemoApplicationTests.java
- Added a basic Spring Boot test class to verify context loading. (Lines 1-13)
- src/test/java/com/example/demo/hypernova/HypernovaPojoTest.java
- Added unit tests for the Hypernova POJO classes (
HypernovaJob,HypernovaJobResult,HypernovaResponse) covering serialization and deserialization. (Lines 1-139)
- Added unit tests for the Hypernova POJO classes (
- src/test/java/com/example/demo/hypernova/HypernovaRendererTest.java
- Added comprehensive unit tests for the
HypernovaRendererclass using Mockito, covering core functionality, error handling, fallback, and plugin interactions. (Lines 1-303)
- Added comprehensive unit tests for the
- src/test/java/com/example/demo/hypernova/plugin/DevModePluginTest.java
- Added unit tests for the
DevModePlugin, specifically testing its error formatting and HTML manipulation logic. (Lines 1-126)
- Added unit tests for the
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
From PHP's old way,
To Java's modern day,
SSR takes flight,
Components rendered right,
In Spring Boot's warm ray.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Code Review
This pull request represents a significant and well-executed migration of the Hypernova client from PHP to a modern Java 17 and Spring Boot application. The codebase is generally of high quality, demonstrating good use of Spring features, robust error handling, and a flexible plugin architecture. The accompanying documentation and unit tests are comprehensive and add great value.
My review focuses on a few areas in the HypernovaRenderer concerning the intricate logic of job tracking through plugin lifecycles, a documentation detail, and a configuration consistency check. Addressing these points should further enhance the client's robustness and clarity.
Summary of Findings
- Documentation Accuracy (README.md): The README.md suggests
DevModePluginis automatically registered if it's a Spring bean, which is not the current behavior. Registration is manual viaaddPlugin(). - Configuration Consistency (pom.xml): There's a discrepancy in the Spring Boot version between
pom.xml(3.4.6) and the PR description (3.3.0). - Job Tracking Robustness (HypernovaRenderer.java): The mechanism for restoring
_originalClientIdafterprepareRequestplugins could be fragile if job names are heavily modified by plugins. - Clarity of Error Context (HypernovaRenderer.java): The logic for determining the list of original jobs passed to
plugin.onErrorinhandlePluginOnErrorcould be complex to follow and might not always provide the most comprehensive context. - Minor: Dockerfile Path in Docs (DEPLOY_CLOUD_RUN.md): Path
/app/Dockerfilemight be slightly confusing; typicallyDockerfileis at project root. (Not commented due to severity settings) - Minor: Code Comment (BaseHypernovaPlugin.java): Comment
// Added import for HashMapis slightly unusual for production code. (Not commented due to severity settings) - Minor: HTML Escaping (DevModePlugin.java):
escapeHtmlmethod doesn't escape single quotes ('). While'is fine for HTML5, it's a minor point. (Not commented due to severity settings)
Merge Readiness
The pull request is very well done and close to being merge-ready. However, given the medium-severity items identified—particularly concerning documentation accuracy, configuration consistency, and the robustness of job tracking in HypernovaRenderer—I recommend addressing these points before merging. This will help ensure the client is as robust, clear, and easy to use as possible.
As a language model, I am not authorized to approve pull requests. Please ensure further review and approval from authorized team members before merging.
| * **`DevModePlugin`:** (Located in `com.example.demo.hypernova.plugin.DevModePlugin`) | ||
| * If a component fails to render and an error is present in the `HypernovaJobResult`, this plugin prepends a detailed error message (including component name, error message, and stack trace) wrapped in styled HTML to the original (often fallback) HTML. This is very useful during development to quickly identify issues with server-side rendering. | ||
| * It is automatically registered if it's a Spring bean in the application context and added to the `HypernovaRenderer` instance. |
There was a problem hiding this comment.
The documentation for DevModePlugin states: "It is automatically registered if it's a Spring bean in the application context and added to the HypernovaRenderer instance."
Currently, HypernovaRenderer does not automatically discover and register plugins that are Spring beans. Plugins, including DevModePlugin (even if it's a @Component), need to be explicitly added to the HypernovaRenderer instance via the addPlugin() method by the application developer (e.g., by autowiring the plugin and renderer in a configuration or service and then calling renderer.addPlugin(devModePlugin)).
Could we clarify this section to reflect that while DevModePlugin can be a Spring bean, its registration with the HypernovaRenderer is a manual step the developer needs to perform?
| <parent> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-parent</artifactId> | ||
| <version>3.4.6</version> |
There was a problem hiding this comment.
The Spring Boot parent version is specified as 3.4.6. However, the pull request description mentions using Spring Boot v3.3.0. Version 3.4.6 does not appear to be a standard released version as of now.
Could you confirm the intended Spring Boot version? If it's 3.3.0, updating the POM to match would ensure consistency and prevent potential issues with resolving a non-standard parent version. If 3.4.6 is a specific internal or pre-release version, this comment can be disregarded.
| for (HypernovaJob job : currentJobs) { | ||
| if (job.getName() != null && (job.getMetadata() == null || !job.getMetadata().containsKey("_originalClientId"))) { | ||
| String originalId = jobNameToOriginalClientIdMap.get(job.getName()); | ||
| if (originalId != null) { | ||
| if (job.getMetadata() == null) { | ||
| job.setMetadata(new HashMap<>()); | ||
| } | ||
| job.getMetadata().put("_originalClientId", originalId); | ||
| log.trace("Restored _originalClientId for job '{}' to '{}' after prepareRequest calls.", job.getName(), originalId); | ||
| } else { | ||
| log.warn("Could not restore _originalClientId for job '{}' after prepareRequest calls, as it was not found in the initial mapping (original name might have changed or job is new).", job.getName()); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The logic to restore _originalClientId after the prepareRequest plugin loop (lines 139-152) relies on a mapping between job names (post-getViewData) and their original client IDs. This could be fragile if a prepareRequest plugin significantly alters job names or introduces new jobs without an _originalClientId in their metadata.
For instance:
- If a plugin renames a job,
jobNameToOriginalClientIdMap.get(job.getName())would fail to find the ID. - If a plugin adds a new job, it won't be in
jobNameToOriginalClientIdMap.
The current logging (line 149) acknowledges this. However, to improve robustness, could we consider alternatives or enhancements?
One thought: Could plugins that modify job names or add jobs in prepareRequest be responsible for ensuring _originalClientId (or a similar tracking mechanism) is correctly propagated or initialized for the jobs they output? This would distribute the responsibility and make the core renderer's tracking less dependent on name stability through prepareRequest.
| List<HypernovaJob> finalOriginalJobsForPlugin = Collections.unmodifiableList(originalJobsForPluginError.isEmpty() && processedJobsContext != null ? | ||
| new ArrayList<>(processedJobsContext) : | ||
| originalJobsForPluginError); |
There was a problem hiding this comment.
The logic to determine finalOriginalJobsForPlugin in handlePluginOnError is a bit complex: Collections.unmodifiableList(originalJobsForPluginError.isEmpty() && processedJobsContext != null ? new ArrayList<>(processedJobsContext) : originalJobsForPluginError);
This means if originalJobsForPluginError (derived from jobResults and processedJobsContext metadata) is empty, but processedJobsContext (jobs passed to willSendRequest or handleFallback) is available, it uses processedJobsContext. Otherwise, it uses originalJobsForPluginError.
This might be correct, but it could be clarified. For example, if originalJobsForPluginError has even one entry, processedJobsContext is effectively ignored for constructing finalOriginalJobsForPlugin, even if processedJobsContext contains other relevant original jobs not captured by the jobResults analysis.
Could we refine this to ensure the most comprehensive set of original jobs relevant to the error context is passed to plugin.onError? Perhaps by merging distinct original jobs derived from both jobResults (if available) and processedJobsContext? Or, if the current logic is intentional, a comment explaining the priority would be helpful.
This commit introduces a new Java 17 client library for Hypernova. The library is a rewrite of the features found in the original PHP client. Key features include: - Core data structures (Job, JobResult, HypernovaResponse, HypernovaError) - HypernovaRenderer for managing rendering batches and HTTP communication - Plugin system (Plugin interface, BasePlugin) for extensibility - DevModePlugin for enhanced error reporting during development - Uses OkHttp for HTTP client and Jackson for JSON processing Comprehensive unit tests have been added for all components using JUnit 5 and Mockito, ensuring functionality and robustness. Documentation includes: - Javadoc comments for all public classes and methods. - A detailed README.md file covering installation, usage, plugin development, configuration, and contribution guidelines. The project has been set up using Maven with Java 17 as the target. A self-review of the codebase has been conducted, ensuring clarity, adherence to best practices, and proper error handling.
|
/gemini walkthrough |
|
Alright team, let's walk through this repository. Historically, this repo has housed a PHP client for interacting with a Hypernova server, which is used for Server-Side Rendering (SSR) of JavaScript components, particularly React applications. The core idea is to offload the initial rendering of your UI components from your backend application (PHP in the original case, now Java) to a dedicated Node.js service (Hypernova), improving performance and SEO. This pull request introduces a significant change: a complete rewrite of the Hypernova client in Java 17, leveraging standard libraries like OkHttp for HTTP communication and Jackson for JSON handling. While the PHP code remains in the repository for now, the focus of this PR, and thus this walkthrough, is the new Java client located under Let's break down the key components and the flow: 1. Core ComponentsThe Java client is structured around a few main classes:
2. How to Use the Java ClientThe basic usage is straightforward, as shown in the updated
3. Rendering Flow (
|
There was a problem hiding this comment.
/gemini why its important to use latest versions?
There was a problem hiding this comment.
That's a great question, gitrey!
Using recent versions of dependencies like OkHttp and Jackson Databind, as seen in this pom.xml, is generally important for several key reasons:
- Security: Newer versions often include patches for security vulnerabilities found in older versions. Using outdated libraries can expose your application to known exploits.
- Bug Fixes: Developers of these libraries continuously fix bugs. Updating means you benefit from these fixes, leading to more stable and reliable code.
- New Features and Improvements: Latest versions introduce new functionalities, performance optimizations, and support for newer language features or standards.
- Compatibility: Staying current helps ensure compatibility with other libraries and frameworks you might use, as they often target recent versions of common dependencies.
- Maintainability: It's easier to find documentation and get support for recent versions. Falling too far behind can make it difficult to upgrade later due to accumulated breaking changes.
While using the absolute latest version might sometimes introduce unexpected issues (which is why testing is crucial after updates), staying reasonably up-to-date is a best practice to leverage these benefits and mitigate risks. Tools like Renovate (which is configured in this repository) are specifically designed to help automate and manage this process of keeping dependencies current.
Does that explanation help clarify why keeping dependencies updated is important?

This commit introduces a complete rewrite of the Hypernova PHP client into a Java 17 and Spring Boot (v3.3.0) application. The new Java client replicates the core functionality of the original PHP version, providing a means for your Java applications to interact with a Hypernova service for server-side rendering of JavaScript components.
Key features and changes:
HypernovaRenderer.javawhich handles batching of rendering jobs, communication with the Hypernova service via HTTP, and processing of responses.HypernovaJob,HypernovaJobResult,HypernovaResponse) equivalent to the PHP data structures, with Jackson annotations for JSON serialization/deserialization.HypernovaPlugin.javainterface andBaseHypernovaPlugin.javaabstract class to allow extensible behavior modification through lifecycle hooks (e.g.,getViewData,prepareRequest,shouldSendRequest,willSendRequest,onSuccess,onError,afterResponse).DevModePlugin.javato provide enhanced error visibility in development environments, similar to its PHP counterpart.hypernova.service.url).HypernovaRenderer,DevModePlugin, and POJO serialization/deserialization, ensuring code quality and correctness. All tests pass.README.md: Provides a comprehensive guide on how to use the library, including setup, configuration, basic usage, and plugin development.DEPLOY_CLOUD_RUN.md: Includes detailed instructions for packaging the Spring Boot application and deploying it to Google Cloud Run, including Dockerfile examples andgcloudcommands.The project is built with Maven and requires Java 17. This migration enables your Java-based backends to leverage Hypernova for server-side rendering, aligning with modern Java ecosystems and providing a tested and documented client library.