@cratis/pi 0.0.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/README.md +43 -0
- package/package.json +24 -0
- package/skills/cratis-application-react-specifications/LICENSE +2 -0
- package/skills/cratis-application-react-specifications/SKILL.md +192 -0
- package/skills/cratis-application-slice-specifications/LICENSE +2 -0
- package/skills/cratis-application-slice-specifications/SKILL.md +222 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/LICENSE +2 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/SKILL.md +233 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authentication.md +140 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/authorization.md +112 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/frontend.md +102 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/local-development.md +85 -0
- package/skills/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +87 -0
- package/skills/cratis-arc-command/LICENSE +2 -0
- package/skills/cratis-arc-command/SKILL.md +286 -0
- package/skills/cratis-arc-command/references/command-result.md +111 -0
- package/skills/cratis-arc-command/references/handler-shapes.md +135 -0
- package/skills/cratis-arc-command/references/proxy-generation.md +87 -0
- package/skills/cratis-arc-command/references/read-model-injection.md +97 -0
- package/skills/cratis-arc-command-execution/LICENSE +2 -0
- package/skills/cratis-arc-command-execution/SKILL.md +148 -0
- package/skills/cratis-arc-command-validation/LICENSE +2 -0
- package/skills/cratis-arc-command-validation/SKILL.md +230 -0
- package/skills/cratis-arc-ef-core-migration/LICENSE +2 -0
- package/skills/cratis-arc-ef-core-migration/SKILL.md +231 -0
- package/skills/cratis-arc-observable-query-http/LICENSE +2 -0
- package/skills/cratis-arc-observable-query-http/SKILL.md +157 -0
- package/skills/cratis-arc-query-paging/LICENSE +2 -0
- package/skills/cratis-arc-query-paging/SKILL.md +173 -0
- package/skills/cratis-arc-react-page/LICENSE +2 -0
- package/skills/cratis-arc-react-page/SKILL.md +291 -0
- package/skills/cratis-arc-react-page/references/data-page.md +125 -0
- package/skills/cratis-arc-react-page/references/data-tables.md +136 -0
- package/skills/cratis-arc-react-page/references/dialogs.md +238 -0
- package/skills/cratis-arc-react-page/references/mvvm.md +150 -0
- package/skills/cratis-arc-react-page/references/queries-and-commands.md +200 -0
- package/skills/cratis-chronicle-cli-operations/LICENSE +2 -0
- package/skills/cratis-chronicle-cli-operations/SKILL.md +199 -0
- package/skills/cratis-chronicle-client-dotnet/LICENSE +2 -0
- package/skills/cratis-chronicle-client-dotnet/SKILL.md +433 -0
- package/skills/cratis-chronicle-client-elixir/LICENSE +2 -0
- package/skills/cratis-chronicle-client-elixir/SKILL.md +429 -0
- package/skills/cratis-chronicle-client-kotlin/LICENSE +2 -0
- package/skills/cratis-chronicle-client-kotlin/SKILL.md +498 -0
- package/skills/cratis-chronicle-client-typescript/LICENSE +2 -0
- package/skills/cratis-chronicle-client-typescript/SKILL.md +396 -0
- package/skills/cratis-chronicle-compliance/LICENSE +2 -0
- package/skills/cratis-chronicle-compliance/SKILL.md +246 -0
- package/skills/cratis-chronicle-event-constraints/LICENSE +2 -0
- package/skills/cratis-chronicle-event-constraints/SKILL.md +227 -0
- package/skills/cratis-chronicle-event-modeling/LICENSE +2 -0
- package/skills/cratis-chronicle-event-modeling/SKILL.md +157 -0
- package/skills/cratis-chronicle-event-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-event-specifications/SKILL.md +200 -0
- package/skills/cratis-chronicle-event-type-migration/LICENSE +2 -0
- package/skills/cratis-chronicle-event-type-migration/SKILL.md +235 -0
- package/skills/cratis-chronicle-mcp-inspection/LICENSE +2 -0
- package/skills/cratis-chronicle-mcp-inspection/SKILL.md +79 -0
- package/skills/cratis-chronicle-mcp-inspection/references/blocked-tools.md +12 -0
- package/skills/cratis-chronicle-mcp-inspection/references/observational-tools.md +8 -0
- package/skills/cratis-chronicle-multi-tenancy/LICENSE +2 -0
- package/skills/cratis-chronicle-multi-tenancy/SKILL.md +198 -0
- package/skills/cratis-chronicle-projection/LICENSE +2 -0
- package/skills/cratis-chronicle-projection/SKILL.md +162 -0
- package/skills/cratis-chronicle-projection/references/fluent-builder.md +126 -0
- package/skills/cratis-chronicle-projection/references/model-bound-attributes.md +118 -0
- package/skills/cratis-chronicle-reactor/LICENSE +2 -0
- package/skills/cratis-chronicle-reactor/SKILL.md +344 -0
- package/skills/cratis-chronicle-read-model/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model/SKILL.md +193 -0
- package/skills/cratis-chronicle-read-model/references/queries.md +142 -0
- package/skills/cratis-chronicle-read-model-specifications/LICENSE +2 -0
- package/skills/cratis-chronicle-read-model-specifications/SKILL.md +176 -0
- package/skills/cratis-chronicle-reducer/LICENSE +2 -0
- package/skills/cratis-chronicle-reducer/SKILL.md +173 -0
- package/skills/cratis-chronicle-web-workbench/LICENSE +2 -0
- package/skills/cratis-chronicle-web-workbench/SKILL.md +177 -0
- package/skills/cratis-cli-terminal-workbench/LICENSE +2 -0
- package/skills/cratis-cli-terminal-workbench/SKILL.md +156 -0
- package/skills/cratis-code-review/LICENSE +2 -0
- package/skills/cratis-code-review/SKILL.md +177 -0
- package/skills/cratis-components-accessibility/LICENSE +2 -0
- package/skills/cratis-components-accessibility/SKILL.md +176 -0
- package/skills/cratis-components-schema-editor/LICENSE +2 -0
- package/skills/cratis-components-schema-editor/SKILL.md +238 -0
- package/skills/cratis-components-stepper-command-dialog/LICENSE +2 -0
- package/skills/cratis-components-stepper-command-dialog/SKILL.md +209 -0
- package/skills/cratis-components-styling/LICENSE +2 -0
- package/skills/cratis-components-styling/SKILL.md +221 -0
- package/skills/cratis-components-toolbar/LICENSE +2 -0
- package/skills/cratis-components-toolbar/SKILL.md +259 -0
- package/skills/cratis-documentation-writing/LICENSE +2 -0
- package/skills/cratis-documentation-writing/SKILL.md +121 -0
- package/skills/cratis-event-model-diagram/LICENSE +2 -0
- package/skills/cratis-event-model-diagram/SKILL.md +142 -0
- package/skills/cratis-fundamentals-concept/LICENSE +2 -0
- package/skills/cratis-fundamentals-concept/SKILL.md +198 -0
- package/skills/cratis-fundamentals-type-discovery/LICENSE +2 -0
- package/skills/cratis-fundamentals-type-discovery/SKILL.md +152 -0
- package/skills/cratis-governed-release-methodology/LICENSE +2 -0
- package/skills/cratis-governed-release-methodology/SKILL.md +297 -0
- package/skills/cratis-lens-browser-extension/LICENSE +2 -0
- package/skills/cratis-lens-browser-extension/SKILL.md +239 -0
- package/skills/cratis-performance-review/LICENSE +2 -0
- package/skills/cratis-performance-review/SKILL.md +139 -0
- package/skills/cratis-screenplay-model-authoring/LICENSE +2 -0
- package/skills/cratis-screenplay-model-authoring/SKILL.md +231 -0
- package/skills/cratis-security-review/LICENSE +2 -0
- package/skills/cratis-security-review/SKILL.md +141 -0
- package/skills/cratis-specification-by-example/LICENSE +2 -0
- package/skills/cratis-specification-by-example/SKILL.md +188 -0
- package/skills/cratis-specifications-csharp/LICENSE +2 -0
- package/skills/cratis-specifications-csharp/SKILL.md +230 -0
- package/skills/cratis-specifications-csharp/references/application-scenarios.md +191 -0
- package/skills/cratis-specifications-csharp/references/csharp-patterns.md +142 -0
- package/skills/cratis-specifications-csharp/references/integration-specs.md +126 -0
- package/skills/cratis-specifications-typescript/LICENSE +2 -0
- package/skills/cratis-specifications-typescript/SKILL.md +202 -0
- package/skills/cratis-specifications-typescript/references/typescript-patterns.md +168 -0
- package/skills/cratis-stage-rendering-and-sandbox/LICENSE +2 -0
- package/skills/cratis-stage-rendering-and-sandbox/SKILL.md +230 -0
- package/skills/cratis-studio-mcp-safety-guidance/LICENSE +2 -0
- package/skills/cratis-studio-mcp-safety-guidance/SKILL.md +69 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +12 -0
- package/skills/cratis-studio-mcp-safety-guidance/references/observational-tools.md +8 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cratis-chronicle-client-kotlin
|
|
3
|
+
description: Talk to a Chronicle server from a Kotlin or Java application with the io.cratis:chronicle client - connection strings, ChronicleClient and the Spring Boot starter, @EventType classes, suspending append, reactors and reducers dispatched by first-parameter type, model-bound read models, classpath artifact discovery, and the blocking API Java uses. Use when building a JVM application that appends to or observes a Chronicle event store. Do not use for the .NET, TypeScript, or Elixir clients, and do not use for Chronicle kernel or Arc work.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# The Chronicle client for Kotlin and Java
|
|
8
|
+
|
|
9
|
+
`io.cratis:chronicle` is a **standalone client SDK**. Your application is an
|
|
10
|
+
ordinary JVM application that happens to talk to a Chronicle server over gRPC.
|
|
11
|
+
There is no framework to inherit from and no application architecture imposed:
|
|
12
|
+
you construct a client, ask it for an event store, and append or observe.
|
|
13
|
+
|
|
14
|
+
## Verified product sources
|
|
15
|
+
|
|
16
|
+
This skill is verified against this exact source:
|
|
17
|
+
|
|
18
|
+
| Artifact | Version | Verified from |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| `io.cratis:chronicle` | `4.0.0` | `Chronicle.Kotlin` at tag `v4.0.0` (commit `63ff629`) |
|
|
21
|
+
| `io.cratis:chronicle-spring-boot-starter` | `4.0.0` | same tag, `Integrations/SpringBoot` |
|
|
22
|
+
| `io.cratis:chronicle-testing` | `4.0.0` | same tag, `Testing` |
|
|
23
|
+
| `io.cratis:chronicle-contracts` | `16.44.1` | `Source/build.gradle.kts:11` |
|
|
24
|
+
|
|
25
|
+
All three artifacts are published to Maven Central under `io.cratis`
|
|
26
|
+
(`Source/build.gradle.kts:54`, `Testing/build.gradle.kts:32`,
|
|
27
|
+
`Integrations/SpringBoot/build.gradle.kts:48`). The JVM toolchain is **17**
|
|
28
|
+
(`Source/build.gradle.kts:43`).
|
|
29
|
+
|
|
30
|
+
> **Do not copy a version number out of the repository's README or docs.** At
|
|
31
|
+
> `v4.0.0` the README still says `2.1.1` (`README.md:186`) and the documentation
|
|
32
|
+
> says `2.1.2` (`Documentation/get-started/index.md:24`). The version comes from
|
|
33
|
+
> a Gradle property injected at release time — `Source/build.gradle.kts:8` reads
|
|
34
|
+
> `providers.gradleProperty("version")` and defaults to `0.0.0-SNAPSHOT` — so
|
|
35
|
+
> the checked-in source never carries the real number. Take it from Maven
|
|
36
|
+
> Central.
|
|
37
|
+
|
|
38
|
+
> **The client and the kernel version independently.** Client `4.0.0` is built
|
|
39
|
+
> against `chronicle-contracts` `16.44.1`; the Chronicle server has since moved
|
|
40
|
+
> to a `17.x` line. Confirm the client/server pair you intend to run is
|
|
41
|
+
> supported before relying on it — do not infer compatibility from the fact that
|
|
42
|
+
> both are "latest".
|
|
43
|
+
|
|
44
|
+
## Two ways in — pick one
|
|
45
|
+
|
|
46
|
+
| You are building | Use | Artifact |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| A plain Kotlin/Java application, a CLI, a worker | `ChronicleClient` directly | `io.cratis:chronicle` |
|
|
49
|
+
| A Spring Boot application | the starter, and inject the beans | `io.cratis:chronicle-spring-boot-starter` |
|
|
50
|
+
|
|
51
|
+
The starter is a wrapper over the same client. Everything below about event
|
|
52
|
+
types, appending, and observers is identical either way; only the wiring differs.
|
|
53
|
+
|
|
54
|
+
## Connecting
|
|
55
|
+
|
|
56
|
+
### Plain Kotlin
|
|
57
|
+
|
|
58
|
+
```kotlin
|
|
59
|
+
import io.cratis.chronicle.ChronicleClient
|
|
60
|
+
import io.cratis.chronicle.ChronicleOptions
|
|
61
|
+
import kotlinx.coroutines.runBlocking
|
|
62
|
+
|
|
63
|
+
fun main() = runBlocking {
|
|
64
|
+
val options = ChronicleOptions.development()
|
|
65
|
+
val client = ChronicleClient(options)
|
|
66
|
+
try {
|
|
67
|
+
val store = client.getEventStore("<EventStoreName>")
|
|
68
|
+
// ... use store ...
|
|
69
|
+
} finally {
|
|
70
|
+
client.dispose()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`IChronicleClient` is small and complete —
|
|
76
|
+
`Source/src/main/kotlin/io/cratis/chronicle/IChronicleClient.kt`:
|
|
77
|
+
|
|
78
|
+
| Member | Line | Note |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `fun getEventStore(name: String, namespace: String = EventStoreNamespaceName.default.value): EventStore` | `:17-20` | **not** suspending; returns the concrete `EventStore` |
|
|
81
|
+
| `suspend fun getEventStores(): List<String>` | `:27` | asks the kernel |
|
|
82
|
+
| `fun evictEventStores()` | `:36` | drops the local cache, keeps the client |
|
|
83
|
+
| `fun dispose()` | `:39` | `IChronicleClient : AutoCloseable`, `close()` delegates to it (`:41`) |
|
|
84
|
+
|
|
85
|
+
**The client connects in its constructor.** `ChronicleClient.kt:13` is
|
|
86
|
+
`ChronicleConnection(options.connectionString).also { it.connect() }` — there is
|
|
87
|
+
no separate `connect()` step to call, and constructing the client is the
|
|
88
|
+
connecting act. Event stores are cached per `"$name/$namespace"`
|
|
89
|
+
(`ChronicleClient.kt:22`), so repeated `getEventStore` calls return the same
|
|
90
|
+
instance.
|
|
91
|
+
|
|
92
|
+
The default namespace is the literal `"Default"` —
|
|
93
|
+
`EventStoreNamespaceName.kt:13`.
|
|
94
|
+
|
|
95
|
+
### Options
|
|
96
|
+
|
|
97
|
+
`ChronicleOptions` is a `data class` with `@JvmOverloads`
|
|
98
|
+
(`ChronicleOptions.kt:35`):
|
|
99
|
+
|
|
100
|
+
| Property | Default | Line |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| `connectionString` | required | `:36` |
|
|
103
|
+
| `programIdentifier` | `"Unknown"` | `:37` |
|
|
104
|
+
| `defaultSinkTypeId` | `CHRONICLE_SINK_TYPE` env var, else `WellKnownSinkTypes.MONGODB` | `:38` |
|
|
105
|
+
| `autoDiscoverAndRegister` | `true` | `:39` |
|
|
106
|
+
| `artifacts` | `ClientArtifacts.default` (scans the classpath) | `:40` |
|
|
107
|
+
| `artifactActivator` | `ArtifactActivator` | `:41` |
|
|
108
|
+
| `openTelemetry` | `null` (uses the globally registered one) | `:42` |
|
|
109
|
+
|
|
110
|
+
Two `@JvmStatic` factories: `ChronicleOptions.fromConnectionString(String)`
|
|
111
|
+
(`:69`) and `ChronicleOptions.development()` (`:78`). Two instance helpers:
|
|
112
|
+
`withoutAutoRegistration()` (`:48`) and `withArtifactsFrom(vararg packages)`
|
|
113
|
+
(`:58`) — the second is worth using in any large application, because the
|
|
114
|
+
default scan walks the whole classpath.
|
|
115
|
+
|
|
116
|
+
### The connection string
|
|
117
|
+
|
|
118
|
+
`ChronicleConnectionString.kt` documents the grammar at `:14-18`:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
chronicle://<host>[:<port>][,<host>[:<port>]...][?<options>]
|
|
122
|
+
chronicle://<username>:<password>@<host>[:<port>][,...][?<options>]
|
|
123
|
+
chronicle+srv://<host>[:<port>][?<options>]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Default port is `35000` (`:39`). Recognized query keys, lowercased at parse time
|
|
127
|
+
(`:111-115`): `disabletls`, `skiptlsvalidation`, `apikey`, `loadbalancer`,
|
|
128
|
+
`srvnameserver`. `ChronicleConnectionString.parse(String)` is on the companion
|
|
129
|
+
(`:63`), and `ChronicleConnectionString.DEVELOPMENT` (`:51`) points at
|
|
130
|
+
`localhost:35000` with the `chronicle-dev-client` / `chronicle-dev-secret`
|
|
131
|
+
credentials (`:42-43`).
|
|
132
|
+
|
|
133
|
+
> **TLS is on, certificate validation is off, by default.** `disableTls = false`
|
|
134
|
+
> but `skipTlsValidation = true` — `ChronicleConnectionString.kt:27-28`, and the
|
|
135
|
+
> credential selection at `:180-181` installs an `InsecureTrustManager` in that
|
|
136
|
+
> case. That default exists because a development kernel serves a self-signed
|
|
137
|
+
> certificate. **A production connection string must carry
|
|
138
|
+
> `?skipTlsValidation=false`**, which is the only way to get chain validation
|
|
139
|
+
> against the platform trust store (`:174-176`).
|
|
140
|
+
|
|
141
|
+
### Spring Boot
|
|
142
|
+
|
|
143
|
+
The starter registers exactly two auto-configurations —
|
|
144
|
+
`Integrations/SpringBoot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`:
|
|
145
|
+
|
|
146
|
+
- `io.cratis.chronicle.spring.ChronicleAutoConfiguration`
|
|
147
|
+
- `io.cratis.chronicle.spring.ChronicleWebAutoConfiguration`
|
|
148
|
+
|
|
149
|
+
Configuration binds under the prefix `cratis.chronicle` —
|
|
150
|
+
`ChronicleProperties.kt:42-43`. The minimum real configuration is one key, as in
|
|
151
|
+
the shipped sample (`Samples/Kotlin/SpringBoot/src/main/resources/application.yml`):
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
cratis:
|
|
155
|
+
chronicle:
|
|
156
|
+
event-store: <EventStoreName>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Everything else has a default (`ChronicleProperties.kt:44-55`):
|
|
160
|
+
|
|
161
|
+
| Key under `cratis.chronicle` | Default | Line |
|
|
162
|
+
| --- | --- | --- |
|
|
163
|
+
| `connection-string` | the development connection string | `:44` |
|
|
164
|
+
| `event-store` | `"Default"` | `:45` |
|
|
165
|
+
| `namespace` | `"Default"` | `:46` |
|
|
166
|
+
| `auto-discover-and-register` | `true` | `:47` |
|
|
167
|
+
| `artifact-packages` | empty — falls back to Spring's auto-configuration packages | `:48` |
|
|
168
|
+
| `default-sink-type-id` | `null` | `:49` |
|
|
169
|
+
| `program-identifier` | `null` — falls back to `spring.application.name` | `:50` |
|
|
170
|
+
| `registration-timeout` | `PT30S` | `:51` |
|
|
171
|
+
| `namespace-resolution.strategy` | `FIXED` | `:65` |
|
|
172
|
+
| `namespace-resolution.http-header` | `"x-cratis-tenant-id"` | `:66` |
|
|
173
|
+
| `namespace-resolution.claim` | `"tenant_id"` | `:67` |
|
|
174
|
+
|
|
175
|
+
`NamespaceResolution.Strategy` is `FIXED`, `HTTP_HEADER`, `SUBDOMAIN`,
|
|
176
|
+
`AUTHENTICATION` (`ChronicleProperties.kt:70-82`).
|
|
177
|
+
|
|
178
|
+
The beans you inject are `IChronicleClient`, `IEventStore`, and the convenience
|
|
179
|
+
facade `Chronicle` (`ChronicleAutoConfiguration.kt:93-114`). `Chronicle` wraps
|
|
180
|
+
the suspending API in blocking calls and takes `Class<T>` rather than
|
|
181
|
+
`KClass<T>`, so Java can use it unchanged —
|
|
182
|
+
`Integrations/SpringBoot/src/main/kotlin/io/cratis/chronicle/spring/Chronicle.kt:47`,
|
|
183
|
+
`append` at `:57`, `appendMany` at `:69`.
|
|
184
|
+
|
|
185
|
+
`ChronicleWebAutoConfiguration` is **servlet-only**
|
|
186
|
+
(`@ConditionalOnWebApplication(SERVLET)`, `ChronicleWebAutoConfiguration.kt:29`).
|
|
187
|
+
A WebFlux application gets no per-request namespace, identity, causation, or
|
|
188
|
+
unit-of-work filter.
|
|
189
|
+
|
|
190
|
+
**Artifacts are Spring beans.** `SpringArtifactActivator` resolves a discovered
|
|
191
|
+
artifact from the container when it is uniquely defined and otherwise builds it
|
|
192
|
+
through `autowireCapableBeanFactory.createBean`
|
|
193
|
+
(`SpringArtifactActivator.kt:28-43`), so a reactor takes constructor
|
|
194
|
+
dependencies exactly like a `@Service` would.
|
|
195
|
+
|
|
196
|
+
## Defining event types
|
|
197
|
+
|
|
198
|
+
```kotlin
|
|
199
|
+
import io.cratis.chronicle.events.EventType
|
|
200
|
+
|
|
201
|
+
/** <What happened, in the past tense.> */
|
|
202
|
+
@EventType
|
|
203
|
+
data class <EventName>(
|
|
204
|
+
val <property>: <Type> = <default>
|
|
205
|
+
)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`io.cratis.chronicle.events.EventType` — `events/EventType.kt:15-19`:
|
|
209
|
+
|
|
210
|
+
```kotlin
|
|
211
|
+
annotation class EventType(
|
|
212
|
+
val id: String = "",
|
|
213
|
+
val generation: Int = 1,
|
|
214
|
+
val tombstone: Boolean = false
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**The id defaults to the class's *simple* name, not its fully qualified name** —
|
|
219
|
+
`events/EventTypesService.kt:67` resolves `ann.id.ifEmpty { cls.simpleName!! }`.
|
|
220
|
+
Two event classes with the same simple name in different packages therefore
|
|
221
|
+
collide on the wire. Give one an explicit `id`.
|
|
222
|
+
|
|
223
|
+
The house shape is a Kotlin `data class` with defaulted properties
|
|
224
|
+
(`Samples/Kotlin/SpringBoot/.../Events.kt:9-14`) or a Java `record`
|
|
225
|
+
(`Samples/Java/SpringBoot/.../EmployeeHired.java:9-10`). Property-level
|
|
226
|
+
annotations that travel with the event: `@io.cratis.chronicle.keys.Key`,
|
|
227
|
+
`@io.cratis.chronicle.compliance.Pii`, `@io.cratis.chronicle.Subject`,
|
|
228
|
+
`@io.cratis.chronicle.schemas.JsonSchemaType`.
|
|
229
|
+
|
|
230
|
+
Evolving a schema is `IEventTypeMigration<TTarget, TSource>` with `upcast` and
|
|
231
|
+
`downcast` — `events/migrations/IEventTypeMigration.kt:18-37`. Migrations
|
|
232
|
+
register in the same call as event types
|
|
233
|
+
(`artifacts/ArtifactRegistrations.kt:63`).
|
|
234
|
+
|
|
235
|
+
## Appending
|
|
236
|
+
|
|
237
|
+
Everything that touches the kernel suspends. `IEventSequence.kt`:
|
|
238
|
+
|
|
239
|
+
```kotlin
|
|
240
|
+
suspend fun append(eventSourceId: String, event: Any, options: AppendOptions? = null): AppendResult // :37
|
|
241
|
+
suspend fun appendMany(eventSourceId: String, events: List<Any>, options: AppendOptions? = null): List<AppendResult> // :47
|
|
242
|
+
suspend fun appendMany( // :65
|
|
243
|
+
events: List<EventForEventSourceId>,
|
|
244
|
+
concurrencyScopes: Map<String, ConcurrencyScope> = emptyMap(),
|
|
245
|
+
correlationId: UUID? = null
|
|
246
|
+
): List<AppendResult>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```kotlin
|
|
250
|
+
val result = store.eventLog.append("<event-source-id>", <EventName>(<value>))
|
|
251
|
+
if (!result.isSuccess) {
|
|
252
|
+
// result.constraintViolations, result.concurrencyViolation, result.errors
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
- **The event source id is a plain `String`** at every call site
|
|
257
|
+
(`IEventSequence.kt:37`). Typed alternatives taking `ConceptAs<String>` exist
|
|
258
|
+
as extension functions in `io.cratis.chronicle.concepts`
|
|
259
|
+
(`concepts/EventSourceIdConcepts.kt`).
|
|
260
|
+
- `AppendResult` carries `sequenceNumber`, `constraintViolations`, `errors`,
|
|
261
|
+
`isSuccess`, `concurrencyViolation` — `eventSequences/AppendResult.kt:18-33`.
|
|
262
|
+
It also carries `sequenceNumberValue: Long` (`:32`) purely so Java can read
|
|
263
|
+
the number, because `EventSequenceNumber` is a `@JvmInline value class` whose
|
|
264
|
+
getter Java cannot name.
|
|
265
|
+
- The three-argument `appendMany(List<EventForEventSourceId>, ...)` is the only
|
|
266
|
+
overload that commits atomically **across** event sources.
|
|
267
|
+
- Reach for the second overload's `EventForEventSourceId` when events in one
|
|
268
|
+
batch must go to different streams — the single-source overloads cannot
|
|
269
|
+
express that.
|
|
270
|
+
|
|
271
|
+
`AppendOptions` (`eventSequences/AppendOptions.kt:41-51`, `@JvmOverloads`)
|
|
272
|
+
carries `correlationId`, `concurrencyScope`, `eventSourceType`,
|
|
273
|
+
`eventStreamType`, `eventStreamId`, `subject`, `tags`, `occurred`, `causation`.
|
|
274
|
+
|
|
275
|
+
A unit of work spans several appends:
|
|
276
|
+
|
|
277
|
+
```kotlin
|
|
278
|
+
val unitOfWork = store.unitOfWorkManager.begin()
|
|
279
|
+
store.eventLog.transactional.append("<id>", <EventName>(<value>))
|
|
280
|
+
store.eventLog.transactional.appendMany("<id>", listOf(<OtherEvent>()))
|
|
281
|
+
unitOfWork.commit()
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`transactional` appends return `Unit`, not an `AppendResult`; the results are on
|
|
285
|
+
the unit of work (`transactions/IUnitOfWork.kt:23-101`).
|
|
286
|
+
|
|
287
|
+
## Observing
|
|
288
|
+
|
|
289
|
+
### Handlers are found by their first parameter's type
|
|
290
|
+
|
|
291
|
+
This is the single most important convention in this client, and it is not the
|
|
292
|
+
method name. `observation/EventHandlerMethod.kt:61-74` reads a function as a
|
|
293
|
+
handler only when **parameter index 1 — the first real parameter — is a class
|
|
294
|
+
annotated with `@EventType`**. The method name is irrelevant. A handler may be
|
|
295
|
+
`suspend` or plain; both are invoked through `callSuspend`.
|
|
296
|
+
|
|
297
|
+
```kotlin
|
|
298
|
+
import io.cratis.chronicle.events.EventContext
|
|
299
|
+
import io.cratis.chronicle.observation.Reactor
|
|
300
|
+
|
|
301
|
+
@Reactor
|
|
302
|
+
class <ReactorName>(private val <dependency>: <Dependency>) {
|
|
303
|
+
fun <anyMethodName>(event: <EventName>, context: EventContext): <SideEffectEvent> {
|
|
304
|
+
<dependency>.<doSomething>(event.<property>)
|
|
305
|
+
return <SideEffectEvent>(<property> = context.eventSourceId)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
That shape is the shipped sample verbatim
|
|
311
|
+
(`Samples/Kotlin/SpringBoot/.../WelcomePackageReactor.kt:16-22`).
|
|
312
|
+
|
|
313
|
+
**A returned event is appended as a side effect.** `null`, `Unit`, and any value
|
|
314
|
+
whose type is not annotated `@EventType` are ignored; a single event, an
|
|
315
|
+
`EventForEventSourceId`, or a `List` mixing both is appended, defaulting to the
|
|
316
|
+
triggering event source — `observation/ReactorSideEffects.kt:26-39`, `:48-59`.
|
|
317
|
+
This is how a reactor appends without ever touching the event log.
|
|
318
|
+
|
|
319
|
+
`EventContext` is a data class with `sequenceNumber: Long`, `eventSourceId`,
|
|
320
|
+
`eventType`, `occurred`, `correlationId`, `causedBy`, `eventSourceType`,
|
|
321
|
+
`eventStreamType`, `eventStreamId`, `eventStore`, `namespace`, `causation`,
|
|
322
|
+
`tags`, `hash`, `observationState` — `events/EventContext.kt:31-47`.
|
|
323
|
+
|
|
324
|
+
### The annotations
|
|
325
|
+
|
|
326
|
+
| Annotation | Arguments | Source |
|
|
327
|
+
| --- | --- | --- |
|
|
328
|
+
| `@Reactor` | `id = ""`, `eventSequence = ""` | `observation/Reactor.kt:15` |
|
|
329
|
+
| `@Reducer` | `id = ""`, `eventSequence = ""`, `isActive = true` | `observation/Reducer.kt:17` |
|
|
330
|
+
| `@ReadModel` | `id = ""`, `displayName = ""` | `readModels/ReadModel.kt:19` |
|
|
331
|
+
| `@Projection` | `id = ""`, `eventSequence = ""` — optional | `projections/Projection.kt:22` |
|
|
332
|
+
| `@Constraint` | `id = ""` | `constraints/Constraint.kt:13` |
|
|
333
|
+
| `@Seeder` | none | `seeding/Seeder.kt:11` |
|
|
334
|
+
|
|
335
|
+
Handler-level: `@Replay`, `@OnceOnly`. Observer filtering: `@Tag`/`@Tags`,
|
|
336
|
+
`@FilterEventsByTag`/`@FilterEventsByTags`, `@EventSequence`,
|
|
337
|
+
`@EventSourceType`, `@EventStreamType`.
|
|
338
|
+
|
|
339
|
+
A reducer's handler may be `(event)`, `(event, state)`, or
|
|
340
|
+
`(event, state, context)` — the three shapes accepted by the dispatcher and by
|
|
341
|
+
`ReadModelScenario` (`Testing/.../ReadModelScenario.kt:146-150`). **Reducers run
|
|
342
|
+
client-side**: the kernel streams events and the handler is invoked in your
|
|
343
|
+
process (`observation/ReducersService.kt:119-140`).
|
|
344
|
+
|
|
345
|
+
### Read models and model-bound projections
|
|
346
|
+
|
|
347
|
+
Read models are queried through `store.readModels`
|
|
348
|
+
(`readModels/IReadModelsService.kt`): `getInstanceByKey(readModelClass, key): T?`
|
|
349
|
+
(`:11`), `getInstances(readModelClass, eventCount)` (`:20`),
|
|
350
|
+
`getSnapshotsById` (`:29`), and `watch(readModelClass): Flow<ReadModelChangeset<T>>`
|
|
351
|
+
(`:37`, which is **not** suspending — it hands back a `Flow`).
|
|
352
|
+
|
|
353
|
+
A model-bound projection puts the projection on the read model with `@FromEvent`
|
|
354
|
+
(`projections/FromEvent.kt:17`) and `@SetFrom`
|
|
355
|
+
(`projections/SetFrom.kt:22`); the full family also includes `SetValue`,
|
|
356
|
+
`SetFromContext`, `AddFrom`, `SubtractFrom`, `Increment`, `Decrement`, `Count`,
|
|
357
|
+
`Join`, `RemovedWith`, `RemovedWithJoin`, `ChildrenFrom`, `ClearWith`,
|
|
358
|
+
`FromAll`, `FromEvery`, `Nested`, `NoAutoMap`, `NotRewindable`. The declarative
|
|
359
|
+
alternative is a class implementing `IProjectionFor<TReadModel>`.
|
|
360
|
+
|
|
361
|
+
## Discovery and registration
|
|
362
|
+
|
|
363
|
+
`ClientArtifacts` scans the classpath with ClassGraph
|
|
364
|
+
(`artifacts/ClientArtifacts.kt:44-48`), and `ClientArtifacts.default` is a
|
|
365
|
+
process-wide lazy singleton (`:156`). What it looks for (`:66-98`):
|
|
366
|
+
|
|
367
|
+
| Kind | Rule |
|
|
368
|
+
| --- | --- |
|
|
369
|
+
| event types | `@EventType` |
|
|
370
|
+
| event type migrations | implements `IEventTypeMigration` |
|
|
371
|
+
| read models | `@ReadModel` |
|
|
372
|
+
| declarative projections | implements `IProjectionFor` |
|
|
373
|
+
| model-bound projections | `@FromEvent` **and** the synthetic `FromEvent$Container` |
|
|
374
|
+
| reactors | `@Reactor` |
|
|
375
|
+
| reducers | `@Reducer` |
|
|
376
|
+
| constraints | implements `IConstraint` |
|
|
377
|
+
| seeders | implements `ICanSeedEvents` |
|
|
378
|
+
| webhooks | implements `IWebhookDefiner` |
|
|
379
|
+
| captures | implements `ICapture` |
|
|
380
|
+
|
|
381
|
+
> The `FromEvent$Container` entry is not incidental: Kotlin's `@Repeatable`
|
|
382
|
+
> replaces repeated annotations with a synthetic container, so a class carrying
|
|
383
|
+
> more than one `@FromEvent` is **not** annotated with `@FromEvent` at runtime.
|
|
384
|
+
> A scan that looks only for the annotation silently misses every multi-event
|
|
385
|
+
> projection.
|
|
386
|
+
|
|
387
|
+
Registration order is fixed and matters
|
|
388
|
+
(`artifacts/ArtifactRegistrations.kt:59-91`): event types and migrations →
|
|
389
|
+
unowned read models → constraints → model-bound constraints → projections →
|
|
390
|
+
webhooks → reactors → reducers → captures → seeders. Reactors and reducers are
|
|
391
|
+
started only on the first pass (`:78-82`).
|
|
392
|
+
|
|
393
|
+
**Registration re-runs on every reconnect.** `EventStore.kt:226-241` launches a
|
|
394
|
+
coroutine on `Dispatchers.IO` collecting the connection lifecycle and
|
|
395
|
+
re-registers each time. The connection id rotates on every disconnect because
|
|
396
|
+
the kernel keys observer subscriptions by it
|
|
397
|
+
(`connection/ConnectionLifecycle.kt:24-32`), so observers must re-register — and
|
|
398
|
+
they do.
|
|
399
|
+
|
|
400
|
+
`store.awaitRegistration()` (`IEventStore.kt:92`) waits for the first pass.
|
|
401
|
+
`store.registerAll()` (`:83`) runs it by hand when
|
|
402
|
+
`autoDiscoverAndRegister = false`.
|
|
403
|
+
|
|
404
|
+
## Java
|
|
405
|
+
|
|
406
|
+
Java is a first-class target here, not an afterthought: there is a compile-only
|
|
407
|
+
Java conformance suite under `Source/src/test/java` whose whole point is stated
|
|
408
|
+
in `conformance/JavaConformance.java:71-74` — *"It is never run — compiling it is
|
|
409
|
+
the assertion"*.
|
|
410
|
+
|
|
411
|
+
**Start Java code at `BlockingChronicleClient`**, not at the raw `ChronicleClient`
|
|
412
|
+
plus static bridges. Verbatim from the compile-checked fixture
|
|
413
|
+
(`Source/src/test/java/io/cratis/chronicle/java/JavaClientFlowUsage.java:29-34`):
|
|
414
|
+
|
|
415
|
+
```java
|
|
416
|
+
var client = BlockingChronicleClient.connect(ChronicleOptions.development());
|
|
417
|
+
var eventStore = client.getEventStore("<EventStoreName>");
|
|
418
|
+
|
|
419
|
+
eventStore.getEventLog().append("<event-source-id>", new <EventName>("<value>"));
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
`BlockingChronicleClient` is `AutoCloseable`, so `try (var client = ...)` works
|
|
423
|
+
(`java/BlockingChronicleClient.kt:35`, `connect` at `:74-76`). The blocking
|
|
424
|
+
surface continues through `BlockingEventStore`, `BlockingEventSequence`,
|
|
425
|
+
`BlockingReadModels`, `BlockingReactors`, `BlockingReducers`,
|
|
426
|
+
`BlockingUnitOfWork`, and `AppendOptionsBuilder`.
|
|
427
|
+
|
|
428
|
+
> The repository's own README shows the **older** low-level route —
|
|
429
|
+
> `new ChronicleClient(...)` plus `EventStoreJavaBridge` / `EventLogJavaBridge`
|
|
430
|
+
> (`README.md:225-244`). Both APIs are real, but the reference documentation and
|
|
431
|
+
> the compile-checked fixture both start at `BlockingChronicleClient`. Write new
|
|
432
|
+
> Java against that; reach for the `*JavaBridge` statics only for a corner the
|
|
433
|
+
> blocking client does not wrap.
|
|
434
|
+
|
|
435
|
+
In Spring Boot, Java injects the `Chronicle` bean instead — it already takes
|
|
436
|
+
`Class<T>` and returns plain values.
|
|
437
|
+
|
|
438
|
+
## Connection lifecycle
|
|
439
|
+
|
|
440
|
+
- **Keepalive is two-way.** The kernel pushes a keep-alive down the `Connect`
|
|
441
|
+
stream and the client answers with a separate unary `connectionKeepAlive` RPC
|
|
442
|
+
(`connection/ConnectionManager.kt:30`). A watchdog checks every
|
|
443
|
+
`WATCHDOG_INTERVAL_MS = 1_000L` and treats a gap longer than
|
|
444
|
+
`KEEP_ALIVE_TIMEOUT_MS = 5_000L` as a lost connection (`:126-128`, `:164`,
|
|
445
|
+
`:171`). **Silence, not an error, is how the connection dies** — nothing throws.
|
|
446
|
+
- Reconnect is an infinite loop with jittered exponential backoff, base 1s,
|
|
447
|
+
capped at 30s, re-resolving DNS/SRV on every attempt
|
|
448
|
+
(`ConnectionManager.kt:75-101`, `:155-158`).
|
|
449
|
+
- The client identifies itself to the kernel as `"Kotlin"`
|
|
450
|
+
(`ConnectionManager.kt:161`).
|
|
451
|
+
- `dispose()` cancels the connection manager, shuts the channel down with a
|
|
452
|
+
5-second `awaitTermination`, then `shutdownNow`
|
|
453
|
+
(`connection/ChronicleConnection.kt:116-125`).
|
|
454
|
+
- **Your process must stay alive** for reactors and reducers to keep receiving —
|
|
455
|
+
observation is a live gRPC stream, and each observer runs on its own
|
|
456
|
+
`CoroutineScope(Dispatchers.IO)` (`observation/ReactorsService.kt:57`,
|
|
457
|
+
`observation/ReducersService.kt:59`).
|
|
458
|
+
|
|
459
|
+
## Testing
|
|
460
|
+
|
|
461
|
+
`io.cratis:chronicle-testing` runs in-process with no kernel and no Docker:
|
|
462
|
+
`EventScenario` (`Testing/.../EventScenario.kt:35`) and
|
|
463
|
+
`ReadModelScenario<TReadModel>` (`Testing/.../ReadModelScenario.kt:47`), which
|
|
464
|
+
folds a reducer through the same handler-shape rules as production
|
|
465
|
+
(`ReadModelScenario.kt:139-150`). Its scope is small — appends and reducer folds
|
|
466
|
+
only; there is no in-process reactor, projection, or constraint scenario
|
|
467
|
+
(`Testing/api/Testing.api` is 56 lines).
|
|
468
|
+
|
|
469
|
+
## Common pitfalls
|
|
470
|
+
|
|
471
|
+
| Pitfall | Why it bites |
|
|
472
|
+
| --- | --- |
|
|
473
|
+
| Naming a reactor method after the event | The name is ignored; **the first parameter's type** is the subscription (`EventHandlerMethod.kt:61-74`) |
|
|
474
|
+
| Two `@EventType` classes sharing a simple name | The id defaults to the simple name, so they collide on the wire (`EventTypesService.kt:67`) |
|
|
475
|
+
| Shipping the default connection string to production | `skipTlsValidation` defaults to **true**; certificate validation is off (`ChronicleConnectionString.kt:28`) |
|
|
476
|
+
| Copying a version from the README or docs | Both are stale at `v4.0.0`; the real version comes from the release, not the source |
|
|
477
|
+
| Expecting `getEventStore` to suspend | It does not — the client already connected in its constructor (`ChronicleClient.kt:13`) |
|
|
478
|
+
| Expecting `awaitRegistration()` to mean "registered" | It completes in a `finally`, so it also returns after a failed pass (`ArtifactRegistrations.kt:47-57`) |
|
|
479
|
+
| Expecting a registration failure to throw | Failures are printed to `System.err`, not raised (`EventStore.kt:236`) |
|
|
480
|
+
| Expecting read model reactors to be discovered | `IReadModelReactor` is absent from the scan and from `IEventStore`; construct `ReadModelReactors(...)` yourself |
|
|
481
|
+
| Leaving the default classpath scan on in a large app | Use `withArtifactsFrom(...)` or `artifact-packages` to narrow it (`ChronicleOptions.kt:58`) |
|
|
482
|
+
| Expecting WebFlux support from the starter | The web auto-configuration is `SERVLET`-only (`ChronicleWebAutoConfiguration.kt:29`) |
|
|
483
|
+
| Exiting `main` after an append | Reactors and reducers stop with the process; the stream is live |
|
|
484
|
+
|
|
485
|
+
## Verify
|
|
486
|
+
|
|
487
|
+
- The dependency resolves from Maven Central and the version is the one you
|
|
488
|
+
intended — not a number copied from a README.
|
|
489
|
+
- A connect against the target kernel succeeds, and the client/server version
|
|
490
|
+
pair is one you confirmed rather than assumed.
|
|
491
|
+
- A production connection string sets `skipTlsValidation=false`.
|
|
492
|
+
- Every `@EventType` class has a unique simple name, or an explicit `id`.
|
|
493
|
+
- Every reactor and reducer handler's **first parameter** is an `@EventType`
|
|
494
|
+
class.
|
|
495
|
+
- `awaitRegistration()` is followed by a check that registration actually
|
|
496
|
+
succeeded, not treated as proof on its own.
|
|
497
|
+
- The build is clean and the specifications pass against the verified artifact
|
|
498
|
+
version.
|