# Choose how modules talk

Modules never import each other. Pick the mechanism by what the caller needs:

| You need to | Use | Guide |
| --- | --- | --- |
| tell others something happened | an [event](<https://sandloom.dev/docs/modules/between/events/index.md>) | [Add an event](<https://sandloom.dev/docs/modules/between/add-event/index.md>) |
| react to something another module did | an [`@on` consumer](<https://sandloom.dev/docs/modules/between/events/index.md>) | [Consume an event](<https://sandloom.dev/docs/modules/between/add-consumer/index.md>) |
| ask another module now | a [contract](<https://sandloom.dev/docs/modules/between/contracts/index.md>) | [Provide and call a contract](<https://sandloom.dev/docs/modules/between/add-contract/index.md>) |
| show a list that spans modules | a [projection](<https://sandloom.dev/docs/modules/between/projections/index.md>), never a request-time join | [Build a projection](<https://sandloom.dev/docs/modules/between/add-projection/index.md>) |
| run a process across modules, with undo | a [saga](<https://sandloom.dev/docs/modules/between/sagas/index.md>) owned by one module | [Add a saga](<https://sandloom.dev/docs/modules/between/add-saga/index.md>) |

## Event or contract?

Most choices come down to these two.

|  | Event | Contract |
| --- | --- | --- |
| **The caller** | moves on | waits for the answer |
| **Timing** | published after commit, handled later | runs during the call |
| **Delivery** | at least once, so a consumer may see a duplicate; `@on` claims each event once | one call; a command is deduplicated by its idempotency key |
| **Transaction** | atomic with the producer's write, through the outbox | runs in the provider's own unit, not atomic with the caller's |
| **If the other side is down** | the default `streams` bus delivers the event later, within [stream retention](<https://sandloom.dev/docs/modules/between/events/#poison-events-and-the-dead-letter-queue>); the volatile `redis` and `memory` buses reach only live subscribers | the call fails with a 503 |
| **Direction** | the producer does not know who listens | the caller names the provider |

Prefer an event when the other module only needs to know something happened. Use a contract when the caller cannot continue without the answer.

## Referring to another module's data

Store the other module's id as a plain UUID, never as a foreign key. A module may later move to its own database, and a foreign key could not follow it.
