@cratis/pi 7.102.0 → 7.103.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package/corpus/harnesses/copilot/instructions/profiles.instructions.md +10 -1
- package/package/corpus/harnesses/copilot/instructions/pull-requests.instructions.md +2 -0
- package/package/corpus/harnesses/copilot/instructions/secrets.instructions.md +4 -0
- package/package/corpus/hooks/README.md +11 -1
- package/package/corpus/hooks/scripts/cratis-check-pr.mjs +12 -1
- package/package/corpus/hooks/scripts/cratis-guard-secrets.sh +79 -23
- package/package/corpus/hooks/scripts/cratis-guard-writes.sh +145 -40
- package/package/corpus/rules/profiles.md +10 -1
- package/package/corpus/rules/pull-requests.md +2 -0
- package/package/corpus/rules/secrets.md +4 -0
- package/package/corpus/skills/analyzing-dotnet-performance/LICENSE +23 -0
- package/package/corpus/skills/analyzing-dotnet-performance/NOTICE +36 -0
- package/package/corpus/skills/analyzing-dotnet-performance/SKILL.md +214 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/async-patterns.md +122 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/collections-and-linq.md +220 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/critical-patterns.md +291 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/io-and-serialization.md +135 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/memory-and-strings.md +219 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/regex-patterns.md +95 -0
- package/package/corpus/skills/analyzing-dotnet-performance/references/structural-patterns.md +38 -0
- package/package/corpus/skills/binlog-generation/LICENSE +23 -0
- package/package/corpus/skills/binlog-generation/NOTICE +22 -0
- package/package/corpus/skills/binlog-generation/SKILL.md +129 -0
- package/package/corpus/skills/check-bin-obj-clash/LICENSE +23 -0
- package/package/corpus/skills/check-bin-obj-clash/NOTICE +27 -0
- package/package/corpus/skills/check-bin-obj-clash/SKILL.md +127 -0
- package/package/corpus/skills/check-bin-obj-clash/references/common-causes-and-fixes.md +206 -0
- package/package/corpus/skills/coverage-analysis/LICENSE +23 -0
- package/package/corpus/skills/coverage-analysis/NOTICE +32 -0
- package/package/corpus/skills/coverage-analysis/SKILL.md +198 -0
- package/package/corpus/skills/coverage-analysis/references/guidelines.md +68 -0
- package/package/corpus/skills/coverage-analysis/references/output-format.md +91 -0
- package/package/corpus/skills/coverage-analysis/references/report-generation.md +58 -0
- package/package/corpus/skills/coverage-analysis/references/setup-discovery.md +307 -0
- package/package/corpus/skills/coverage-analysis/scripts/Compute-CrapScores.ps1 +80 -0
- package/package/corpus/skills/coverage-analysis/scripts/CoverageData.ps1 +272 -0
- package/package/corpus/skills/coverage-analysis/scripts/Extract-MethodCoverage.ps1 +65 -0
- package/package/corpus/skills/cratis-screenplay-command-surface/SKILL.md +26 -1
- package/package/corpus/skills/cratis-screenplay-projections/SKILL.md +17 -0
- package/package/corpus/skills/cratis-screenplay-render-and-gap-fill/SKILL.md +17 -0
- package/package/corpus/skills/cratis-screenplay-specifications/SKILL.md +8 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/SKILL.md +1 -1
- package/package/corpus/skills/cratis-screenplay-toolchain/references/cheat-sheet-example.md +1 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/references/fast-path.md +7 -0
- package/package/corpus/skills/cratis-screenplay-toolchain/references/traps.md +1 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/SKILL.md +40 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/references/response-continuations.md +1 -0
- package/package/corpus/skills/cratis-screenplay-ui-composition/references/worked-flow.md +1 -0
- package/package/corpus/skills/cratis-stage-rendering-and-sandbox/references/admission.md +3 -2
- package/package/corpus/skills/csharp-refactoring/LICENSE +23 -0
- package/package/corpus/skills/csharp-refactoring/NOTICE +22 -0
- package/package/corpus/skills/csharp-refactoring/SKILL.md +175 -0
- package/package/corpus/skills/csharp-refactoring/references/operation-catalog.md +37 -0
- package/package/corpus/skills/directory-build-organization/LICENSE +23 -0
- package/package/corpus/skills/directory-build-organization/NOTICE +21 -0
- package/package/corpus/skills/directory-build-organization/SKILL.md +205 -0
- package/package/corpus/skills/directory-build-organization/references/common-patterns.md +56 -0
- package/package/corpus/skills/directory-build-organization/references/multi-level-examples.md +167 -0
- package/package/corpus/skills/directory-build-organization/references/targetframework-props-pitfall.md +54 -0
- package/package/corpus/skills/dotnet-aot-compat/LICENSE +23 -0
- package/package/corpus/skills/dotnet-aot-compat/NOTICE +18 -0
- package/package/corpus/skills/dotnet-aot-compat/SKILL.md +265 -0
- package/package/corpus/skills/dotnet-aot-compat/references/polyfills.md +43 -0
- package/package/corpus/skills/dotnet-trace-collect/LICENSE +23 -0
- package/package/corpus/skills/dotnet-trace-collect/NOTICE +65 -0
- package/package/corpus/skills/dotnet-trace-collect/SKILL.md +116 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-monitor.md +97 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect-linux.md +84 -0
- package/package/corpus/skills/dotnet-trace-collect/references/dotnet-trace-collect.md +91 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfcollect.md +200 -0
- package/package/corpus/skills/dotnet-trace-collect/references/perfview.md +141 -0
- package/package/corpus/skills/dotnet-trace-collect/references/tool-selection.md +160 -0
- package/package/corpus/skills/dump-collect/LICENSE +23 -0
- package/package/corpus/skills/dump-collect/NOTICE +40 -0
- package/package/corpus/skills/dump-collect/SKILL.md +97 -0
- package/package/corpus/skills/dump-collect/references/container-dumps.md +412 -0
- package/package/corpus/skills/dump-collect/references/coreclr-dumps.md +108 -0
- package/package/corpus/skills/dump-collect/references/nativeaot-dumps.md +280 -0
- package/package/corpus/skills/eliminate-redundant-restores/LICENSE +23 -0
- package/package/corpus/skills/eliminate-redundant-restores/NOTICE +19 -0
- package/package/corpus/skills/eliminate-redundant-restores/SKILL.md +112 -0
- package/package/corpus/skills/extension-points/LICENSE +23 -0
- package/package/corpus/skills/extension-points/NOTICE +18 -0
- package/package/corpus/skills/extension-points/SKILL.md +248 -0
- package/package/corpus/skills/filter-syntax/LICENSE +23 -0
- package/package/corpus/skills/filter-syntax/NOTICE +17 -0
- package/package/corpus/skills/filter-syntax/SKILL.md +179 -0
- package/package/corpus/skills/including-generated-files/LICENSE +23 -0
- package/package/corpus/skills/including-generated-files/NOTICE +17 -0
- package/package/corpus/skills/including-generated-files/SKILL.md +139 -0
- package/package/corpus/skills/microbenchmarking/LICENSE +23 -0
- package/package/corpus/skills/microbenchmarking/NOTICE +36 -0
- package/package/corpus/skills/microbenchmarking/SKILL.md +131 -0
- package/package/corpus/skills/microbenchmarking/references/bdn-internals-and-tuning.md +143 -0
- package/package/corpus/skills/microbenchmarking/references/comparison-strategies.md +200 -0
- package/package/corpus/skills/microbenchmarking/references/diagnosers-and-exporters.md +150 -0
- package/package/corpus/skills/microbenchmarking/references/project-setup-and-running.md +189 -0
- package/package/corpus/skills/microbenchmarking/references/writing-benchmarks.md +310 -0
- package/package/corpus/skills/msbuild/LICENSE +23 -0
- package/package/corpus/skills/msbuild/NOTICE +55 -0
- package/package/corpus/skills/msbuild/SKILL.md +117 -0
- package/package/corpus/skills/msbuild/references/antipatterns/additional-antipatterns.md +291 -0
- package/package/corpus/skills/msbuild/references/antipatterns/incremental-build-inputs-outputs.md +57 -0
- package/package/corpus/skills/msbuild/references/antipatterns/private-assets.md +29 -0
- package/package/corpus/skills/msbuild/references/binlog-failure-analysis.md +85 -0
- package/package/corpus/skills/msbuild/references/binlog-generation.md +69 -0
- package/package/corpus/skills/msbuild/references/build-perf-baseline.md +493 -0
- package/package/corpus/skills/msbuild/references/build-perf-diagnostics.md +268 -0
- package/package/corpus/skills/msbuild/references/copy-to-output-directory.md +183 -0
- package/package/corpus/skills/msbuild/references/eval-performance.md +190 -0
- package/package/corpus/skills/msbuild/references/extension-points.md +281 -0
- package/package/corpus/skills/msbuild/references/incremental-build.md +365 -0
- package/package/corpus/skills/msbuild/references/msbuild-antipatterns.md +401 -0
- package/package/corpus/skills/orleans/LICENSE +21 -0
- package/package/corpus/skills/orleans/NOTICE +64 -0
- package/package/corpus/skills/orleans/SKILL.md +140 -0
- package/package/corpus/skills/orleans/references/anti-patterns.md +660 -0
- package/package/corpus/skills/orleans/references/configuration-api.md +933 -0
- package/package/corpus/skills/orleans/references/examples.md +66 -0
- package/package/corpus/skills/orleans/references/grain-api.md +784 -0
- package/package/corpus/skills/orleans/references/grains.md +75 -0
- package/package/corpus/skills/orleans/references/hosting.md +71 -0
- package/package/corpus/skills/orleans/references/implementation.md +591 -0
- package/package/corpus/skills/orleans/references/official-docs-index.md +250 -0
- package/package/corpus/skills/orleans/references/patterns.md +811 -0
- package/package/corpus/skills/orleans/references/persistence-api.md +495 -0
- package/package/corpus/skills/orleans/references/primitive-selection.md +35 -0
- package/package/corpus/skills/orleans/references/scheduling-and-services.md +295 -0
- package/package/corpus/skills/orleans/references/serialization-api.md +299 -0
- package/package/corpus/skills/orleans/references/streaming-api.md +396 -0
- package/package/corpus/skills/orleans/references/testing-patterns.md +210 -0
- package/package/corpus/skills/platform-detection/LICENSE +23 -0
- package/package/corpus/skills/platform-detection/NOTICE +17 -0
- package/package/corpus/skills/platform-detection/SKILL.md +230 -0
- package/package/corpus/skills/platform-detection/references/command-mode.md +27 -0
- package/package/corpus/skills/profiling/LICENSE +21 -0
- package/package/corpus/skills/profiling/NOTICE +50 -0
- package/package/corpus/skills/profiling/SKILL.md +144 -0
- package/package/corpus/skills/profiling/references/commands.md +439 -0
- package/package/corpus/skills/profiling/references/patterns.md +586 -0
- package/package/corpus/skills/profiling/references/profiling.md +190 -0
- package/package/corpus/skills/run-tests/LICENSE +23 -0
- package/package/corpus/skills/run-tests/NOTICE +19 -0
- package/package/corpus/skills/run-tests/SKILL.md +305 -0
- package/package/corpus/skills/target-authoring/LICENSE +23 -0
- package/package/corpus/skills/target-authoring/NOTICE +17 -0
- package/package/corpus/skills/target-authoring/SKILL.md +165 -0
- package/package/corpus/skills/vectorization/LICENSE +23 -0
- package/package/corpus/skills/vectorization/NOTICE +17 -0
- package/package/corpus/skills/vectorization/SKILL.md +189 -0
- package/package/profile-catalog.json +85 -1
- package/package.json +1 -1
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
# Persistence, Databases, Journaling, Event Sourcing, and Transactions API
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
- State Ownership and Database Boundary
|
|
6
|
+
- IPersistentState API
|
|
7
|
+
- Storage Provider Configuration
|
|
8
|
+
- Experimental Orleans.Journaling
|
|
9
|
+
- Event Sourcing with JournaledGrain
|
|
10
|
+
- ACID Transactions
|
|
11
|
+
|
|
12
|
+
Detailed state management API patterns from official Orleans documentation.
|
|
13
|
+
|
|
14
|
+
## State Ownership and Database Boundary
|
|
15
|
+
|
|
16
|
+
Use Orleans persistence to preserve state owned by a logical grain identity. The normal model is:
|
|
17
|
+
|
|
18
|
+
1. Orleans activates the grain and loads its named state before application calls are processed.
|
|
19
|
+
2. Grain code reads and mutates the in-memory state during serialized turns.
|
|
20
|
+
3. Grain code explicitly calls `WriteStateAsync` when the new state must become durable.
|
|
21
|
+
4. The storage provider persists an opaque state record keyed by grain identity and state name.
|
|
22
|
+
|
|
23
|
+
This is an entity-state API, not a general database abstraction. Select it for bounded current state addressed by grain identity. Select an application database/repository or read model for:
|
|
24
|
+
|
|
25
|
+
- joins, search, reports, arbitrary filters, or scans across many identities;
|
|
26
|
+
- large/unbounded collections, time-series, files, and append-only history;
|
|
27
|
+
- set-based updates and database-native constraints;
|
|
28
|
+
- data shared with non-Orleans applications;
|
|
29
|
+
- an existing external system of record.
|
|
30
|
+
|
|
31
|
+
It is common to use both. Let the grain own command serialization, invariants, workflow position, idempotency keys, and a small current snapshot. Let a database own query projections, history, reporting rows, or large payloads. Define which side is authoritative for each field and how projections recover after failure.
|
|
32
|
+
|
|
33
|
+
Do not:
|
|
34
|
+
|
|
35
|
+
- query another grain's storage row instead of calling its contract;
|
|
36
|
+
- mutate provider tables/containers behind an active grain;
|
|
37
|
+
- assume that using an ADO.NET or Cosmos provider turns grain state into a supported application query model;
|
|
38
|
+
- store an unbounded child collection in one grain because the provider can serialize it;
|
|
39
|
+
- keep two writable copies without conflict/reconciliation rules.
|
|
40
|
+
|
|
41
|
+
When external data is authoritative, keep only a cache plus its version/staleness metadata in the grain, or re-read the external repository at the consistency points required by the domain.
|
|
42
|
+
|
|
43
|
+
## IPersistentState API
|
|
44
|
+
|
|
45
|
+
```csharp
|
|
46
|
+
public class PlayerGrain : Grain, IPlayerGrain
|
|
47
|
+
{
|
|
48
|
+
private readonly IPersistentState<PlayerProfile> _profile;
|
|
49
|
+
private readonly IPersistentState<PlayerInventory> _inventory;
|
|
50
|
+
|
|
51
|
+
public PlayerGrain(
|
|
52
|
+
[PersistentState("profile", "profileStore")]
|
|
53
|
+
IPersistentState<PlayerProfile> profile,
|
|
54
|
+
[PersistentState("inventory", "itemStore")]
|
|
55
|
+
IPersistentState<PlayerInventory> inventory)
|
|
56
|
+
{
|
|
57
|
+
_profile = profile;
|
|
58
|
+
_inventory = inventory;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Operations
|
|
64
|
+
|
|
65
|
+
| Operation | Behavior |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `state.State` | Access/modify in-memory state |
|
|
68
|
+
| `state.ReadStateAsync()` | Reload from storage (automatic on activation) |
|
|
69
|
+
| `state.WriteStateAsync()` | Persist current state (must call explicitly) |
|
|
70
|
+
| `state.ClearStateAsync()` | Clear/delete from storage |
|
|
71
|
+
| `state.RecordExists` | Whether state exists in storage |
|
|
72
|
+
| `state.Etag` | Provider-specific optimistic concurrency token |
|
|
73
|
+
|
|
74
|
+
### State Type Requirements
|
|
75
|
+
|
|
76
|
+
```csharp
|
|
77
|
+
[GenerateSerializer]
|
|
78
|
+
public class PlayerProfile
|
|
79
|
+
{
|
|
80
|
+
[Id(0)] public string Name { get; set; } = "";
|
|
81
|
+
[Id(1)] public int Level { get; set; }
|
|
82
|
+
[Id(2)] public DateTime LastLogin { get; set; }
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- Use `[GenerateSerializer]` and `[Id(N)]` on all state types
|
|
87
|
+
- Use JSON or version-tolerant format for stored state
|
|
88
|
+
- Adding new `[Id]` members is safe; removing is safe if ID not reused; changing types is breaking
|
|
89
|
+
|
|
90
|
+
## Storage Provider Configuration
|
|
91
|
+
|
|
92
|
+
### Redis
|
|
93
|
+
|
|
94
|
+
```csharp
|
|
95
|
+
siloBuilder.AddRedisGrainStorage("redis", options =>
|
|
96
|
+
{
|
|
97
|
+
options.ConfigurationOptions = new ConfigurationOptions
|
|
98
|
+
{
|
|
99
|
+
EndPoints = { "localhost:6379" }
|
|
100
|
+
};
|
|
101
|
+
options.DeleteStateOnClear = true;
|
|
102
|
+
options.EntryExpiry = TimeSpan.FromDays(30);
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Azure Cosmos DB
|
|
107
|
+
|
|
108
|
+
```csharp
|
|
109
|
+
siloBuilder.AddCosmosGrainStorage("cosmos", options =>
|
|
110
|
+
{
|
|
111
|
+
options.DatabaseName = "OrleansState";
|
|
112
|
+
options.ContainerName = "GrainState";
|
|
113
|
+
options.IsResourceCreationEnabled = true;
|
|
114
|
+
options.DeleteStateOnClear = true;
|
|
115
|
+
options.StateFieldsToIndex = new[] { "/State/Type" };
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Azure Table/Blob Storage
|
|
120
|
+
|
|
121
|
+
```csharp
|
|
122
|
+
siloBuilder.AddAzureTableGrainStorage("table", options =>
|
|
123
|
+
options.ConfigureTableServiceClient(connectionString));
|
|
124
|
+
|
|
125
|
+
siloBuilder.AddAzureBlobGrainStorage("blob", options =>
|
|
126
|
+
options.ConfigureBlobServiceClient(connectionString));
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### ADO.NET
|
|
130
|
+
|
|
131
|
+
```csharp
|
|
132
|
+
siloBuilder.AddAdoNetGrainStorage("sql", options =>
|
|
133
|
+
{
|
|
134
|
+
options.Invariant = "Microsoft.Data.SqlClient";
|
|
135
|
+
options.ConnectionString = connectionString;
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Amazon DynamoDB
|
|
140
|
+
|
|
141
|
+
```csharp
|
|
142
|
+
siloBuilder.AddDynamoDBGrainStorage("dynamo", options =>
|
|
143
|
+
{
|
|
144
|
+
options.Service = "us-east-1";
|
|
145
|
+
options.AccessKey = accessKey;
|
|
146
|
+
options.SecretKey = secretKey;
|
|
147
|
+
options.TableName = "OrleansGrainState";
|
|
148
|
+
options.CreateIfNotExists = true;
|
|
149
|
+
options.DeleteStateOnClear = true;
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Storage Provider Summary
|
|
154
|
+
|
|
155
|
+
| Provider | Package | Config Method |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| Redis | `Microsoft.Orleans.Persistence.Redis` | `AddRedisGrainStorage` |
|
|
158
|
+
| Azure Table | `Microsoft.Orleans.Persistence.AzureStorage` | `AddAzureTableGrainStorage` |
|
|
159
|
+
| Azure Blob | `Microsoft.Orleans.Persistence.AzureStorage` | `AddAzureBlobGrainStorage` |
|
|
160
|
+
| Cosmos DB | `Microsoft.Orleans.Persistence.Cosmos` | `AddCosmosGrainStorage` |
|
|
161
|
+
| ADO.NET | `Microsoft.Orleans.Persistence.AdoNet` | `AddAdoNetGrainStorage` |
|
|
162
|
+
| DynamoDB | `Microsoft.Orleans.Persistence.DynamoDB` | `AddDynamoDBGrainStorage` |
|
|
163
|
+
| Memory (dev) | built-in | `AddMemoryGrainStorage` |
|
|
164
|
+
|
|
165
|
+
### Custom Storage Provider
|
|
166
|
+
|
|
167
|
+
```csharp
|
|
168
|
+
public class MyStorage : IGrainStorage
|
|
169
|
+
{
|
|
170
|
+
public Task ReadStateAsync<T>(string stateName, GrainId grainId, IGrainState<T> grainState) { }
|
|
171
|
+
public Task WriteStateAsync<T>(string stateName, GrainId grainId, IGrainState<T> grainState) { }
|
|
172
|
+
public Task ClearStateAsync<T>(string stateName, GrainId grainId, IGrainState<T> grainState) { }
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Experimental Orleans.Journaling
|
|
177
|
+
|
|
178
|
+
`Microsoft.Orleans.Journaling` is a separate experimental persistence surface in Orleans 10.3. It records durable state operations in an ordered journal and replays them to recover durable collections and values. The `10.3.1` package is published as `10.3.1-alpha.1`, so require an explicit adoption decision and version pin.
|
|
179
|
+
|
|
180
|
+
Use it when operation-journaled durable collections or completion state solve a concrete need. Available durable shapes include dictionary, list, queue, set, value, persistent state, and durable task completion source. Do not confuse it with `JournaledGrain<TState,TEvent>`:
|
|
181
|
+
|
|
182
|
+
| Surface | Purpose | Entry model |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| `IPersistentState<T>` | Persist the current entity snapshot | Application writes the current state |
|
|
185
|
+
| `Orleans.Journaling` | Persist/replay durable collection/value operations | Framework durable-operation codecs |
|
|
186
|
+
| `JournaledGrain<TState,TEvent>` | Event-source domain state | Application-defined business events |
|
|
187
|
+
|
|
188
|
+
Install and configure a journaling provider on the same Orleans version:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
dotnet add package Microsoft.Orleans.Journaling --version 10.3.1-alpha.1
|
|
192
|
+
dotnet add package Microsoft.Orleans.Journaling.AzureStorage --version 10.3.1-alpha.1
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```csharp
|
|
196
|
+
[JsonSerializable(typeof(string))]
|
|
197
|
+
[JsonSerializable(typeof(int))]
|
|
198
|
+
internal partial class JournalJsonContext : JsonSerializerContext;
|
|
199
|
+
|
|
200
|
+
builder.UseOrleans(siloBuilder =>
|
|
201
|
+
siloBuilder
|
|
202
|
+
.AddAzureBlobJournalStorage()
|
|
203
|
+
.UseJsonJournalFormat(JournalJsonContext.Default));
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Use durable keyed state from a `DurableGrain` and commit through `WriteStateAsync`:
|
|
207
|
+
|
|
208
|
+
```csharp
|
|
209
|
+
public sealed class CartGrain(
|
|
210
|
+
[FromKeyedServices("cart")] IDurableDictionary<string, int> items)
|
|
211
|
+
: DurableGrain, ICartGrain
|
|
212
|
+
{
|
|
213
|
+
public async ValueTask SetQuantityAsync(string itemId, int quantity)
|
|
214
|
+
{
|
|
215
|
+
items[itemId] = quantity;
|
|
216
|
+
await WriteStateAsync();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Orleans 10.2 uses JSON Lines for new journal writes by default. Existing records with format metadata remain readable; records without metadata are treated as legacy Orleans binary. Pin `JournaledStateManagerOptions.JournalFormatKey = "orleans-binary"` only when a staged compatibility plan requires continuing old-format writes.
|
|
222
|
+
|
|
223
|
+
For trimming or Native AOT, register source-generated `System.Text.Json` metadata for every journaled key, value, and state type. Test replay, compaction/snapshot migration, provider failure, and version upgrade with production-like storage.
|
|
224
|
+
|
|
225
|
+
Official package sources:
|
|
226
|
+
|
|
227
|
+
- [Orleans.Journaling README for 10.3.1](https://github.com/dotnet/orleans/blob/v10.3.1/src/Orleans.Journaling/README.md)
|
|
228
|
+
- [Azure Storage Journaling README for 10.3.1](https://github.com/dotnet/orleans/blob/v10.3.1/src/Azure/Orleans.Journaling.AzureStorage/README.md)
|
|
229
|
+
- [Orleans 10.3.0 release notes](https://github.com/dotnet/orleans/releases/tag/v10.3.0)
|
|
230
|
+
|
|
231
|
+
## Event Sourcing with JournaledGrain
|
|
232
|
+
|
|
233
|
+
```csharp
|
|
234
|
+
// State class
|
|
235
|
+
[GenerateSerializer]
|
|
236
|
+
public class BankAccountState
|
|
237
|
+
{
|
|
238
|
+
[Id(0)] public decimal Balance { get; set; }
|
|
239
|
+
|
|
240
|
+
// Apply methods — called automatically by the runtime
|
|
241
|
+
public void Apply(DepositEvent e) => Balance += e.Amount;
|
|
242
|
+
public void Apply(WithdrawEvent e) => Balance -= e.Amount;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Event base
|
|
246
|
+
[GenerateSerializer]
|
|
247
|
+
public abstract record AccountEvent;
|
|
248
|
+
|
|
249
|
+
[GenerateSerializer]
|
|
250
|
+
public record DepositEvent([property: Id(0)] decimal Amount) : AccountEvent;
|
|
251
|
+
|
|
252
|
+
[GenerateSerializer]
|
|
253
|
+
public record WithdrawEvent([property: Id(0)] decimal Amount) : AccountEvent;
|
|
254
|
+
|
|
255
|
+
// Grain
|
|
256
|
+
public class BankAccountGrain : JournaledGrain<BankAccountState, AccountEvent>, IBankAccountGrain
|
|
257
|
+
{
|
|
258
|
+
public Task Deposit(decimal amount)
|
|
259
|
+
{
|
|
260
|
+
RaiseEvent(new DepositEvent(amount));
|
|
261
|
+
return ConfirmEvents(); // immediate confirmation
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
public Task<decimal> GetBalance() => Task.FromResult(State.Balance);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Confirmation Modes
|
|
269
|
+
|
|
270
|
+
| Mode | Method | Tradeoff |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| Immediate | `ConfirmEvents()` after `RaiseEvent` | Consistent but slower |
|
|
273
|
+
| Delayed | Just `RaiseEvent`, no confirm | Higher throughput, eventual consistency |
|
|
274
|
+
| Conditional | Confirm at checkpoints | Balance of both |
|
|
275
|
+
|
|
276
|
+
### Log Consistency Providers
|
|
277
|
+
|
|
278
|
+
Three built-in providers. Configure via silo builder:
|
|
279
|
+
|
|
280
|
+
```csharp
|
|
281
|
+
siloBuilder.AddLogStorageBasedLogConsistencyProvider("LogStorage");
|
|
282
|
+
siloBuilder.AddStateStorageBasedLogConsistencyProvider("StateStorage");
|
|
283
|
+
siloBuilder.AddCustomStorageBasedLogConsistencyProvider("CustomStorage");
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Raising Multiple Events
|
|
287
|
+
|
|
288
|
+
```csharp
|
|
289
|
+
// Individual raises — two storage accesses, risk of partial write
|
|
290
|
+
RaiseEvent(e1);
|
|
291
|
+
RaiseEvent(e2);
|
|
292
|
+
await ConfirmEvents();
|
|
293
|
+
|
|
294
|
+
// Atomic batch — written atomically
|
|
295
|
+
RaiseEvents(new[] { e1, e2 });
|
|
296
|
+
await ConfirmEvents();
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Retrieving Events
|
|
300
|
+
|
|
301
|
+
```csharp
|
|
302
|
+
// Not all providers support this (throws NotSupportedException if unavailable)
|
|
303
|
+
IReadOnlyList<AccountEvent> events = await RetrieveConfirmedEvents(0, Version);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Immediate vs Delayed Confirmation
|
|
307
|
+
|
|
308
|
+
**Immediate**: call `ConfirmEvents()` after every `RaiseEvent`. Grain unavailable during storage issues. Safe, consistent.
|
|
309
|
+
|
|
310
|
+
**Delayed**: raise events without confirming. Higher throughput but eventual consistency.
|
|
311
|
+
|
|
312
|
+
```csharp
|
|
313
|
+
// Delayed — access tentative state
|
|
314
|
+
IEnumerable<AccountEvent> UnconfirmedEvents { get; }
|
|
315
|
+
AccountState TentativeState { get; } // State + all unconfirmed events applied
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
`TentativeState` is a best guess — events may be canceled or reordered in multi-instance scenarios.
|
|
319
|
+
|
|
320
|
+
### Conditional Events (Conflict Resolution)
|
|
321
|
+
|
|
322
|
+
```csharp
|
|
323
|
+
// Returns false if race lost (version mismatch)
|
|
324
|
+
bool success = await RaiseConditionalEvent(new WithdrawEvent { Amount = 100 });
|
|
325
|
+
// Awaiting the task is sufficient confirmation — no need for ConfirmEvents
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Analogous to e-tag conditional updates. Use for operations that must not silently conflict (withdrawals). Unconditional events (deposits) always append.
|
|
329
|
+
|
|
330
|
+
### Notifications
|
|
331
|
+
|
|
332
|
+
```csharp
|
|
333
|
+
// Called when confirmed state changes (load, write success, remote notification)
|
|
334
|
+
protected override void OnStateChanged() { }
|
|
335
|
+
|
|
336
|
+
// Called when tentative state changes (including during RaiseEvent)
|
|
337
|
+
protected override void OnTentativeStateChanged() { }
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Explicit Synchronization
|
|
341
|
+
|
|
342
|
+
```csharp
|
|
343
|
+
await RefreshNow(); // confirms all unconfirmed events + loads latest from storage
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Diagnostics
|
|
347
|
+
|
|
348
|
+
```csharp
|
|
349
|
+
EnableStatsCollection();
|
|
350
|
+
LogConsistencyStatistics stats = GetStats();
|
|
351
|
+
DisableStatsCollection();
|
|
352
|
+
|
|
353
|
+
// Connection error monitoring
|
|
354
|
+
protected override void OnConnectionIssue(ConnectionIssue issue) { }
|
|
355
|
+
protected override void OnConnectionIssueResolved(ConnectionIssue issue) { }
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Log Consistency Providers
|
|
359
|
+
|
|
360
|
+
| Provider | Storage | RetrieveConfirmedEvents | Use Case |
|
|
361
|
+
|---|---|---|---|
|
|
362
|
+
| `StateStorage` | State snapshot via standard storage provider | No (events not persisted) | Production — snapshot-based |
|
|
363
|
+
| `LogStorage` | Complete event list as single object | Yes (all events in memory) | Testing only — full read/write each time |
|
|
364
|
+
| `CustomStorage` | Grain implements `ICustomStorageInterface<S,E>` | No (you control storage) | Production — custom backends |
|
|
365
|
+
|
|
366
|
+
```csharp
|
|
367
|
+
// Register providers
|
|
368
|
+
siloBuilder.AddStateStorageBasedLogConsistencyProvider("StateStorage");
|
|
369
|
+
siloBuilder.AddLogStorageBasedLogConsistencyProvider("LogStorage");
|
|
370
|
+
siloBuilder.AddCustomStorageBasedLogConsistencyProvider("CustomStorage");
|
|
371
|
+
|
|
372
|
+
// Attribute on grain
|
|
373
|
+
[StorageProvider(ProviderName = "OrleansStorage")]
|
|
374
|
+
[LogConsistencyProvider(ProviderName = "StateStorage")]
|
|
375
|
+
public class MyGrain : JournaledGrain<MyState, MyEvent> { }
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### CustomStorage Interface
|
|
379
|
+
|
|
380
|
+
```csharp
|
|
381
|
+
public interface ICustomStorageInterface<StateType, EventType>
|
|
382
|
+
{
|
|
383
|
+
Task<KeyValuePair<int, StateType>> ReadStateFromStorage();
|
|
384
|
+
// Return false on version mismatch (e-tag semantics)
|
|
385
|
+
Task<bool> ApplyUpdatesToStorage(IReadOnlyList<EventType> updates, int expectedVersion);
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Replicated Instances
|
|
390
|
+
|
|
391
|
+
Multi-cluster event sourcing guarantees:
|
|
392
|
+
- Same version number = same state across instances
|
|
393
|
+
- Racing events resolved into one agreed sequence
|
|
394
|
+
- Notifications propagated after events raised
|
|
395
|
+
|
|
396
|
+
## ACID Transactions
|
|
397
|
+
|
|
398
|
+
```mermaid
|
|
399
|
+
flowchart LR
|
|
400
|
+
C["Client / Grain"] -->|"RunTransaction"| T["TransactionClient"]
|
|
401
|
+
T -->|"Create tx context"| G1["Grain A<br/>[Reentrant]"]
|
|
402
|
+
T -->|"Join tx context"| G2["Grain B<br/>[Reentrant]"]
|
|
403
|
+
G1 -->|"PerformUpdate"| S1["ITransactionalState<T>"]
|
|
404
|
+
G2 -->|"PerformUpdate"| S2["ITransactionalState<T>"]
|
|
405
|
+
S1 & S2 -->|"Commit / Abort"| T
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Setup
|
|
409
|
+
|
|
410
|
+
```csharp
|
|
411
|
+
// Silo
|
|
412
|
+
siloBuilder.UseTransactions();
|
|
413
|
+
|
|
414
|
+
// Client
|
|
415
|
+
clientBuilder.UseTransactions();
|
|
416
|
+
|
|
417
|
+
// Storage
|
|
418
|
+
siloBuilder.AddAzureTableTransactionalStateStorage("TransactionStore",
|
|
419
|
+
options => options.ConfigureTableServiceClient(connectionString));
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Transaction Attributes
|
|
423
|
+
|
|
424
|
+
| Attribute | Meaning |
|
|
425
|
+
|---|---|
|
|
426
|
+
| `[Transaction(TransactionOption.Create)]` | Always starts new transaction |
|
|
427
|
+
| `[Transaction(TransactionOption.Join)]` | Must be within existing transaction |
|
|
428
|
+
| `[Transaction(TransactionOption.CreateOrJoin)]` | Joins existing or creates new |
|
|
429
|
+
| `[Transaction(TransactionOption.Suppress)]` | Not transactional |
|
|
430
|
+
| `[Transaction(TransactionOption.Supported)]` | Not transactional but passes context |
|
|
431
|
+
| `[Transaction(TransactionOption.NotAllowed)]` | Cannot be called in transaction |
|
|
432
|
+
|
|
433
|
+
### Grain Implementation
|
|
434
|
+
|
|
435
|
+
```csharp
|
|
436
|
+
// Interface
|
|
437
|
+
public interface IAccountGrain : IGrainWithStringKey
|
|
438
|
+
{
|
|
439
|
+
[Transaction(TransactionOption.Join)]
|
|
440
|
+
Task Withdraw(decimal amount);
|
|
441
|
+
|
|
442
|
+
[Transaction(TransactionOption.Join)]
|
|
443
|
+
Task Deposit(decimal amount);
|
|
444
|
+
|
|
445
|
+
[Transaction(TransactionOption.CreateOrJoin)]
|
|
446
|
+
Task<decimal> GetBalance();
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Grain — MUST be [Reentrant]
|
|
450
|
+
[Reentrant]
|
|
451
|
+
public class AccountGrain : Grain, IAccountGrain
|
|
452
|
+
{
|
|
453
|
+
private readonly ITransactionalState<AccountBalance> _balance;
|
|
454
|
+
|
|
455
|
+
public AccountGrain(
|
|
456
|
+
[TransactionalState("balance", "TransactionStore")]
|
|
457
|
+
ITransactionalState<AccountBalance> balance)
|
|
458
|
+
{
|
|
459
|
+
_balance = balance;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
public Task Withdraw(decimal amount) =>
|
|
463
|
+
_balance.PerformUpdate(state =>
|
|
464
|
+
{
|
|
465
|
+
if (state.Value < amount)
|
|
466
|
+
throw new InsufficientFundsException();
|
|
467
|
+
state.Value -= amount;
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
public Task Deposit(decimal amount) =>
|
|
471
|
+
_balance.PerformUpdate(state => state.Value += amount);
|
|
472
|
+
|
|
473
|
+
public Task<decimal> GetBalance() =>
|
|
474
|
+
_balance.PerformRead(state => state.Value);
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Client-Side Transactions
|
|
479
|
+
|
|
480
|
+
```csharp
|
|
481
|
+
var transactionClient = serviceProvider.GetRequiredService<ITransactionClient>();
|
|
482
|
+
|
|
483
|
+
await transactionClient.RunTransaction(TransactionOption.Create, async () =>
|
|
484
|
+
{
|
|
485
|
+
await fromAccount.Withdraw(100m);
|
|
486
|
+
await toAccount.Deposit(100m);
|
|
487
|
+
});
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Error Handling
|
|
491
|
+
|
|
492
|
+
- `OrleansTransactionException` wraps app exceptions
|
|
493
|
+
- `OrleansTransactionAbortedException` is retryable
|
|
494
|
+
- Unknown-state: wait for `SystemResponseTimeout` before retrying
|
|
495
|
+
- `OrleansTransactionsDisabledException` if `UseTransactions()` not called
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Communication, execution, and placement selection
|
|
2
|
+
|
|
3
|
+
## Choose Communication
|
|
4
|
+
|
|
5
|
+
| Primitive | Purpose | Choose it when | Failure/ownership model |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| Direct grain call | Typed request/response to a known logical owner | The caller needs completion, a result, or an exception | At-most-once by default; a timeout does not prove whether the target committed a side effect |
|
|
8
|
+
| `IAsyncEnumerable<T>` | Stream a response within one caller-initiated request | One caller consumes progressive results with cancellation and backpressure | Call-scoped, not pub/sub, not a durable subscription |
|
|
9
|
+
| Orleans stream | Decouple producers and consumers across dynamic, long-lived event flows | Pub/sub, multiple consumers, provider-backed queues, replay, or durable subscriptions are needed | Delivery, ordering, replay, and backpressure depend on the selected provider |
|
|
10
|
+
| Broadcast channel | Transient, low-overhead fan-out to implicit grain subscribers | All interested grains need the latest signal and occasional loss/history gaps are acceptable | Best-effort, no message storage, no replay; not a persistent stream |
|
|
11
|
+
| Observer | Push callbacks to a connected client or addressable subscriber | A UI/client needs live notifications and can resubscribe after reconnect | Ephemeral and inherently unreliable for clients; expire, unsubscribe, and delete references |
|
|
12
|
+
| One-way request | Remove the response path for a specialized lossy notification | The sender needs no result, completion, or error and measured response overhead matters | No acknowledgement and no guarantee the callee received the request |
|
|
13
|
+
| `RequestContext` | Flow small request-scoped metadata | Trace, correlation, tenant, or request metadata must follow a call chain | Not durable state; metadata does not flow back in responses |
|
|
14
|
+
| Grain call filter | Apply cross-cutting behavior around calls | Authorization, telemetry, argument/result inspection, or exception conversion spans many grains | Keep domain decisions in grains, not global filters |
|
|
15
|
+
| Grain extension | Attach a runtime/protocol capability to an addressable grain | Infrastructure needs an additional interface without changing the domain interface | Advanced runtime mechanism; avoid as ordinary domain composition |
|
|
16
|
+
|
|
17
|
+
Prefer a direct call unless decoupling is a requirement. Do not use a stream merely to avoid calling a known target. Do not use broadcast channels for commands, money movement, audit events, or anything that must be replayed. Do not use observers as a durable event bus.
|
|
18
|
+
|
|
19
|
+
When adding retries, make side effects idempotent. Default Orleans calls are at-most-once only while neither the runtime nor application retries. Retried calls can arrive more than once, and Orleans does not durably deduplicate them for the application.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Choose Execution and Scaling
|
|
23
|
+
|
|
24
|
+
| Primitive | Purpose | Decision rule |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| Standard grain | Serialize behavior for one identity | Default for stateful domain entities and digital twins |
|
|
27
|
+
| `[StatelessWorker]` | Scale fungible work locally and across silos | Use when activations are interchangeable and their local state need not agree |
|
|
28
|
+
| `[ReadOnly]` | Permit compatible reads to interleave | Use only when the method cannot mutate grain state or external invariants |
|
|
29
|
+
| `[Reentrant]` | Allow turns from other calls while the grain awaits | Use for call cycles or measured concurrency needs after auditing every invariant |
|
|
30
|
+
| `[AlwaysInterleave]` / `[MayInterleave]` | Selectively admit interleaving | Prefer narrow scheduling exceptions over making the whole grain reentrant |
|
|
31
|
+
| Placement strategy/filter | Constrain or optimize activation location | Keep the resource-optimized default unless locality, hardware, zone, compliance, or role requirements are proven |
|
|
32
|
+
| Placement hint | Suggest a target silo for activation or migration | Choose an active compatible silo and scope the request-context hint; it does not move an existing activation |
|
|
33
|
+
| Heterogeneous silo/versioning | Run different grain sets or versions during rollout | Use explicit compatibility/version selection for safe rolling deployments |
|
|
34
|
+
|
|
35
|
+
Turn-based execution is single-threaded, not magically race-free. Reentrancy allows another turn to run while the first awaits; any state observed before the `await` can be stale afterward. Avoid blocking calls, `.Result`, `.Wait()`, thread-affine work, and unbounded CPU loops on the grain scheduler.
|