I have to design a part of a system in responsible for creating tickets for attractions and then sell them. Basically there are
three different parts:
- Product: to create the tickets for sale for each attraction.
- Prices: to assign prices according to some criterias.
- Qutoas: to manage the available quotas.
I have to take care of point 1. Once the ticket is created, other people will be in charge of maintaining its selling prices and quotas.
I am interested in the part of creating and offering a ticket option. The user will be able to see what options are available for a 5 year old child and an adult (or other combination). The system will display a list of all the ticks that meet the search criteria.
Once an option is selected, another process will check if there is a quota and what price it has.
My problem is centered on step 1. Create the tickets.
From the point of view of creation, I think that there is the object
Attraction. An attraction can be “Theme Park
X” or a theater show or a museum. The attraction has a configuration that determines which
TicketOptions can be created. For example, entry times, person categories by age, languages in case of a guided tour.
So an attraction might have a “General Admission Ticket” that is valid for both children and adults. But there may be a “Park + Night Show” ticket that will be created for adults only.
Attraction is who has the configuration of how a ticket can be created. Therefore, the attraction would be the root. And it would have a list of
Apart from the creation it is also in charge of invalidating the tickets options created. If the attraction is classified as “adults only” in the future, it’ll have to know which ticket exists in order to invalidate those for minors.
The problem is that the application as a whole will actually work with
TicketOptions. It doesn’t work with
Attraction. Everything else is ticket-centric.
My problem is that
TicketOption, in the context of creation I see it as a child of an aggregate. But, at the same time, in all other contexts it would make sense for it to be a root.
TicketOption has an ID and the other systems reference it. Then I would have an external reference to a child of an aggregate. Which doesn’t sound right to me.
Is there any way to solve this problem?
“Don’t play with another aggregate root’s child entities” is a rule that is overrated because people tend to overuse aggregates and thus over-apply it:
First of all, if we’re talking about a back-office/administration interface with low to moderate complexity and low collaboration, DDD’s tactical patterns (aggregate, repository…) don’t necessarily make sense there. Sometimes a simple CRUD is just good enough.
Aggregate is a write (command)-side pattern. The read side of your application is allowed to manipulate
Ticket Options without going through their parent
My problem is that TicketOption, in the context of creation I see it as a child of an aggregate. But, at the same time, in all other contexts it would make sense for it to be a root.
The interesting word here is “context”. You might have discovered that there are multiple bounded contexts inside the Product domain. It’s perfectly fine for an entity to be a child in one bounded context’s model and (its homonym) to be a root or a value object in another.
Be careful about reference data <-> production data, or Template <-> Instance business rules. You seem to say that if a ticket (Template) changes to
AdultsOnly, sold tickets (Instances) might have to be invalidated. I would recommend against Template aggregates containing Instance children: in this case don’t make
Attractionthe parent of
SoldTickets. This type of rule is better handled with eventual consistency than inside the same aggregate because it potentially affects a lot of entities.
SoldTicketis also probably already too big an aggregate root of itself with its own ivariants, etc. to become the child of another entity.
OK I think I understand your problem.
Where adding and removing ticket options has invariants,
But you also want
I don’t think this is a real issue as long as you just have read only methods for the TicketOptions.
Essentially your second call is a report on the stored data. You could enforce the readonlyness and add more separation by introducing a second type
PublishedTicketOption or similar. It sounds like publishing would be a useful process to have anyway in this scenario where you might have new ticket types only available from date X, or “can’t delete ticketoption as you have issues tickets of this type” validations