Skip to content

chore: repurpose as Spring Boot starter examples repo#1

Merged
jlc488 merged 1 commit into
mainfrom
chore/repurpose-as-examples-repo
May 23, 2026
Merged

chore: repurpose as Spring Boot starter examples repo#1
jlc488 merged 1 commit into
mainfrom
chore/repurpose-as-examples-repo

Conversation

@jlc488
Copy link
Copy Markdown
Contributor

@jlc488 jlc488 commented May 23, 2026

Summary

Strip the GitHub demo-repository template and reframe this repo as a home for runnable examples of devslab-kr Spring Boot starters and libraries. Sets up the structural scaffolding (README, CI, .gitignore) so the first demo (and every demo after) can be added with zero workflow changes.

What changed

  • Removed demo-template leftovers: `index.html`, `package.json`, `.github/workflows/proof-html.yml`. Kept `auto-assign.yml` — auto-assigning issues/PRs is still useful here.
  • Rewrote `README.md`: documents the multi-demo layout, the "each demo = standalone Gradle project" convention, and how to add a new demo.
  • Added `.gitignore`: Gradle-flavoured.
  • Added `.github/workflows/ci.yml`: detect + matrix strategy.
    • On PRs: builds only demos whose files changed (path-filtered via `git diff`).
    • On `main` pushes: builds every demo (catches drift from external starter version bumps).
    • Detection scans for top-level dirs containing a `build.gradle.kts` — no workflow edits when adding a new demo.

Design choice worth noting

Each demo will be a standalone Gradle project (its own `settings.gradle.kts` and `gradlew`), not a subproject of a shared root build. This means:

  • Demos can drift in JDK / Spring Boot baseline / dependency versions independently.
  • Breaking one demo doesn't block CI for the others.
  • Dependabot bumps are scoped per demo.

Trade-off: slightly more boilerplate per demo (each has its own wrapper). Worth it for the isolation.

What's next

No demos exist yet. The first one (`easy-paging-demo`) lands in a follow-up PR and establishes the template other demos copy from.

Test plan

  • CI green on this PR (detect job runs, build job correctly skipped — no demos exist yet)
  • After merge: a follow-up PR adding `easy-paging-demo/` triggers the build job for exactly that demo

Strip the GitHub demo-repository template and reframe this repo as a
home for runnable examples of devslab-kr Spring Boot starters and
libraries. Each future demo will live in its own top-level directory
as a standalone Gradle project (independent settings.gradle.kts and
gradlew), so demos can drift in JDK / Spring Boot baseline / starter
versions without affecting each other.

Changes:
- Remove demo-template leftovers: index.html, package.json, and the
  proof-html workflow (kept auto-assign.yml — still useful here).
- Rewrite README to document the multi-demo layout, conventions, and
  how to add a new demo.
- Add a Gradle-flavoured .gitignore.
- Add .github/workflows/ci.yml with a detect + matrix strategy:
  - On PRs, build only demos whose files changed.
  - On main pushes, build every demo (catches drift from external
    starter version bumps).
  - Detection scans for top-level directories containing a
    build.gradle.kts, so adding a demo needs no workflow edits.

No demos exist yet; the first one will land in a follow-up PR to
establish the template other demos can copy from.
@jlc488 jlc488 merged commit f6dcedf into main May 23, 2026
3 checks passed
@jlc488 jlc488 deleted the chore/repurpose-as-examples-repo branch May 23, 2026 04:54
jlc488 added a commit that referenced this pull request May 23, 2026
Two bugs in the workflow from #1 conspired to make path-filtered builds
silently skip on every PR. Discovered while opening #2, the first demo PR,
where the build job was reported as 'skipping' even though
easy-paging-demo/ was clearly the changed directory.

Bug 1 — GitHub Actions ternary trap on fetch-depth
    fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }}
The intent was "0 (full history) on PRs, 1 (shallow) otherwise". But in
Actions expressions, `0` is falsy, so `0 || 1` short-circuits to `1`.
The expression evaluated to `1` on both PRs and main pushes, leaving PR
checkouts with no history beyond the merge commit.

Bug 2 — silent failure on git diff
    mapfile -t changed_files < <(git diff --name-only "$base" HEAD)
With bug 1 in effect, `git diff` exited with "fatal: bad object" because
the base SHA wasn't fetched. But process substitution does not propagate
the subshell's exit status under `set -e`, so the script kept going with
an empty `changed_files` array — and therefore an empty matrix and a
"skipped" build job. CI looked green, but it had built nothing.

Fix:
- fetch-depth: 0 unconditionally. Cost is negligible for this repo and
  removes the ternary footgun entirely.
- Run git diff in a separate statement so `set -e` actually catches a
  non-zero exit, with an explicit ::error:: pointer to the likely cause
  (shallow checkout) so the next person debugging this gets a head start.

Verification will happen on the next PR (the rebase of #2) — the matrix
should populate with easy-paging-demo and the build job should actually
execute ./gradlew build instead of skipping.
jlc488 added a commit that referenced this pull request May 23, 2026
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.
jlc488 added a commit that referenced this pull request May 23, 2026
…#7)

* feat: add easy-paging-reactive-demo

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.

* chore(reactive-demo): verbose test logging for CI diagnosis

* fix(reactive-demo): register WebFlux argument resolvers for Pageable + 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).
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.

1 participant