Why Price and Status Need Two Different Mapping Classes

How a relationship with its own data becomes its own class, and why price and seat status can't share the same one even though both sit between Show and a seat.

August 31, 20263 min read11 / 25

Price still needs an actual home, not just a rule that says it lives on a relationship somewhere.

Resolving Price: A Relationship Becomes Its Own Class

Run the test one more time to be sure. The same seat type, Maharaja, costs different amounts in a morning show and an evening show. The same show charges different amounts for Maharaja and a regular seat.

Price can't sit on SeatType, and it can't sit on Show, because it changes depending on which specific pair you're looking at.

That's the payoff of calling price "a relationship" concrete: when a relationship between two entities carries its own data, the relationship becomes its own class. Here, that class sits between Show and SeatType, call it ShowSeatType, and it's the one place that actually holds the price.

The Same Question, Applied to Status, Needs a Different Mapping Class

Try the identical move with a seat's booking status. Picture one auditorium running a morning show and an evening show, using the exact same physical seats. Someone books seat 14 for the morning show.

Does that seat need to show as booked for the evening show too? No.

So status can't live on Seat either, for the same reason price couldn't live on SeatType. But it also can't reuse ShowSeatType, because status doesn't vary by seat type, it varies by one specific seat. That calls for its own mapping class, ShowSeat, sitting between Show and Seat.

Price lives on a mapping class between Show and SeatType, since it's shared by every seat of that type. Status lives on a separate mapping class between Show and Seat, since it's unique to one physical seat ExpandPrice lives on a mapping class between Show and SeatType, since it's shared by every seat of that type. Status lives on a separate mapping class between Show and Seat, since it's unique to one physical seat

Two relationships that both sit "between a show and a seat" can still need two different classes, because one varies by type and the other varies by the individual seat.

Why Collapsing Them Into One Class Causes Redundancy

A fair pushback shows up here: since every seat already has a type, why not skip ShowSeatType entirely and put price directly on ShowSeat, alongside status?

That's not wrong, but it's not the better choice. Say a show sells fifty Gold seats. Storing price on ShowSeat means fifty separate rows, each independently holding the exact same number.

Change that price later, and now fifty rows need updating instead of one, and every one of those updates is a chance for two rows to drift out of sync.

There's a real, defensible trade-off underneath this, worth naming rather than treating as an absolute rule. Collapsing the two tables does cut down on joins, and reads can get faster because of it. That's a legitimate call to make, as long as it's a deliberate one, stated out loud, not a default reached because nobody noticed the redundancy in the first place.

Show's Actual Attributes, and a Few Traps Along the Way

With both mapping classes settled, Show itself ends up holding: an ID, one Auditorium, a start time, an end time, a reference to the movie, and a language.

A few of those hide a decision worth noticing. A show happens in exactly one auditorium, never a list of them, since one specific screening is a single real-world event. Duration doesn't need its own field either, since start and end time already answer that question without storing it twice.

Theatre doesn't belong on Show directly, for the same reason a redundant relationship got cut earlier in this series: Auditorium already leads to a theatre, so adding the theatre again would just be storing the same fact twice.

A movie can support a whole list of languages, but a specific show plays in exactly one of them. So Movie.languages is a list while Show.language is a single value, even though they're describing the same underlying idea at two different levels of specificity.

One more trap: a feature like an age restriction is real and genuinely exists on the actual platform, but it doesn't belong here unless a discussed requirement actually calls for it. An attribute earns its place by serving a use case that's already on the table, not by being a plausible-sounding real-world detail.