refactor(geschichte): switch entity to LAZY fetch and add GeschichteSummary projection DTO #383

Open
opened 2026-05-02 18:54:13 +02:00 by marcel · 0 comments
Owner

Context

Markus flagged on PR #382 (#5763 B2) that Geschichte declares @ManyToMany(fetch = FetchType.EAGER) on both persons and documents. With 200+ stories on the index page that drags the full Person + Document object graph through every list query — the body field is the heaviest part of the row, and Document serialises ~30 fields per row through Jackson.

The current PR follows the existing Document.java precedent (EAGER everywhere) so it is internally consistent, but the cliff is real once stories scale.

Scope

  • Switch Geschichte.persons and Geschichte.documents to FetchType.LAZY.
  • Add a GeschichteSummary DTO with the fields the index page actually needs (id, title, status, author, persons[id+displayName], documents[id+title+documentDate], createdAt, updatedAt, publishedAt, plus a server-computed plain-text excerpt field so the frontend stops shipping the full body in list responses).
  • GeschichteService.list() returns List<GeschichteSummary>.
  • GeschichteService.getById() keeps the full Geschichte.
  • Update the typed API client (npm run generate:api).
  • Update frontend list views (/geschichten/+page.svelte, GeschichtenCard.svelte, DocumentMetadataDrawer.svelte) to render the excerpt field directly instead of running plainExcerpt(g.body) client-side.

Acceptance criteria

  • The index page payload no longer contains the unbounded body field for any story.
  • Loading /geschichten with 50 published stories does fewer DB joins than today (verified by query log).
  • All existing tests still pass; integration test asserts that list() returns rows whose body field is null/absent.

Out of scope

  • The Document.java EAGER pattern. That's a separate audit — this issue is just for Geschichte.

Closes the deferred portion of Markus's review on PR #382.

## Context Markus flagged on PR #382 (#5763 B2) that `Geschichte` declares `@ManyToMany(fetch = FetchType.EAGER)` on both `persons` and `documents`. With 200+ stories on the index page that drags the full Person + Document object graph through every list query — the body field is the heaviest part of the row, and `Document` serialises ~30 fields per row through Jackson. The current PR follows the existing `Document.java` precedent (EAGER everywhere) so it is internally consistent, but the cliff is real once stories scale. ## Scope - Switch `Geschichte.persons` and `Geschichte.documents` to `FetchType.LAZY`. - Add a `GeschichteSummary` DTO with the fields the index page actually needs (`id`, `title`, `status`, `author`, `persons[id+displayName]`, `documents[id+title+documentDate]`, `createdAt`, `updatedAt`, `publishedAt`, **plus a server-computed plain-text `excerpt` field** so the frontend stops shipping the full body in list responses). - `GeschichteService.list()` returns `List<GeschichteSummary>`. - `GeschichteService.getById()` keeps the full `Geschichte`. - Update the typed API client (`npm run generate:api`). - Update frontend list views (`/geschichten/+page.svelte`, `GeschichtenCard.svelte`, `DocumentMetadataDrawer.svelte`) to render the `excerpt` field directly instead of running `plainExcerpt(g.body)` client-side. ## Acceptance criteria - The index page payload no longer contains the unbounded `body` field for any story. - Loading `/geschichten` with 50 published stories does fewer DB joins than today (verified by query log). - All existing tests still pass; integration test asserts that `list()` returns rows whose `body` field is null/absent. ## Out of scope - The `Document.java` EAGER pattern. That's a separate audit — this issue is just for `Geschichte`. Closes the deferred portion of Markus's review on PR #382.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#383