1035. Lesson identity
1036. Learning objective
After this lesson, you can justify either a direct dependency or an event for three game interactions by identifying ownership, coupling, completion needs, dispatch timing, and failure behavior.
1037. Why this matters
Events can reduce direct references, but they can also make control flow harder to see. If every interaction becomes an event, a small action can trigger a chain of consumers that no developer can easily trace. If every interaction is direct, systems that should remain independent can become tightly coupled. The goal is not to prefer one mechanism everywhere. It is to choose the mechanism that makes responsibility, dependencies, and consequences clear.
This judgment also matters when directing AI. An AI assistant can quickly generate an event bus, signal, callback, or service call. You still need to determine whether that communication makes the system easier to reason about.
1038. Prior knowledge
You should already be able to:
- Describe the S-R-P-T-F event contract from A message is a contract.
- Identify a sender, receiver, payload, timing, and failure behavior.
- Distinguish a system's responsibility from effects that other systems may present.
1039. Core concept
Use a direct dependency when one component requires a specific collaborator to complete a known operation and that relationship belongs to the feature's primary control flow. Use an event when a meaningful fact has occurred and independent systems may react without the publisher needing to know which consumers exist.
An event is not automatically better because it removes a direct reference. Publishers and subscribers still share dependencies on the event name, payload contract, dispatch semantics, ordering guarantees, and failure policy. The publisher does not need to know each subscriber, but the communicating system still needs an explicit contract.
The key question is:
Is this communication a request to a known owner, or a fact that independent observers may react to?
A request for a known owner to perform an operation usually favors a direct dependency. A completed fact with independent consumers usually favors an event.
1040. Mental model
The visibility–distribution test
Evaluate each interaction with these questions:
| Question | Favors a direct dependency | Favors an event |
|---|---|---|
| Who owns the operation? | A specific collaborator owns it | No single consumer owns every reaction |
| What is being communicated? | A request or command | A completed fact or occurrence |
| How many consumers are expected? | One known collaborator | Several independent or optional consumers |
| Does the initiator require completion or a return value? | Yes; the primary flow needs a result or explicit failure | No; the publisher can report the fact without collecting each consumer's result |
| What timing contract is required? | The call contract defines when completion or failure returns | Dispatch may be synchronous, deferred, or queued, but that timing must be stated explicitly |
Do not assume that an event runs later. An event dispatcher may invoke consumers immediately in the same call stack, defer them until a later update, or place the message in a queue. The contract must specify which behavior applies, along with any ordering and failure guarantees.
Then apply the trace test:
- Can a developer follow the primary outcome from the initiator to the operation's owner through a small number of explicit references?
- If an event is used, can its contract and important consumers be found?
- Is the event name a stable domain fact, or is it disguising a request such as
OpenDoorRequested? - Which component must know the collaborator or message contract, and is that knowledge appropriate for its responsibility?
Choose the option that keeps the primary control flow visible while preserving the independence the interaction actually needs.
Direct dependency versus event
Direct request:
PlayerInteraction -> Door.open()
Published fact:
Door -> publishes DoorOpened
DoorOpened -> [QuestTracker, AudioSystem, TutorialSystem]
The first line names the object responsible for attempting to open the door. The caller can receive a success or failure result from that owner. The second reports that the door opened; each independent consumer decides whether the fact matters to it.
The DoorOpened contract must still define dispatch behavior. Consumers might run synchronously, during a later update, or through a queue. That choice comes from the architecture's contract, not from the fact that the communication is an event.
1041. Concrete example
Consider a chest interaction:
- The player presses the interact button.
- The interaction controller asks the targeted chest to open.
- The chest checks its lock and inventory state.
- The controller receives the result needed for the immediate interaction.
- If opening succeeds, other systems may react to the completed fact.
A reasonable design is:
InteractionController -> Chest.try_open(player_context)
Chest.try_open -> returns success or failure
Chest -> publishes ChestOpened(chest_id, contents_summary)
ChestOpened -> QuestTracker updates objectives
ChestOpened -> AudioPresentation plays the opening sound
ChestOpened -> UI presents the reward
try_open is a direct dependency because the controller needs a specific chest to perform an operation and return a result. The controller appropriately knows the targeted chest and its operation contract.
ChestOpened is an event because it reports a fact with several independent consumers. The chest knows the event contract but does not need to know which consumers are registered. Each consumer knows the same contract and the dispatch semantics that affect its reaction.
An event-only design such as OpenChestRequested can be appropriate in an architecture with explicit command routing, but it is not automatically an improvement. If one controller targets one known chest and needs the result, routing the request as an event may obscure the operation's owner and make completion and failure harder to trace.
1042. Common mistakes
Treating decoupling as the same thing as quality
Replacing every method call with an event may remove an import while adding indirect coupling to message names, payload interpretation, dispatch behavior, ordering, and failure policy. The publisher need not know specific subscribers, but all participants still depend on the shared contract.
Assuming events are asynchronous
Events may be dispatched synchronously, deferred, or through a queue. If timing, ordering, or failure isolation matters, record those properties in the contract instead of relying on the word “event.”
Publishing disguised method calls
InventoryAddItemRequestedByChestAnimation describes an implementation path rather than a stable fact. Prefer a direct call for a request to a known owner, or publish a concise fact such as ChestOpened when the occurrence has independent meaning.
1043. Guided practice
For each interaction below, choose direct dependency, event, or a deliberate combination of both. Justify the choice using ownership, request-versus-fact meaning, consumer distribution, required completion or return values, dispatch timing, failure behavior, and introduced knowledge.
Interaction A — Door operation
The player interaction controller needs a locked door to attempt opening. The controller must display whether the attempt succeeded or failed. Only the targeted door owns the lock rules.
Interaction B — Mission consequence
A door has successfully opened. The mission system, audio presentation, and tutorial system may each react. The door should not know which of these systems are present. The event contract must state whether reactions occur synchronously, are deferred, or are queued.
Interaction C — Save request
The pause menu needs to request a save and display a clear result or error. One save service owns serialization and storage policy.
Use this worksheet:
| Interaction | Mechanism | Owner | Request or fact? | Consumers | Completion, timing, and failure | Required collaborator or contract knowledge |
|---|---|---|---|---|---|---|
| A | ||||||
| B | ||||||
| C |
A strong answer will usually select a direct dependency for A, an event for B, and a direct dependency for C. A combination can be valid when it preserves the direct result required by the caller and publishes a separate completed fact for independent observers. The justification matters more than matching a single pattern.
1044. Validation / evidence
Your evidence is a completed three-row decision table and three short explanations, one for each interaction. Each explanation must:
- Name the component that owns the operation or fact.
- State whether the communication is a request or a completed fact.
- Identify the expected consumers.
- State whether the initiator requires completion or a return value.
- Describe the applicable timing, ordering, and failure behavior without assuming that events run later.
- Identify which component must know the collaborator or message contract, and explain whether that knowledge is appropriate.
- Explain one consequence of choosing the alternative mechanism.
Before moving on, apply the trace test to your decisions. You should be able to trace a request from its initiator to the operation's owner, or trace a published fact from its event contract to its important consumers, without relying on an unexplained global listener.
1045. Key takeaways
- A direct dependency is appropriate for a request to a known owner when the initiator requires completion or a result.
- An event is appropriate for a meaningful completed fact with independent consumers.
- Events may be synchronous, deferred, or queued; timing must be part of the contract.
- Events remove some direct references but retain shared dependencies on contracts and dispatch semantics.
- Good communication design keeps the primary flow traceable and places knowledge in components whose responsibilities justify it.
1046. Next lesson
Continue to 3.4 — Declarative data: Data is not behavior, where you will build on explicit communication boundaries by distinguishing the data a system receives from the behavior that interprets it and the state that changes over time.
1047. Knowledge check
Answer these items for yourself before reading the answers.
Which situation most strongly favors a direct dependency?
Show answer and feedback
Answer: A known caller needs a result from one component that owns the operation.
Why: A direct dependency is appropriate when a known caller needs a specific collaborator to perform an operation and return a result. The other choices describe characteristics that favor an event.
Why can replacing every method call with an event create hidden coupling?
Show answer and feedback
Answer: Publishers and subscribers still share dependencies on the event contract and dispatch semantics, including timing, ordering, and failure policy.
Why: An event can remove a direct reference without removing all dependency. Participants still rely on the message contract and on how dispatch, timing, ordering, and failures are defined. A well-designed publisher does not need to know specific subscriber registrations.
A door opens successfully, and the quest, audio, and tutorial systems may react independently. Which design best communicates the situation?
Show answer and feedback
Answer: The door publishes a concise DoorOpened fact under an explicit dispatch contract.
Why: DoorOpened is a completed fact with independent consumers. Publishing it avoids requiring the door to know each consumer, while the explicit contract defines whether dispatch is synchronous, deferred, or queued.
What should a strong communication-mechanism justification include?
Show answer and feedback
Answer: Ownership, request-versus-fact meaning, consumers, completion needs, dispatch and failure semantics, and who must know the collaborator or contract.
Why: The mechanism should be justified through responsibility, information needs, contract knowledge, and observable communication behavior. Code volume, universal rules, and framework syntax do not establish whether the boundary is appropriate.