ExerciseChallenge
Challenge
Six endpoints, and what each publishes
25 minintermediate1–15 yrs
Edge cases. You have to reason, and two valid fixes differ.
What this teaches
- The same defect produces disclosure, coupling, recursion or an N+1 depending on the endpoint
- A DTO built after the transaction ends fixes nothing
- Accepting an entity is a write vulnerability, not a design preference
- Annotations that stop the recursion make the entity worse for the next consumer
Starter
Starter.java
import java.util.*;
/**
* Challenge: six endpoints from one service. All six return or accept an
* entity somewhere. They fail in four different ways.
*/
public class Starter {
// ── 1 ─────────────────────────────────────────────────────────────────
// @GetMapping("/users/{id}")
// public User get(@PathVariable Long id) { return repo.findById(id).orElseThrow(); }
// User has: id, email, displayName, passwordHash, lastLoginIp, deleted
// ── 2 ─────────────────────────────────────────────────────────────────
// @PostMapping("/users")
// public User create(@RequestBody User user) { return repo.save(user); }
// ── 3 ─────────────────────────────────────────────────────────────────
// @GetMapping("/authors/{id}")
// public Author get(...) // Author has List<Book>, Book has Author
// ── 4 ─────────────────────────────────────────────────────────────────
// @GetMapping("/orders/{id}")
// public OrderResponse get(@PathVariable Long id) {
// return mapper.toResponse(orderService.find(id)); // service is @Transactional
// }
// ── 5 ─────────────────────────────────────────────────────────────────
// @GetMapping("/books/{id}")
// public Book get(...) // Book.author is annotated @JsonIgnore
// ── 6 ─────────────────────────────────────────────────────────────────
// @GetMapping("/orders")
// public List<Order> list() // Order.lines is LAZY, open-in-view is default
public static void main(String[] args) {
// TODO 1: classify each endpoint by which problem it has — disclosure,
// schema coupling, recursion, or queries during serialisation. Some
// have more than one.
// TODO 2: for endpoint 1, list every field a client can see today.
// Then say what happens when someone adds a `mfa_secret` column.
// TODO 3: for endpoint 2, list every field a caller can SET. Which one
// turns a create into an update, and against which row?
// TODO 4: endpoint 4 has a DTO and still fails. Explain where the
// mapping happens and why that undoes the DTO. Move it.
// TODO 5: endpoint 5 is "fixed". Say what a consumer who needs the
// author from a book must now do, and who ends up paying for that fix.
// TODO 6: endpoint 6 works in production and is slow. Count the
// queries for a page of 50 orders, and say in which phase they happen.
// Then say what changes if open-in-view is turned off — the answer is
// not "it gets faster".
// TODO 7: one change fixes five of these six. Name it, name the one it
// does not fix, and say what that one needs instead.
}
}Run it locally:
cd exercises/java/jpa-hibernate/entity-at-the-boundary/02-challenge
javac Starter.java -d /tmp/out && java -cp /tmp/out StarterHints
Hint 1
For each endpoint, answer three things: what fields does it publish, what can a caller set, and where does the mapping happen relative to the transaction.
Hint 2
One endpoint has a DTO and still throws. The DTO is not the problem.
Hint 3
One endpoint is only dangerous in the request direction. List what a caller could set that the developer never considered.
Hint 4
One is fixed with @JsonIgnore and is worse for it. Say who pays.
Done when
- Each endpoint classified by which of the four problems it has
- The DTO-that-still-throws is diagnosed as a placement problem
- The request-body endpoint has a list of fields a caller could set, including the dangerous one
- You said what the @JsonIgnore endpoint costs the next consumer
- One change is named that fixes five of the six, and its cost is stated
← Back to Should you return a JPA entity from a REST controller?