Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds documentation and polish #1557

Merged
merged 12 commits into from
Dec 5, 2024
Merged

Adds documentation and polish #1557

merged 12 commits into from
Dec 5, 2024

Conversation

rajadilipkolli
Copy link
Owner

@rajadilipkolli rajadilipkolli commented Dec 5, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new method to search for restaurants within a specified distance from given coordinates.
    • Added a service for processing Elasticsearch aggregations, enhancing data handling capabilities.
    • Implemented a global exception handler for managing duplicate restaurant entries.
  • Improvements

    • Enhanced the restaurant creation process to support reactive programming and improved request validation.
    • Added null handling in date conversion to prevent errors.
    • Refactored data loading to utilize asynchronous processing for improved efficiency.
  • Documentation

    • Improved API documentation with detailed parameter descriptions for the new search method.

@rajadilipkolli rajadilipkolli self-assigned this Dec 5, 2024
Copy link
Contributor

coderabbitai bot commented Dec 5, 2024

Warning

Rate limit exceeded

@rajadilipkolli has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 9 minutes and 39 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 6337a58 and b99c322.

Walkthrough

The pull request introduces several significant changes across multiple classes in the boot-mongodb-elasticsearch project. The DateUtility class has been enhanced with null handling in the convertToLocalDateViaInstant method. The RestaurantController has shifted to a reactive programming model with updated method signatures and validation. The SearchController added a new method for searching restaurants within a specified range, improving API documentation. A new AggregationProcessor class has been introduced for handling Elasticsearch aggregations, and the SearchService class has been updated to utilize this new processor. Additionally, a global exception handler for duplicate restaurant entries has been implemented.

Changes

File Path Change Summary
.../utils/DateUtility.java Updated convertToLocalDateViaInstant method to include null check, returning null if input is null.
.../web/controller/RestaurantController.java Changed createRestaurant method return type to Mono<ResponseEntity<GenericMessage>>, added @Valid annotation, and updated internal logic to utilize reactive programming.
.../web/controller/SearchController.java Added new method searchRestaurantsWithInRange with Swagger annotations for better API documentation; updated parameter annotations for clarity.
.../web/service/AggregationProcessor.java Introduced new class with method processAggregations for processing Elasticsearch aggregations based on a provided configuration map.
.../web/service/SearchService.java Updated constructor to include aggregationProcessor, modified aggregateSearch to call aggregationProcessor.processAggregations.
.../config/GlobalExceptionHandler.java Added new global exception handler for DuplicateRestaurantException with method to handle exceptions and return appropriate response.
.../web/exception/DuplicateRestaurantException.java Introduced new exception class for signaling duplicate restaurant entries.
.../web/service/RestaurantService.java Refactored loadData method for asynchronous JSON file reading, updated createRestaurant to throw DuplicateRestaurantException for duplicate names.

Possibly related PRs

Poem

🐰 In the code where rabbits hop,
New methods bloom and never stop.
With checks for null and docs so bright,
Our APIs now shine with delight!
From aggregations to restaurants near,
Code grows strong, let’s give a cheer! 🎉


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
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: 3

🧹 Outside diff range and nitpick comments (3)
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/SearchController.java (1)

150-154: API documentation could be enhanced further

While the current documentation is good, consider adding:

  • @ApiResponse annotations to document possible response scenarios
  • Example values for lat/lon parameters
  • Description of the ResultData structure in the response
 @Operation(
         summary = "Search restaurants within range",
-        description = "Find restaurants within specified distance from given coordinates")
+        description = "Find restaurants within specified distance from given coordinates",
+        responses = {
+            @ApiResponse(
+                responseCode = "200",
+                description = "Successfully retrieved restaurants",
+                content = @Content(schema = @Schema(implementation = ResultData.class))
+            ),
+            @ApiResponse(
+                responseCode = "400",
+                description = "Invalid parameters provided"
+            )
+        })
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/RestaurantController.java (1)

82-93: Consider improving the URI creation and error handling.

While the implementation is functionally correct, consider these improvements:

  1. The URI creation uses the restaurant name which might not be unique or URL-safe. Consider using the restaurant ID instead.
  2. The response could benefit from additional error handling for duplicate restaurants.

Here's a suggested improvement:

 return this.restaurantService
         .createRestaurant(restaurantRequest)
         .map(
                 restaurant ->
                         ResponseEntity.created(
-                                URI.create("/restaurant/" + restaurant.getName()))
+                                URI.create("/api/restaurant/" + restaurant.getRestaurantId()))
                                 .body(
                                         new GenericMessage(
                                                 "restaurant with name %s created"
                                                         .formatted(
                                                                 restaurantRequest
                                                                         .name()))));
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/AggregationProcessor.java (1)

11-12: Consider adding class-level documentation and reviewing visibility

The class is marked as package-private. If this is intended to be used only within the service package, consider documenting this design decision. Otherwise, consider making it public.

Add class-level Javadoc explaining the purpose and usage of this processor:

 @Service
+/**
+ * Processes Elasticsearch aggregations and transforms them into a structured map format.
+ * Supports 'terms' and 'dateRange' aggregation types.
+ */
 class AggregationProcessor {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce45a3 and 38841a0.

📒 Files selected for processing (5)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/utils/DateUtility.java (1 hunks)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/RestaurantController.java (1 hunks)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/SearchController.java (2 hunks)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/AggregationProcessor.java (1 hunks)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/SearchService.java (2 hunks)
🔇 Additional comments (6)
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/SearchController.java (1)

8-9: LGTM: Appropriate Swagger imports added

The addition of Swagger annotation imports enhances API documentation capabilities.

boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/RestaurantController.java (1)

80-81: LGTM! Good improvements in validation and reactive support.

The method signature changes look good with:

  • Proper reactive return type using Mono
  • Added request validation with @Valid
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/utils/DateUtility.java (2)

9-14: Well-documented null handling behavior!

The Javadoc clearly documents the method's behavior with null inputs, which is a great practice for API documentation.


16-18: Robust null handling implementation

Good defensive programming practice by adding an explicit null check at the start of the method.

boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/SearchService.java (2)

25-31: Clean dependency injection implementation

Good practice using constructor injection for the new AggregationProcessor dependency.


124-128: Improved separation of concerns

Good refactoring to move aggregation processing logic to a dedicated class, making the code more maintainable.

Let's verify that all aggregation processing has been moved to the AggregationProcessor:

✅ Verification successful

Let me verify the aggregation processing logic by checking the actual implementation in SearchService.java.


Let me try a different approach to verify the aggregation processing logic.


Let me check the complete implementation of AggregationProcessor to verify all aggregation processing has been moved there.


Aggregation processing successfully moved to dedicated class

The verification confirms that all aggregation processing logic has been properly moved to the AggregationProcessor class. The SearchService now only delegates the aggregation processing to the injected AggregationProcessor instance, while the AggregationProcessor handles both string terms and date range aggregations in a clean, focused implementation. This confirms the successful separation of concerns mentioned in the review comment.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining aggregation processing logic in SearchService

# Search for aggregation processing patterns
rg -i "bucket|aggregate.*map|sterms|daterange" --type java

Length of output: 14079


Script:

#!/bin/bash
# Check the SearchService implementation for any remaining aggregation processing
ast-grep --pattern 'class SearchService {
  $$$
  processAggregations($$$) {
    $$$
  }
  $$$
}'

Length of output: 99


Script:

#!/bin/bash
# Check the content of SearchService.java and AggregationProcessor.java
rg -A 10 "class SearchService" boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/SearchService.java
echo "---"
rg -A 10 "class AggregationProcessor" boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/AggregationProcessor.java

Length of output: 1277


Script:

#!/bin/bash
# Get the complete implementation of AggregationProcessor
cat boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/AggregationProcessor.java

Length of output: 2427

rajadilipkolli and others added 4 commits December 5, 2024 18:32
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
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: 2

🧹 Outside diff range and nitpick comments (6)
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/SearchController.java (1)

157-194: Consider adding rate limiting for geospatial queries

Geospatial queries can be resource-intensive. Consider adding rate limiting to prevent abuse.

Add the @RateLimiter annotation to protect the endpoint:

 @GetMapping("/search/restaurant/withInRange")
+@RateLimiter(name = "geospatialSearch", fallbackMethod = "geospatialSearchFallback")
 public Flux<ResultData> searchRestaurantsWithInRange(

You'll need to configure the rate limiter properties in your application configuration:

resilience4j.ratelimiter:
  instances:
    geospatialSearch:
      limitForPeriod: 10
      limitRefreshPeriod: 1s
      timeoutDuration: 500ms
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/AggregationProcessor.java (3)

9-12: Enhance class-level documentation

While the documentation is clear, consider adding examples of the expected input and output formats to make it more helpful for other developers.

Add something like:

 /**
  * Processes Elasticsearch aggregations and transforms them into a structured map format. Supports
  * 'terms' and 'dateRange' aggregation types.
+ *
+ * Example output format:
+ * {
+ *   "termAggregation": {"term1": 10, "term2": 20},
+ *   "dateRangeAggregation": {"2023-01-01 - 2023-12-31": 100}
+ * }
  */

24-38: Add additional validation and error handling

While the null check is good, consider adding more defensive programming:

  1. Check if the aggregation or its components are null inside the lambda
  2. Handle potential exceptions from the aggregation operations

Consider this enhancement:

     public Map<String, Map<String, Long>> processAggregations(
             Map<String, ElasticsearchAggregation> aggregationMap) {
         if (aggregationMap == null) {
             throw new IllegalArgumentException("aggregationMap must not be null");
         }
         Map<String, Map<String, Long>> resultMap = new HashMap<>();
         aggregationMap.forEach(
                 (String aggregateKey, ElasticsearchAggregation aggregation) -> {
+                    if (aggregation == null || aggregation.aggregation() == null) {
+                        return;  // Skip invalid entries
+                    }
                     Map<String, Long> countMap = new HashMap<>();
-                    Aggregate aggregate = aggregation.aggregation().getAggregate();
-                    processAggregate(aggregate, countMap);
+                    try {
+                        Aggregate aggregate = aggregation.aggregation().getAggregate();
+                        processAggregate(aggregate, countMap);
+                    } catch (Exception e) {
+                        // Log the error and continue processing other aggregations
+                        log.warn("Failed to process aggregation {}: {}", aggregateKey, e.getMessage());
+                    }
                     resultMap.put(aggregateKey, countMap);
                 });
         return resultMap;
     }

40-46: Handle unsupported aggregation types

The processAggregate method silently ignores unsupported aggregation types. Consider logging these cases for better observability.

Consider this enhancement:

     private void processAggregate(Aggregate aggregate, Map<String, Long> countMap) {
         if (aggregate.isSterms()) {
             processTermsAggregate(aggregate, countMap);
         } else if (aggregate.isDateRange()) {
             processDateRangeAggregate(aggregate, countMap);
+        } else {
+            log.debug("Unsupported aggregation type encountered: {}", 
+                     aggregate.getClass().getSimpleName());
         }
     }
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/RestaurantController.java (1)

91-96: Simplify the message formatting

The current implementation uses string formatting twice unnecessarily. Consider simplifying it.

-                        .body(
-                                new GenericMessage(
-                                        "restaurant with name %s created"
-                                                .formatted(
-                                                        restaurantRequest
-                                                                .name())));
+                        .body(new GenericMessage(
+                            String.format("restaurant with name %s created", 
+                                restaurantRequest.name())));
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/RestaurantService.java (1)

90-110: Consider simplifying the document-to-entity mapping using a library

Manually mapping fields from Document to Restaurant can be verbose and error-prone. Consider using a library like Spring Data MongoDB's MongoConverter or MapStruct to automate the mapping process.

Example using MongoConverter:

@Autowired
private MongoConverter mongoConverter;

private Restaurant documentToRestaurant(Document document) {
    return mongoConverter.read(Restaurant.class, document);
}

This approach reduces boilerplate code and ensures consistency in the mapping process.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 38841a0 and e011b6f.

📒 Files selected for processing (7)
  • .vscode/launch.json (1 hunks)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/config/GlobalExceptionHandler.java (1 hunks)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/RestaurantController.java (1 hunks)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/SearchController.java (2 hunks)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/exception/DuplicateRestaurantException.java (1 hunks)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/AggregationProcessor.java (1 hunks)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/RestaurantService.java (6 hunks)
🔇 Additional comments (10)
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/SearchController.java (3)

8-16: LGTM! Well-organized imports

The new imports are properly organized and necessary for API documentation and parameter validation.


157-166: LGTM! Well-documented API endpoint

The OpenAPI documentation is comprehensive with clear descriptions and response specifications.


169-193: Parameter validation implemented as suggested

The implementation includes all the suggested validations from the previous review.

.vscode/launch.json (1)

173-182: Consider aligning test configuration with production profile settings.

The new test configuration looks good, but consider adding the same profile setting as the production configuration (--spring.profiles.active=cluster) to ensure consistent testing environment, unless the test specifically requires different settings.

             "mainClass": "com.example.mongoes.TestMongoESApplication",
             "projectName": "boot-mongodb-elasticsearch",
-            "args": "",
+            "args": "--spring.profiles.active=cluster",
             "envFile": "${workspaceFolder}/.env"

Let's verify the existence of the test class:

boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/AggregationProcessor.java (1)

48-64: LGTM! Well-structured aggregation processing

The implementations for both terms and date range aggregations are clean, efficient, and handle their specific cases well. The filtering of zero counts in date range aggregations is a nice touch.

boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/controller/RestaurantController.java (1)

80-96: Verify global exception handling implementation

The controller relies on global exception handling for duplicate restaurants as discussed in previous reviews. Let's verify the implementation.

✅ Verification successful

Let me check the actual implementation of the exception handler.


Global exception handling for duplicate restaurants is properly implemented

The GlobalExceptionHandler is correctly implemented with:

  • @RestControllerAdvice annotation for global exception handling
  • Dedicated handler method for DuplicateRestaurantException
  • Appropriate HTTP 409 Conflict status code
  • Proper error message propagation through GenericMessage
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of global exception handler for duplicate restaurants

# Check for the global exception handler
echo "Checking for GlobalExceptionHandler..."
rg -l "DuplicateRestaurantException" --type java

# Check the actual implementation
echo "Checking handler implementation..."
ast-grep --pattern 'class $_ {
  $$$
  @ExceptionHandler(DuplicateRestaurantException.class)
  $_ handle$_($_) {
    $$$
  }
  $$$
}'

Length of output: 651


Script:

#!/bin/bash
# Check the implementation of GlobalExceptionHandler
echo "GlobalExceptionHandler implementation:"
rg -A 10 "DuplicateRestaurantException" boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/config/GlobalExceptionHandler.java

Length of output: 873

boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/RestaurantService.java (2)

162-162: Verify the necessity of using Schedulers.boundedElastic()

Adding .publishOn(Schedulers.boundedElastic()) shifts the processing to the bounded elastic scheduler. Ensure this is necessary because it may introduce unintended threading and performance implications. If the downstream operations are non-blocking and CPU-bound, it's preferable to continue on the default scheduler.

If blocking calls exist downstream, consider confining those specific calls to Schedulers.boundedElastic() using subscribeOn or publishOn at that point.


64-80: 🛠️ Refactor suggestion

Consider parsing the JSON file as an array instead of splitting strings

Splitting the file content by newline (\n) may not correctly parse the JSON data, especially if the JSON entries contain newlines or if the file uses different line endings. This might be causing only 8 instead of 32,786 rows to be inserted. Parsing the file content as a JSON array would be more robust.

Here's how you might refactor the loadData method to parse the JSON file as a JSON array:

 public Flux<Restaurant> loadData() {
     return DataBufferUtils.read(
             new ClassPathResource("restaurants.json"),
             new DefaultDataBufferFactory(),
             4096)
-            .map(
+            .reduce(
+                new StringBuilder(),
                 (stringBuilder, dataBuffer) -> {
                     byte[] bytes = new byte[dataBuffer.readableByteCount()];
                     dataBuffer.read(bytes);
                     DataBufferUtils.release(dataBuffer);
-                    return new String(bytes, StandardCharsets.UTF_8);
+                    return stringBuilder.append(new String(bytes, StandardCharsets.UTF_8));
                 })
+            )
-            .flatMap(
-                fileContent -> {
-                    List<String> restaurantArray = Arrays.asList(fileContent.split("\n"));
-                    return this.saveAll(restaurantArray);
-                });
+            .flatMapMany(
+                stringBuilder -> {
+                    String fileContent = stringBuilder.toString();
+                    ObjectMapper objectMapper = new ObjectMapper();
+                    List<Restaurant> restaurants = objectMapper.readValue(
+                        fileContent, new TypeReference<List<Restaurant>>() {});
+                    return restaurantRepository.saveAll(restaurants);
+                });
 }

Note: You'll need to import com.fasterxml.jackson.databind.ObjectMapper and com.fasterxml.jackson.core.type.TypeReference. Also, ensure that restaurants.json is formatted as a JSON array.

boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/exception/DuplicateRestaurantException.java (1)

1-8: Well-defined custom exception class

The DuplicateRestaurantException class is properly defined, extending RuntimeException and providing a constructor that accepts a message. This allows for clear and specific exception handling in the application.

boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/config/GlobalExceptionHandler.java (1)

13-17: Ensure consistent error response structure

The global exception handler correctly handles DuplicateRestaurantException and returns a ResponseEntity with status CONFLICT. Verify that GenericMessage provides sufficient information to the client. Consider adding standardized error codes or additional context if required.

Comment on lines +231 to +241
public Mono<Object> createRestaurant(RestaurantRequest restaurantRequest) {
return restaurantESRepository
.findByName(restaurantRequest.name())
.flatMap(
existingRestaurant ->
Mono.error(
new DuplicateRestaurantException(
"Restaurant with name "
+ restaurantRequest.name()
+ " already exists")))
.switchIfEmpty(restaurantRepository.save(restaurantRequest.toRestaurant()));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid using Mono<Object> as the return type; prefer a specific type

Changing the return type to Mono<Object> reduces type safety and clarity. It's better to return a specific type, such as Mono<Restaurant>, to maintain consistency and provide a clear contract to API consumers.

Here's how you might refactor the method:

-public Mono<Object> createRestaurant(RestaurantRequest restaurantRequest) {
+public Mono<Restaurant> createRestaurant(RestaurantRequest restaurantRequest) {
     return restaurantESRepository
             .findByName(restaurantRequest.name())
             .flatMap(
                     existingRestaurant ->
                         Mono.error(
                             new DuplicateRestaurantException(
                                 "Restaurant with name "
                                 + restaurantRequest.name()
                                 + " already exists")))
-            .switchIfEmpty(restaurantRepository.save(restaurantRequest.toRestaurant()));
+            .switchIfEmpty(
+                restaurantRepository.save(restaurantRequest.toRestaurant())
+                    .flatMap(savedRestaurant ->
+                        restaurantESRepository.save(savedRestaurant)
+                            .thenReturn(savedRestaurant)
+                    )
+            );
 }

This refactoring maintains type specificity and ensures that the newly created restaurant is saved in both MongoDB and Elasticsearch repositories within the same reactive flow.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public Mono<Object> createRestaurant(RestaurantRequest restaurantRequest) {
return restaurantESRepository
.findByName(restaurantRequest.name())
.flatMap(
existingRestaurant ->
Mono.error(
new DuplicateRestaurantException(
"Restaurant with name "
+ restaurantRequest.name()
+ " already exists")))
.switchIfEmpty(restaurantRepository.save(restaurantRequest.toRestaurant()));
public Mono<Restaurant> createRestaurant(RestaurantRequest restaurantRequest) {
return restaurantESRepository
.findByName(restaurantRequest.name())
.flatMap(
existingRestaurant ->
Mono.error(
new DuplicateRestaurantException(
"Restaurant with name "
+ restaurantRequest.name()
+ " already exists")))
.switchIfEmpty(
restaurantRepository.save(restaurantRequest.toRestaurant())
.flatMap(savedRestaurant ->
restaurantESRepository.save(savedRestaurant)
.thenReturn(savedRestaurant)
)
);

Copy link
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: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e011b6f and 6337a58.

📒 Files selected for processing (1)
  • boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/RestaurantService.java (6 hunks)
🔇 Additional comments (2)
boot-mongodb-elasticsearch/src/main/java/com/example/mongoes/web/service/RestaurantService.java (2)

163-163: LGTM! Good use of Schedulers.boundedElastic()

The addition of publishOn(Schedulers.boundedElastic()) is appropriate for handling potentially blocking operations in the change stream processor.


232-242: ⚠️ Potential issue

Return type should be specific and data should be synchronized.

The current implementation has two issues:

  1. Using Mono<Object> reduces type safety
  2. Restaurant is only saved in MongoDB, not in Elasticsearch

Apply this fix:

-public Mono<Object> createRestaurant(RestaurantRequest restaurantRequest) {
+public Mono<Restaurant> createRestaurant(RestaurantRequest restaurantRequest) {
     return restaurantESRepository
             .findByName(restaurantRequest.name())
             .flatMap(
                     existingRestaurant ->
                             Mono.error(
                                     new DuplicateRestaurantException(
                                             "Restaurant with name "
                                                     + restaurantRequest.name()
                                                     + " already exists")))
-            .switchIfEmpty(restaurantRepository.save(restaurantRequest.toRestaurant()));
+            .switchIfEmpty(
+                restaurantRepository.save(restaurantRequest.toRestaurant())
+                    .flatMap(savedRestaurant ->
+                        restaurantESRepository.save(savedRestaurant)
+                            .thenReturn(savedRestaurant)
+                    )
+            );
 }

Comment on lines +64 to +81
public Flux<Restaurant> loadData() {
return DataBufferUtils.join(
DataBufferUtils.read(
new ClassPathResource("restaurants.json"),
new DefaultDataBufferFactory(),
4096))
.map(
dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return new String(bytes, StandardCharsets.UTF_8);
})
.flatMapMany(
fileContent -> {
List<String> restaurantArray = Arrays.asList(fileContent.split("\n"));
return this.saveAll(restaurantArray);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for file reading operations.

While the reactive file reading implementation is correct, it should handle potential errors that could occur during file operations.

Consider adding error handling:

 public Flux<Restaurant> loadData() {
     return DataBufferUtils.join(
                     DataBufferUtils.read(
                             new ClassPathResource("restaurants.json"),
                             new DefaultDataBufferFactory(),
                             4096))
             .map(
                     dataBuffer -> {
                         byte[] bytes = new byte[dataBuffer.readableByteCount()];
                         dataBuffer.read(bytes);
                         DataBufferUtils.release(dataBuffer);
                         return new String(bytes, StandardCharsets.UTF_8);
                     })
             .flatMapMany(
                     fileContent -> {
                         List<String> restaurantArray = Arrays.asList(fileContent.split("\n"));
                         return this.saveAll(restaurantArray);
                     })
+            .onErrorResume(error -> {
+                log.error("Error loading restaurant data: {}", error.getMessage());
+                return Flux.error(new RuntimeException("Failed to load restaurant data", error));
+            })
+            .doFinally(signalType -> log.info("Completed loading restaurant data: {}", signalType));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public Flux<Restaurant> loadData() {
return DataBufferUtils.join(
DataBufferUtils.read(
new ClassPathResource("restaurants.json"),
new DefaultDataBufferFactory(),
4096))
.map(
dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return new String(bytes, StandardCharsets.UTF_8);
})
.flatMapMany(
fileContent -> {
List<String> restaurantArray = Arrays.asList(fileContent.split("\n"));
return this.saveAll(restaurantArray);
});
public Flux<Restaurant> loadData() {
return DataBufferUtils.join(
DataBufferUtils.read(
new ClassPathResource("restaurants.json"),
new DefaultDataBufferFactory(),
4096))
.map(
dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return new String(bytes, StandardCharsets.UTF_8);
})
.flatMapMany(
fileContent -> {
List<String> restaurantArray = Arrays.asList(fileContent.split("\n"));
return this.saveAll(restaurantArray);
})
.onErrorResume(error -> {
log.error("Error loading restaurant data: {}", error.getMessage());
return Flux.error(new RuntimeException("Failed to load restaurant data", error));
})
.doFinally(signalType -> log.info("Completed loading restaurant data: {}", signalType));
}

Comment on lines +91 to +111
private Restaurant documentToRestaurant(Document document) {
Restaurant restaurant = new Restaurant();
restaurant.setRestaurantId(Long.valueOf(document.get("restaurant_id", String.class)));
restaurant.setName(document.get("name", String.class));
restaurant.setCuisine(document.get("cuisine", String.class));
restaurant.setBorough(document.get("borough", String.class));

Address address = new Address();
Document addressDoc = document.get("address", Document.class);
address.setBuilding(addressDoc.get("building", String.class));
address.setStreet(addressDoc.get("street", String.class));
address.setZipcode(Integer.valueOf(addressDoc.get("zipcode", String.class)));
List<Double> coord = addressDoc.getList("coord", Double.class);
Point geoJsonPoint = new Point(coord.get(0), coord.get(1));
address.setLocation(geoJsonPoint);
restaurant.setAddress(address);

List<Grades> gradesList = getGradesList(document.getList("grades", Document.class));
restaurant.setGrades(gradesList);

return restaurant;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation and null checks in documentToRestaurant.

The method assumes all fields exist and are of the correct type, which could lead to NullPointerException or ClassCastException.

Consider adding validation:

 private Restaurant documentToRestaurant(Document document) {
+    if (document == null) {
+        throw new IllegalArgumentException("Document cannot be null");
+    }
     Restaurant restaurant = new Restaurant();
-    restaurant.setRestaurantId(Long.valueOf(document.get("restaurant_id", String.class)));
+    String restaurantId = document.get("restaurant_id", String.class);
+    if (restaurantId == null) {
+        throw new IllegalArgumentException("restaurant_id is required");
+    }
+    restaurant.setRestaurantId(Long.valueOf(restaurantId));
     restaurant.setName(document.get("name", String.class));
     restaurant.setCuisine(document.get("cuisine", String.class));
     restaurant.setBorough(document.get("borough", String.class));

     Address address = new Address();
     Document addressDoc = document.get("address", Document.class);
+    if (addressDoc == null) {
+        throw new IllegalArgumentException("address is required");
+    }
     address.setBuilding(addressDoc.get("building", String.class));
     address.setStreet(addressDoc.get("street", String.class));
     address.setZipcode(Integer.valueOf(addressDoc.get("zipcode", String.class)));
     List<Double> coord = addressDoc.getList("coord", Double.class);
+    if (coord == null || coord.size() != 2) {
+        throw new IllegalArgumentException("Invalid coordinates");
+    }
     Point geoJsonPoint = new Point(coord.get(0), coord.get(1));
     address.setLocation(geoJsonPoint);
     restaurant.setAddress(address);

     List<Grades> gradesList = getGradesList(document.getList("grades", Document.class));
     restaurant.setGrades(gradesList);

     return restaurant;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private Restaurant documentToRestaurant(Document document) {
Restaurant restaurant = new Restaurant();
restaurant.setRestaurantId(Long.valueOf(document.get("restaurant_id", String.class)));
restaurant.setName(document.get("name", String.class));
restaurant.setCuisine(document.get("cuisine", String.class));
restaurant.setBorough(document.get("borough", String.class));
Address address = new Address();
Document addressDoc = document.get("address", Document.class);
address.setBuilding(addressDoc.get("building", String.class));
address.setStreet(addressDoc.get("street", String.class));
address.setZipcode(Integer.valueOf(addressDoc.get("zipcode", String.class)));
List<Double> coord = addressDoc.getList("coord", Double.class);
Point geoJsonPoint = new Point(coord.get(0), coord.get(1));
address.setLocation(geoJsonPoint);
restaurant.setAddress(address);
List<Grades> gradesList = getGradesList(document.getList("grades", Document.class));
restaurant.setGrades(gradesList);
return restaurant;
private Restaurant documentToRestaurant(Document document) {
if (document == null) {
throw new IllegalArgumentException("Document cannot be null");
}
Restaurant restaurant = new Restaurant();
String restaurantId = document.get("restaurant_id", String.class);
if (restaurantId == null) {
throw new IllegalArgumentException("restaurant_id is required");
}
restaurant.setRestaurantId(Long.valueOf(restaurantId));
restaurant.setName(document.get("name", String.class));
restaurant.setCuisine(document.get("cuisine", String.class));
restaurant.setBorough(document.get("borough", String.class));
Address address = new Address();
Document addressDoc = document.get("address", Document.class);
if (addressDoc == null) {
throw new IllegalArgumentException("address is required");
}
address.setBuilding(addressDoc.get("building", String.class));
address.setStreet(addressDoc.get("street", String.class));
address.setZipcode(Integer.valueOf(addressDoc.get("zipcode", String.class)));
List<Double> coord = addressDoc.getList("coord", Double.class);
if (coord == null || coord.size() != 2) {
throw new IllegalArgumentException("Invalid coordinates");
}
Point geoJsonPoint = new Point(coord.get(0), coord.get(1));
address.setLocation(geoJsonPoint);
restaurant.setAddress(address);
List<Grades> gradesList = getGradesList(document.getList("grades", Document.class));
restaurant.setGrades(gradesList);
return restaurant;

Copy link

sonarqubecloud bot commented Dec 5, 2024

@rajadilipkolli rajadilipkolli merged commit e8245e9 into main Dec 5, 2024
7 checks passed
@rajadilipkolli rajadilipkolli deleted the mongoes branch December 5, 2024 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant