feat: add easy-paging-reactive-demo (WebFlux + R2DBC against real PG)#7
Merged
Conversation
Fourth easy-paging demo, completing the original starter coverage plan. Showcases the reactive companion artifact (kr.devslab:easy-paging-spring-boot-starter-reactive:0.4.0) — WebFlux + Spring Data R2DBC against real PostgreSQL via Testcontainers — so the same JSON pagination envelope as the MVC/MyBatis demos is served as Mono<PageResponse<T>> over a non-blocking stack. Reuses the Docker pattern established in easy-paging-postgres-demo: docker-compose.yml for `bootRun`, Testcontainers + @Serviceconnection for `./gradlew test`. The compose file binds host port 5433 (not 5432) so the reactive and postgres demos can run side by side. Architectural difference worth knowing about (also flagged in the demo's README): the MVC demos use the @AutoPaginate aspect, which is MyBatis/PageHelper-specific. On the reactive path there's no per-thread context to hook into, so the reactive starter instead exposes an explicit helper (R2dbcOffsetPagingSupport.paginate) the service calls directly. Wire-level contract is unchanged. Demo content: - Article entity (id, title, author, publishedAt, viewCount) — newsfeed use case, distinct from the postgres demo's Product so readers can see the entity mapping for a different domain. - 500 rows seeded via generate_series, deterministically across 5 authors (100 each), so the integration test can assert exact totals. - Service is a one-liner around R2dbcOffsetPagingSupport.paginate, with an optional Criteria.where("author") filter to mirror the postgres demo's ?category=. - Spring Data Relational entity mapping: @table on the class, @column only on fields where camelCase ≠ snake_case (publishedAt, viewCount). The integration test (ArticleControllerIT) covers: - envelope metadata on unfiltered listing, - ?author= filter narrowing totalElements from 500 to 100, - sort=id,asc returning Article #1 as content[0], - last page reporting last=true correctly. Uses WebTestClient (WebFlux) instead of MockMvc (servlet) and asserts via JSON paths — same shape as the postgres demo's tests with the reactive client swap. Top-level README updated to list this demo after the postgres one and before the ssrf-guard rows. Local verification note: my Windows + Docker Desktop setup has the named-pipe quirk where Testcontainers ends up talking to the CLI proxy pipe (com.docker.desktop.address=npipe://...docker_cli) and getting a 400 placeholder response, so the local `./gradlew test` couldn't reach a real Docker engine. Compile passes cleanly, and CI on a clean Ubuntu runner (unix socket, no proxies) is the meaningful verification — that will run on this PR.
8683ea0 to
c990df3
Compare
…+ Sort First CI run on PR #7 failed all four ArticleControllerIT tests with 500 INTERNAL_SERVER_ERROR, and the verbose-logging commit surfaced the real cause: java.lang.IllegalStateException: No primary or single unique constructor found for interface org.springframework.data.domain.Pageable Spring Boot's SpringDataWebAutoConfiguration only registers PageableHandlerMethodArgumentResolver for Spring MVC; the @EnableSpringDataWebSupport annotation is also servlet-only. WebFlux apps that want `Pageable pageable` method parameters have to wire ReactivePageableHandlerMethodArgumentResolver (and the matching Sort one) themselves via a WebFluxConfigurer bean. PagingWebFluxConfig now does exactly that. The Javadoc on the class documents the failure mode so the next person reading the file sees straight away why this seemingly redundant boilerplate exists. Trap worth documenting (and the kind of thing future reactive demos in this repo will need to repeat — the postgres demo, which uses servlet MVC, doesn't hit this because Spring Boot auto-wires the servlet resolver for it).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fourth and final easy-paging demo. Showcases the reactive companion artifact (`kr.devslab:easy-paging-spring-boot-starter-reactive:0.4.0`) — WebFlux + Spring Data R2DBC against real PostgreSQL via Testcontainers — so the same JSON pagination envelope as the MVC/MyBatis demos is now served as `Mono<PageResponse>` over a non-blocking stack.
Reuses the Docker pattern established in #5: `docker-compose.yml` for `bootRun`, Testcontainers + `@ServiceConnection` for `./gradlew test`. The compose file binds host port 5433 (not 5432) so the reactive and postgres demos can run side by side.
Architectural difference worth flagging
The MVC demos use the `@AutoPaginate` aspect, which is MyBatis/PageHelper-specific. On the reactive path there's no per-thread context to hook into, so the reactive starter instead exposes an explicit helper that the service calls directly:
```java
>R2dbcOffsetPagingSupport.paginate(template, Article.class, criteria, pageable)
// → Mono<PageResponse
```
Wire-level contract is unchanged — clients can't tell which stack is behind a given endpoint.
Demo content
Integration test (`ArticleControllerIT`)
Uses WebTestClient (WebFlux) instead of MockMvc, and asserts via JSON paths — same shape as the postgres demo's tests with the reactive client swap. Covers:
Verification
CI expectation
The `detect` job should identify `easy-paging-reactive-demo/` as the only changed demo. The `build` job will then:
Expected to take roughly the same as the postgres demo's CI (~50s) since the image is cached.
Closes the easy-paging roadmap
With this merged, all four originally-planned easy-paging scenarios have their own runnable demos:
Test plan
Note on PR #6
I noticed PR #6 (`ssrf-guard-demos`) is open in parallel. This PR is entirely orthogonal — different starter, different directories — and doesn't touch any files in that PR's diff. The top-level README row for the reactive demo is added between the postgres row and where the ssrf-guard rows will land; the two PRs will merge cleanly in either order.