You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+21Lines changed: 21 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -116,6 +116,27 @@ Still fire-and-forget, but failures are visible in production logs.
116
116
Any in-memory collection that grows per-message (e.g. conversation history) must have a configurable cap.
117
117
Without a cap, long-running sessions leak memory.
118
118
119
+
### One grain per responsibility — isolate heavy I/O
120
+
Each major domain component should operate in its own grain. When a grain needs heavy I/O (DB writes, persistence flushes), delegate that work to a dedicated secondary grain so it does not block the primary grain's turn.
121
+
- Example: `RoomGrain` delegates furniture saves to `RoomPersistenceGrain`. The room grain stays responsive while persistence queues and flushes.
122
+
- Do not combine domain logic and persistence flushing in the same grain.
123
+
124
+
### Use grain boundaries for thread safety
125
+
Orleans grains are single-threaded by design. Use this for concurrency-sensitive operations by giving each user their own grain for the operation.
126
+
- Example: each player gets a `PurchaseGrain` so catalog purchases are serialized per-player with no locks needed.
127
+
- Example: limited-edition items should use a dedicated grain (e.g. `LimitedItemGrain`) so concurrent buyers are safely serialized.
128
+
- Do not add manual locking (`lock`, `SemaphoreSlim`) inside grains — that fights the actor model.
129
+
130
+
### Grains orchestrate their own outbound communication
131
+
When grain state changes (e.g. wallet balance updates), the grain itself sends the snapshot to `PlayerPresenceGrain.SendComposerAsync`. The caller that triggered the change does not pass or send the composer — the grain owns that responsibility.
-**Wrong**: handler calls `grain.UpdateWalletAsync(...)` → handler builds composer → handler sends composer to player.
134
+
135
+
### Do not mutate the database directly for grain-owned state
136
+
Grains may hold cached or in-memory state that will not reflect direct DB changes. All mutations to grain-owned data must go through the grain's methods, even when the player is offline.
137
+
- If a grain uses `[PersistentState]`, state is hydrated from the configured store (not DB) on activation. Direct DB edits will be overwritten by stale store data.
138
+
- Admin tools and external systems must call grain methods, not issue raw SQL/DB updates, for data that grains own.
Copy file name to clipboardExpand all lines: CONTEXT.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -61,6 +61,10 @@
61
61
- When a delete + insert must be atomic, use EF tracked operations (`Remove` + `SaveChangesAsync`), not `ExecuteDeleteAsync`.
62
62
- Replace `.Ignore()` on grain tasks with a `LogAndForget` helper that logs faulted continuations.
63
63
- In-memory collections that grow per-event (message history, queues) must have a configurable cap.
64
+
- One grain per responsibility: isolate heavy I/O (DB writes, persistence) into secondary grains so the primary domain grain stays responsive (e.g. `RoomGrain` → `RoomPersistenceGrain`).
65
+
- Use grain single-threading for concurrency safety: per-player `PurchaseGrain` for catalog buys, dedicated grains for limited-edition items. Do not add manual locks inside grains.
66
+
- Grains orchestrate their own outbound: when state changes, the grain sends the composer via `PlayerPresenceGrain.SendComposerAsync`. Callers do not build or send composers after calling a grain method.
67
+
- All mutations to grain-owned data must go through grain methods. Do not update the database directly — grains may hold cached state that will not reflect raw DB changes.
0 commit comments