Design the Response Around What the Screen Needs, Not the Whole Entity

Why a response DTO shouldn't mirror every field on an entity, and why splitting data across microservices doesn't remove a relationship, it just moves who has to assemble it.

September 1, 20263 min read28 / 43

BookTicketRequestDTO only carries which seats and which show, plus who's asking. Its response, BookTicketResponseDTO, needs a lot more than that, starting with the amount actually due, and every one of those extra fields has to come from somewhere else.

Amount Comes From the Service, Not the Request

The request travels from the controller straight into the service layer. The service is what actually computes the real amount, adding whatever fees or taxes apply, and hands back a full ticket object. The controller's only job is passing that object back out as the response, not computing anything itself.

One Booking, One Ticket, Many Seats

Worth naming directly, since it explains why Ticket holds a list of seats instead of being paired one-to-one with a single seat: a platform like this issues one ticket per booking, covering every seat in it, unlike a flight, where one ticket exists per seat, per passenger. Neither shape is more "correct" than the other. It's a real business rule each platform sets for itself, not something a class diagram could have guessed on its own.

One more small design choice follows from this: the actual payment page doesn't need its own separate identity at all. A URL like bookmyshow.com/pay/{ticketId} is enough, since the ticket ID alone tells that page everything it needs to load.

Design the Response Around What the Screen Actually Needs

Building a response DTO isn't a matter of mirroring every field an entity happens to have. The real question is what the specific screen making this request actually needs, and what it already has.

Take the movie's name. By the time a user reaches checkout, the frontend already picked up that name on an earlier page. Resending it in this response would just be repeating something the client already knows. Seat numbers are the opposite case: whether they belong in the response depends entirely on whether that particular checkout screen actually displays them at all.

This is the same discipline from designing the request, aimed the other way. A request DTO gets built by asking who's allowed to control each field. A response DTO gets built by asking what the one specific screen consuming it actually needs, not what the underlying entity happens to store.

Splitting Data Across Services Doesn't Remove a Relationship, It Moves Who Assembles It

A ticket's list of payments stays empty right up until a payment actually succeeds, then gets attached to it automatically. A fair question follows from that: if payments lived in a separate microservice of their own, couldn't Ticket just store a payment ID and fetch the real details lazily, only when actually needed?

That's a legitimate technique, lazy loading, and it doesn't make the underlying relationship disappear. Someone still has to answer "what payments happened for this ticket" eventually. Moving payment data into its own service doesn't remove that responsibility, it just relocates it: whichever service needs that answer now has to reach out and assemble it itself, instead of reading it directly out of its own database.

Splitting data across services is a real, valid architectural choice. It trades a direct read for a network call, not the need to answer the question at all.