Configuration
ChronicleOptions
Section titled “ChronicleOptions”data class ChronicleOptions( val connectionString: ChronicleConnectionString, val programIdentifier: String = "Unknown", val defaultSinkTypeId: String = System.getenv("CHRONICLE_SINK_TYPE") ?: WellKnownSinkTypes.MONGODB, val autoDiscoverAndRegister: Boolean = true, val artifacts: IClientArtifacts = ClientArtifacts.default, val artifactActivator: IArtifactActivator = ArtifactActivator, val openTelemetry: OpenTelemetry? = null)| Property | Default | Description |
|---|---|---|
connectionString | (required) | Parsed address of the server |
programIdentifier | "Unknown" | Name of the connecting program |
defaultSinkTypeId | MongoDB | Sink for reducers and projections |
autoDiscoverAndRegister | true | Register artifacts on connect |
artifacts | ClientArtifacts.default | What the application is made of |
artifactActivator | ArtifactActivator | How artifacts are created |
openTelemetry | null | Where spans go — see Tracing |
readModelNamingPolicy | DefaultReadModelNamingPolicy | Names each read model’s container — see Read model container names |
programIdentifier is a human-readable label that shows up in diagnostics.
defaultSinkTypeId defaults to WellKnownSinkTypes.MONGODB, and can be
overridden per process with the CHRONICLE_SINK_TYPE environment variable
— for example CHRONICLE_SINK_TYPE=SQL.
The last three control artifact discovery and registration. Two helpers cover the common adjustments — see Artifact Registration for the full picture:
ChronicleOptions.development().withoutAutoRegistration()ChronicleOptions.development().withArtifactsFrom("com.acme.ordering")readModelNamingPolicy is not a constructor parameter. Set it with
withReadModelNamingPolicy, described below. copy does not carry it over,
so set it again after copying.
There are two factories on the companion object:
ChronicleOptions.fromConnectionString( "chronicle://chronicle.internal:35000?skipTlsValidation=false")ChronicleOptions.development()Both are @JvmStatic, so Java calls them as ordinary static methods:
ChronicleOptions.fromConnectionString( "chronicle://chronicle.internal:35000?skipTlsValidation=false");ChronicleOptions.development();Read model container names
Section titled “Read model container names”Every read model is stored in a container: a MongoDB collection or a SQL
table. By default the client names it after the read model’s identifier — the
@ReadModel(id = ...) value, or the class simple name. A Customer read model
is stored in a container called Customer.
That is not always the name another layer expects. Arc’s MongoDB naming policy,
for example, pluralizes, so it reads Customers while the client writes
Customer. A ReadModelNamingPolicy decides the container name instead:
fun interface ReadModelNamingPolicy { fun getReadModelName(readModelClass: Class<*>): String}Set one on the options. The policy names only the container. The read model identifier, and everything keyed on it — projections, reducers, observers, and the queries that read a model back — stays as it was.
import io.cratis.chronicle.readModels.ReadModelNamingPolicy
val options = ChronicleOptions.development() .withReadModelNamingPolicy( ReadModelNamingPolicy { "${it.simpleName}s" } )import io.cratis.chronicle.ChronicleOptions;
var options = ChronicleOptions.development() .withReadModelNamingPolicy( readModelClass -> readModelClass.getSimpleName() + "s");A policy that already exists as an object, such as Arc’s, plugs in by
delegating to it from the lambda. In a Spring Boot application, declare a
ReadModelNamingPolicy bean and the starter uses it — see
Spring Boot.
Connection string format
Section titled “Connection string format”chronicle://<host>[:<port>][,<host>[:<port>]...][?<options>]chronicle://<username>:<password>@<host>[:<port>][,...][?<options>]chronicle+srv://<host>[:<port>][?<options>]The port defaults to 35000 when omitted. IPv6 literals use bracket
notation — chronicle://[::1]:35000. A chronicle+srv:// connection
string accepts exactly one host, and DNS SRV records supply the real
targets and ports.
Query string options
Section titled “Query string options”| Option | Default | Description |
|---|---|---|
disableTls | false | Connect over plaintext instead of TLS |
skipTlsValidation | true | Accept any server certificate |
apiKey | (none) | Rejected at client creation; see below |
loadBalancer | least-connections | Policy across multiple addresses |
srvNameServer | (none) | DNS server for chronicle+srv:// |
loadBalancer accepts least-connections, round-robin or random.
Option names are matched without regard to case. An option name the client
does not recognize is ignored without an error, so check the spelling of
skipTlsValidation in particular: a misspelled one leaves certificate
validation off. An unknown loadBalancer value throws
IllegalArgumentException.
skipTlsValidation accepts self-signed certificates. Set it to false to
require full certificate chain validation.
val options = ChronicleOptions.fromConnectionString( "chronicle://my-client:my-secret@chronicle.internal:35000?skipTlsValidation=false")Round-tripping to a string
Section titled “Round-tripping to a string”ChronicleConnectionString.toString() renders a parsed connection string
back to its chronicle:///chronicle+srv:// textual form. The result
isn’t guaranteed to be byte-identical to whatever was originally parsed —
for example a host without an explicit port is rendered with the resolved
default port — but re-parsing it always yields an equal
ChronicleConnectionString. This is useful for storing a connection string
that was built up programmatically rather than typed by hand. The rendered
string includes the password, so do not write it to logs; log its target
(the first host and port) instead:
import io.cratis.chronicle.connection.ChronicleConnectionString
val original = ChronicleConnectionString.parse( "chronicle://my-client:my-secret@chronicle.internal:35000?skipTlsValidation=false")val rendered = original.toString()val reparsed = ChronicleConnectionString.parse(rendered)check(reparsed == original)Development shortcut
Section titled “Development shortcut”val client = ChronicleClient(ChronicleOptions.development())Equivalent to a connection string of
chronicle://chronicle-dev-client:chronicle-dev-secret@localhost:35000.
It connects over TLS but skips certificate validation, so it works
against the Kernel’s self-signed development certificate without further
configuration.
TLS and authentication
Section titled “TLS and authentication”The client connects over TLS by default. Certificate validation is
skipped unless you set skipTlsValidation=false, which makes the client
validate the certificate chain against the JVM’s default trust store. Set
disableTls=true only for plaintext environments.
The client authenticates with a client id and secret: the user name and
password in the connection string’s user info section. It exchanges them for
an access token at the kernel’s /connect/token endpoint. Without user info
it uses the development credentials, chronicle-dev-client and
chronicle-dev-secret. Read the secret from the environment or a secret
store rather than writing it into source code.
The connection string is parsed as text: everything after the first ? is
read as options, and nothing is URL-decoded. A secret that contains ? cannot
be written into it. Construct the connection string from its parts instead:
import io.cratis.chronicle.connection.ChronicleConnectionStringimport io.cratis.chronicle.connection.ChronicleServerAddress
val options = ChronicleOptions( ChronicleConnectionString( addresses = listOf(ChronicleServerAddress("chronicle.internal", 35000)), username = "my-client", password = System.getenv("CHRONICLE_CLIENT_SECRET") ?: error("Set CHRONICLE_CLIENT_SECRET"), skipTlsValidation = false ))From Java, the constructor takes all nine properties in declaration order.
A kernel that is unreachable, or that rejects the client’s TLS settings,
makes the client constructor throw. Wrong credentials are detected once the
client tries to connect: the first append or awaitRegistration() throws
ChronicleConnectionFailed. See
Connection lifecycle for how each failure shows
up.
Namespace
Section titled “Namespace”See Namespaces for the full tenancy model.
The default namespace is "Default". Override it when calling
getEventStore:
val store = client.getEventStore("MyApp", namespace = "production")From Java, BlockingChronicleClient has both forms, so you can name the
namespace or leave it at the default:
import io.cratis.chronicle.ChronicleOptions;import io.cratis.chronicle.java.BlockingChronicleClient;import io.cratis.chronicle.java.BlockingEventStore;
var client = BlockingChronicleClient.connect(ChronicleOptions.development());BlockingEventStore store = client.getEventStore("MyApp", "production");ChronicleClient.getEventStore declares the namespace as a Kotlin default
parameter without @JvmOverloads, so Java callers of that method must pass
both arguments. See Java interop.