Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions reactive/src/main/java/com/example/reactive/BlogController.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,76 @@
public class BlogController {
private final BlogService blogService;

/****
* Constructs a BlogController with the specified BlogService.
*
* @param blogService the service used to handle blog operations
*/
public BlogController(BlogService blogService) {
this.blogService = blogService;
}

/**
* Creates a new blog entry.
*
* @param blog the blog data to create
* @return a Mono emitting the created blog
*/
@PostMapping
public Mono<Blog> createBlog(@RequestBody Blog blog) {
return blogService.createBlog(blog);
}

/**
* Retrieves all blog entries as a reactive stream.
*
* @return a Flux emitting all Blog entities
*/
@GetMapping
public Flux<Blog> getAllBlogs() {
return blogService.getAllBlogs();
}

/**
* Retrieves a blog by its unique identifier.
*
* @param id the unique identifier of the blog to retrieve
* @return a Mono emitting the blog with the specified ID, or empty if not found
*/
@GetMapping("/{id}")
public Mono<Blog> getBlogById(@PathVariable String id) {
return blogService.getBlogById(id);
}

/**
* Retrieves all blogs authored by the specified author as a reactive stream.
*
* @param author the name of the author whose blogs are to be retrieved
* @return a Flux emitting blogs written by the given author
*/
@GetMapping("/author/{author}")
public Flux<Blog> getBlogsByAuthor(@PathVariable String author) {
return blogService.getBlogsByAuthor(author);
}

/**
* Updates an existing blog with the specified ID using the provided blog data.
*
* @param id the unique identifier of the blog to update
* @param blog the new blog data to apply
* @return a Mono emitting the updated blog
*/
@PutMapping("/{id}")
public Mono<Blog> updateBlog(@PathVariable String id, @RequestBody Blog blog) {
return blogService.updateBlog(id, blog);
}

/**
* Deletes a blog with the specified ID.
*
* @param id the unique identifier of the blog to delete
* @return a Mono signaling completion when the blog is deleted
*/
@DeleteMapping("/{id}")
public Mono<Void> deleteBlog(@PathVariable String id) {
return blogService.deleteBlog(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@
import reactor.core.publisher.Flux;

public interface BlogRepository extends ReactiveMongoRepository<Blog, String> {
Flux<Blog> findByAuthor(String author);
/****
* Returns a reactive stream of blog entries authored by the specified user.
*
* @param author the author's name to filter blog entries by
* @return a Flux emitting all Blog entities with the given author
*/
Flux<Blog> findByAuthor(String author);
}
41 changes: 41 additions & 0 deletions reactive/src/main/java/com/example/reactive/BlogService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,61 @@
public class BlogService {
private final BlogRepository blogRepository;

/****
* Constructs a BlogService with the specified BlogRepository for data access.
*
* @param blogRepository the repository used for blog persistence operations
*/
public BlogService(BlogRepository blogRepository) {
this.blogRepository = blogRepository;
}

/**
* Creates a new blog entry and saves it to the repository.
*
* @param blog the blog entity to be created
* @return a Mono emitting the saved blog entity
*/
public Mono<Blog> createBlog(Blog blog) {
return blogRepository.save(blog);
}

/**
* Retrieves all blog entries as a reactive stream.
*
* @return a Flux emitting all Blog entities
*/
public Flux<Blog> getAllBlogs() {
return blogRepository.findAll();
}

/**
* Retrieves a blog entry by its unique identifier.
*
* @param id the unique identifier of the blog
* @return a Mono emitting the blog with the specified ID, or empty if not found
*/
public Mono<Blog> getBlogById(String id) {
return blogRepository.findById(id);
}

/**
* Retrieves all blogs authored by the specified author.
*
* @param author the name of the author whose blogs are to be retrieved
* @return a Flux emitting all Blog entries written by the given author
*/
public Flux<Blog> getBlogsByAuthor(String author) {
return blogRepository.findByAuthor(author);
}

/**
* Updates an existing blog with new title, content, and author information.
*
* @param id the unique identifier of the blog to update
* @param blog the blog object containing updated fields
* @return a Mono emitting the updated Blog, or empty if the blog does not exist
*/
public Mono<Blog> updateBlog(String id, Blog blog) {
return blogRepository.findById(id)
.flatMap(existingBlog -> {
Expand All @@ -38,6 +73,12 @@ public Mono<Blog> updateBlog(String id, Blog blog) {
});
}

/**
* Deletes the blog entry with the specified ID.
*
* @param id the unique identifier of the blog to delete
* @return a Mono signaling completion when the blog is deleted
*/
public Mono<Void> deleteBlog(String id) {
return blogRepository.deleteById(id);
}
Expand Down
58 changes: 17 additions & 41 deletions reactive/src/main/java/com/example/reactive/EpisodeController.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,47 +29,15 @@ public class EpisodeController {
private final Tracer tracer; // from micrometer
private final Logger logger = LoggerFactory.getLogger(EpisodeController.class);

/* @GetMapping
public Mono<ResponseEntity<List<CustomEpisode>>> getEpisodes() {
// Manually create and start a span
Span newSpan = tracer.nextSpan().name("fetch-episodes").start();
try (Tracer.SpanInScope ws = tracer.withSpan(newSpan)) {

return webClient.get()
.uri("https://apissa.sampleapis.com/futurama/episodexxs") // Intentionally faulty for error testing
.retrieve()
.bodyToFlux(EpisodeResponse.class)
.map(ep -> new CustomEpisode(ep.title, ep.writers, ep.originalAirDate, ep.desc, ep.id))
.collectList()
.map(ResponseEntity::ok)
.doOnSuccess(res -> {
newSpan.tag("episodes.success", "true");
newSpan.event("Successfully fetched episodes");
newSpan.end();
})
.doOnError(e -> {
newSpan.tag("episodes.success", "false");
newSpan.error(e);
newSpan.end();
})
.onErrorResume(e -> {
String traceId = newSpan.context().traceId();

// logger.error("Error fetching episodes. Trace ID: {}", traceId, e);

CustomEpisode errorEpisode = new CustomEpisode(
"Error occurred", "", "", "Trace ID: " + traceId + ", error: " + e.getMessage(), -1
);
return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(Collections.singletonList(errorEpisode)));
});

} catch (Exception e) {
newSpan.error(e);
newSpan.end();
throw e;
}
}*/
/**
* Handles HTTP GET requests to retrieve a list of Futurama episodes from an external API, propagating the current trace ID for distributed tracing.
*
* If the external API call succeeds, returns a list of episodes wrapped in a 200 OK response. If an error occurs, returns a 500 response containing a single episode entry describing the error and including the trace ID for troubleshooting.
*
* The trace ID is propagated via Reactor's context and included in error responses and logs for observability.
*
* @return a reactive Mono emitting a ResponseEntity containing either the list of episodes or an error description with trace information
*/

@GetMapping
public Mono<ResponseEntity<List<CustomEpisode>>> getEpisodes() {
Expand Down Expand Up @@ -105,6 +73,14 @@ public Mono<ResponseEntity<List<CustomEpisode>>> getEpisodes() {
}


/**
* Performs a GET request to an external API and returns a response containing the current trace ID.
*
* The trace ID from the current span is propagated through Reactor's context and included in the response.
* On error, returns a 500 response with the error message and trace ID.
*
* @return a Mono emitting a ResponseEntity with a success or error message including the trace ID
*/
public Mono<ResponseEntity<String>> callApiWithTrace(WebClient webClient, Tracer tracer) {
String traceId = tracer.currentSpan() != null
? tracer.currentSpan().context().traceId()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
@SpringBootApplication
public class ReactiveApplication {

/**
* Launches the Spring Boot application.
*
* @param args command-line arguments passed to the application
*/
public static void main(String[] args) {
SpringApplication.run(ReactiveApplication.class, args);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ public class CustomEpisode {
public String description;
public int id;

/**
* Constructs a CustomEpisode with the specified title, writers, publish date, description, and ID.
*
* @param title the episode's title
* @param writers the episode's writers
* @param publishDate the episode's publish date
* @param description a brief description of the episode
* @param id the unique identifier for the episode
*/
public CustomEpisode(String title, String writers, String publishDate, String description, int id) {
this.title = title;
this.writers = writers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
@Configuration
public class TracingConfig {

/**
* Creates and configures a Tracer bean for distributed tracing using OpenTelemetry with span data exported to logs.
*
* @return a Tracer instance set up with a logging span exporter and current trace context management
*/
@Bean
public Tracer tracer() {
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
@Configuration
public class WebClientConfig {

/**
* Provides a configured {@link WebClient} bean for use in the application.
*
* @return a built {@link WebClient} instance
*/
@Bean
public WebClient webClient(WebClient.Builder builder) {
return builder.build();
Expand Down