Loslegen
Javaneer
Zurück zur Stufe
Stufe 5·API- & Vertragsdesign

Ressourcen entwerfen

Ressourcen und URIs modellieren (Substantive, keine Verben), die Konventionen für Pagination, Filterung und Sortierung - und Repräsentationen, die deine Datenbank nicht durchsickern lassen.

14 Min. LesezeitFortgeschritten

Deutsche Übersetzung in Arbeit

Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.

Auf dieser Seite

Getting the verbs right (last lesson) is half of REST; the other half is modeling the resources those verbs act on. Good resource design makes an API feel obvious - a developer guesses the URL correctly on the first try. Bad design leaks your database schema, buries actions in query strings, and forces consumers to memorize idiosyncrasies. The craft is choosing the right nouns, shaping representations that hide your internals, and following the boring conventions for collections.

Resources are nouns; hierarchy shows relationships

A resource is a thing the API exposes - an account, a transaction, an invoice. URLs name these nouns, and nesting expresses containment:

/accounts                     collection of accounts
/accounts/42                  a single account
/accounts/42/transactions     the transactions belonging to account 42 (a sub-collection)
/accounts/42/transactions/9   one specific transaction of that account

Conventions that keep this predictable:

  • Plural nouns for collections (/accounts, not /account) - consistency beats grammar debates.
  • Nest to show relationship, but not too deep. /accounts/42/transactions is clear; nesting four levels (/banks/1/branches/2/accounts/42/transactions/9) is painful - once a resource has its own stable id, prefer /transactions/9 over deep nesting.
  • No verbs (last lesson) - the action is the HTTP method.

The awkward case: actions that aren't CRUD

Not everything is create/read/update/delete. "Freeze this account," "transfer money," "retry this job" are operations, and forcing them into pure CRUD is unnatural. Two accepted approaches:

  • Model the operation as a resource. A transfer is a thing: POST /transfers with a body describing it, returning a Transfer you can later GET. This is often the cleanest - the operation becomes a first-class, inspectable resource.
  • A controller sub-resource for state transitions: POST /accounts/42/freeze. Pragmatic and readable for simple state changes; use it sparingly and consistently.

Prefer modeling operations as resources when they have their own data or lifecycle; the sub-resource verb is fine for simple, stateless transitions.

Collection conventions: paging, filtering, sorting

Any collection large enough to matter must never return everything at once (recall the batch/streaming lessons - unbounded results kill servers and clients). Use consistent query parameters:

GET /transactions?accountId=42&status=SETTLED     ← filtering
GET /transactions?sort=-createdAt                 ← sorting (- for descending)
GET /transactions?page=2&size=50                  ← pagination (or cursor-based)
  • Filtering narrows a collection via query params - these are not new resources, just views of one.
  • Sorting via a sort param; a leading - for descending is a common convention.
  • Pagination is mandatory for large collections. Offset pagination (page/size) is simple but drifts if data changes between pages; cursor pagination (an opaque ?after=<cursor>) is stable and scales better for large or live datasets. Return metadata (total count or a next-page link) so clients can navigate.

Representations should not leak your database

The JSON you return is a representation you design deliberately - it is not your JPA entity serialized:

// ❌ leaking the entity: exposes internal column names, an internal FK, a nullable audit field
{ "acct_id": 42, "bal": 100.00, "usr_fk": 7, "internal_risk_flag": null, "db_version": 3 }

// ✅ a designed DTO: consumer-friendly names, only what clients should see, computed fields
{ "id": 42, "balance": "100.00", "currency": "USD", "status": "ACTIVE",
  "ownerId": 7, "createdAt": "2026-07-22T10:00:00Z" }

Serializing entities directly couples your public contract to your schema (rename a column, break every client), exposes fields consumers shouldn't see, and leaks internal concepts. Map to a purpose-built response DTO at the boundary - the same "map at the edge" discipline from the hexagonal module, now protecting your API contract.

Design money and dates carefully - they're contract landmines

Represent money as a string or integer minor units ('100.00' or 10000 cents), never a floating-point number - JSON floats lose precision and clients disagree on rounding. Always include the currency. Use ISO-8601 with a timezone (UTC) for timestamps, never a raw epoch or a local-time string. These small representation choices are part of the contract, and getting them wrong causes subtle, expensive bugs across every consumer.

A shop window vs. the stockroom shelves

Your database is the stockroom: shelves labeled with cryptic SKUs, internal reorder flags, supplier codes - organized for your operations. The API representation is the shop window: a deliberately arranged display showing customers exactly what they need in an appealing, understandable form, hiding the loading dock and the inventory system. A shop that just wheeled its stockroom shelves to the front (serializing entities) confuses customers with internal codes and exposes things they shouldn't see - and every time you reorganize the stockroom, the 'display' breaks. You design the window on purpose, independently of how the stockroom happens to be arranged.

Design the resource model

Design the API for this feature: users have wallets, wallets contain transactions, and a user can initiate a 'top-up' (adding funds, which is a multi-step operation with its own status). Also, the transactions list can be huge. Sketch the URLs and verbs for: listing a wallet's transactions (filtered by status, paginated), fetching one transaction, and initiating a top-up. Note one representation choice you'd make carefully.

Why should an API return a designed DTO rather than serializing your JPA entity directly?

Key takeaways

  • Resources are nouns; URL hierarchy shows relationships (/accounts/42/transactions) - use plural collections and avoid over-deep nesting.
  • For non-CRUD operations, model the operation as a resource (POST /transfers) or use a controller sub-resource for simple state changes (POST /accounts/42/freeze).
  • Large collections must be paginated (offset or, better, cursor) and support consistent filtering and sorting query params - never return everything.
  • The API representation is a deliberately designed DTO, not your serialized entity - protecting the contract from schema changes and hiding internal fields.
  • Representation details are part of the contract: money as string/minor-units with a currency (never a float), timestamps as ISO-8601 UTC.
War diese Lektion hilfreich?
Diese Seite auf GitHub bearbeiten