Configuration Reference
soda-core's configuration falls into two blocks:
| Prefix | Class | Purpose |
|---|---|---|
soda.event.* | com.hibuka.soda.bus.configuration.EventProperties | Event bus, Redis Stream, serialization |
scoda.ddd.cqrs.async.* | com.hibuka.soda.bus.configuration.AsyncConfig | CQRS async thread pool |
The thread pool's prefix is scoda.ddd.cqrs.async, not soda.ddd.cqrs.async. README-CN gets this wrong.
soda.event
EventProperties is annotated with @ConfigurationProperties(prefix = "soda.event") and @Validated, so a bad configuration throws at startup instead of surfacing at runtime.
soda.event.bus-type
| Item | Value |
|---|---|
| Key | soda.event.bus-type |
| Type | String |
| Default | spring |
| Validation | @NotBlank (must not be blank) |
Values:
| Value | Effect |
|---|---|
simple | In-process event bus, SimpleEventBus |
redis | Redis Streams event bus, RedisStreamEventBus |
spring | Not implemented in the current version; don't use |
The EventProperties default is "spring", but in 17.0.1 the soda-event-spring module has been removed, and no EventBus implementation is conditional on havingValue = "spring".
The actual behavior: SimpleEventBusAutoConfiguration is conditional on havingValue = "simple", matchIfMissing = true, so unconfigured falls back to Simple; setting spring instead leaves you with no EventBus bean at all — and RepositoryEventAutoConfiguration (@ConditionalOnBean(EventBus.class)) doesn't register either, so domain events never go out.
In production, write soda.event.bus-type: simple or redis explicitly.
soda.event.spring
| Item | Value |
|---|---|
| Key | soda.event.spring |
| Type | SpringProperties |
| Default | Empty object |
| Validation | @NotNull |
SpringProperties is an empty class — with no configurable options at all. It is kept only for the completeness of the configuration structure.
soda.event.serialization
Serialization settings. There are only two keys, and in 17.0.1 no code actually reads them — serialization is fully handed to the EventSerializer abstraction.
| Key | Type | Default | Validation |
|---|---|---|---|
soda.event.serialization.circular-reference-handler | CircularReferenceHandler enum | IGNORE | @NotNull |
soda.event.serialization.fail-on-self-references | boolean | false | None |
The three values of CircularReferenceHandler:
| Value | Meaning |
|---|---|
IGNORE | Ignore circular references (default) |
ERROR | Throw when a circular reference is detected |
RETAIN | Preserve circular references via JSON references |
These two keys are kept for backward compatibility. 17.0.1 removed SmartCircularReferenceSerializerModifier; handling circular references is now the responsibility of whichever EventSerializer implementation you use.
soda.event.redis
Redis-related settings. They take effect only when soda.event.bus-type=redis.
soda.event.redis.topic
| Item | Value |
|---|---|
| Key | soda.event.redis.topic |
| Type | String |
| Default | soda-events |
| Validation | @NotBlank |
The Redis Stream key name. Published events are written to this stream, and consumer groups pull from it.
soda.event.redis.host
| Item | Value |
|---|---|
| Key | soda.event.redis.host |
| Type | String |
| Default | localhost |
| Validation | @NotBlank |
soda.event.redis.port
| Item | Value |
|---|---|
| Key | soda.event.redis.port |
| Type | int |
| Default | 6379 |
| Validation | @Positive |
soda.event.redis.password
| Item | Value |
|---|---|
| Key | soda.event.redis.password |
| Type | String |
| Default | None (null) |
| Validation | None |
soda.event.redis.database
| Item | Value |
|---|---|
| Key | soda.event.redis.database |
| Type | int |
| Default | 0 |
| Validation | @PositiveOrZero |
soda.event.redis.stream
Redis Stream consumption parameters.
soda.event.redis.stream.group-name
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.group-name |
| Type | String |
| Default | soda-events-group |
| Validation | @NotBlank |
The consumer group name. Multiple consumers in the same group share the message load. Created with XGROUP CREATE at startup; ignored if it already exists.
soda.event.redis.stream.consumer-name
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.consumer-name |
| Type | String |
| Default | soda-events-consumer- + System.getProperty("server.port", "0") |
| Validation | @NotBlank |
The consumer name. By default it appends server.port, so multiple instances on one machine don't collide.
With concurrency > 1, the actual consumer names get a suffix: consumerName-0, consumerName-1, …
soda.event.redis.stream.concurrency
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.concurrency |
| Type | int |
| Default | 1 |
| Validation | @Positive |
Concurrent consumer threads. One Redis Stream subscription per thread.
soda.event.redis.stream.batch-size
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.batch-size |
| Type | int |
| Default | 10 |
| Validation | @Positive |
Maximum entries pulled per XREADGROUP.
soda.event.redis.stream.maxlen
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.maxlen |
| Type | long |
| Default | 10000 |
| Validation | @Positive |
Maximum stream length. Older messages get trimmed beyond it.
maxlen trims the history beyond messages not yet acknowledged by consumer groups. If consumption can't keep up with production, messages may be trimmed before they are processed. maxlen is not a safety net — for reliable delivery, raise it and monitor consumption lag.
soda.event.redis.stream.poll-timeout
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.poll-timeout |
| Type | long (milliseconds) |
| Default | 1000 |
| Validation | @Positive |
Blocking timeout for pulls.
soda.event.redis.stream.acknowledge-timeout
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.acknowledge-timeout |
| Type | long (milliseconds) |
| Default | 30000 |
| Validation | @Positive |
Acknowledgement timeout.
This parameter is defined and validated in EventProperties, but when RedisEventBusAutoConfiguration constructs RedisStreamEventBus it never passes it in (the Builder has no setter for it). It is currently a no-op configuration option.
soda.event.redis.stream.max-retries
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.max-retries |
| Type | int |
| Default | 3 |
| Validation | @PositiveOrZero |
Maximum retries on processing failure. The retry loop is for (int retryCount = 0; retryCount <= maxRetries; retryCount++), so there are maxRetries + 1 attempts in total.
soda.event.redis.stream.initial-retry-delay
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.initial-retry-delay |
| Type | long (milliseconds) |
| Default | 1000 |
| Validation | @Positive |
Delay before the first retry.
soda.event.redis.stream.exponential-backoff
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.exponential-backoff |
| Type | boolean |
| Default | true |
| Validation | None |
Whether to use exponential backoff. When enabled the delay is computed as:
private long calculateRetryDelay(int retryCount) {
if (exponentialBackoff) {
return (long) (initialRetryDelay * Math.pow(2, retryCount));
}
return initialRetryDelay;
}With the defaults, the retry delays are 1s, 2s, 4s in sequence.
soda.event.redis.stream.dead-letter-stream
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.dead-letter-stream |
| Type | String |
| Default | soda-events-dead-letter |
| Validation | @NotBlank |
The dead-letter stream name. Once retries are exhausted, the message is written to this stream along with the failure reason:
Map<String, String> deadLetterEntry = new HashMap<>(message.getValue().size() + 4);
deadLetterEntry.putAll(message.getValue());
deadLetterEntry.put("deadLetterReason", reason);
deadLetterEntry.put("deadLetterTimestamp", String.valueOf(System.currentTimeMillis()));
deadLetterEntry.put("originalStream", message.getStream());
deadLetterEntry.put("originalId", message.getId().getValue());soda.event.redis.stream.idempotency.enabled
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.idempotency.enabled |
| Type | boolean |
| Default | false |
| Validation | None |
Whether idempotency is enabled. Recommended for cross-service scenarios.
soda.event.redis.stream.idempotency.redis-key-prefix
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.idempotency.redis-key-prefix |
| Type | String |
| Default | soda-events-idempotency |
| Validation | @NotBlank |
Redis key prefix for idempotency state. The full key is prefix:eventId.
soda.event.redis.stream.idempotency.expire-time
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.idempotency.expire-time |
| Type | long (seconds) |
| Default | 86400 (24 hours) |
| Validation | @Positive |
Expiry for idempotency state.
soda.event.redis.stream.idempotency.retry-interval
| Item | Value |
|---|---|
| Key | soda.event.redis.stream.idempotency.retry-interval |
| Type | long (milliseconds) |
| Default | 100 |
| Validation | @Positive |
Retry interval for idempotency-state checks.
scoda.ddd.cqrs.async
AsyncConfig uses the prefix scoda.ddd.cqrs.async. It is enabled by SpringBusAutoConfiguration's @EnableConfigurationProperties(AsyncConfig.class).
| Key | Type | Default | Validation |
|---|---|---|---|
scoda.ddd.cqrs.async.core-pool-size | int | 8 | @PositiveOrZero |
scoda.ddd.cqrs.async.max-pool-size | int | 16 | @Positive |
scoda.ddd.cqrs.async.queue-capacity | int | 100 | @PositiveOrZero |
scoda.ddd.cqrs.async.thread-name-prefix | String | cqrs-async- | @NotBlank |
| Key | Meaning |
|---|---|
core-pool-size | Core thread count |
max-pool-size | Maximum thread count |
queue-capacity | Queue capacity |
thread-name-prefix | Thread name prefix |
The pool's other parameters are hard-coded (not configurable):
| Item | Value |
|---|---|
| keepAliveSeconds | 60 |
| waitForTasksToCompleteOnShutdown | true |
| awaitTerminationSeconds | 60 |
| Rejection policy | Run on the caller thread (the CallerRunsPolicy idea) |
Full example
soda:
event:
bus-type: redis
redis:
topic: soda-events
host: 127.0.0.1
port: 6379
password: ${REDIS_PASSWORD:}
database: 0
stream:
group-name: soda-events-group
consumer-name: soda-events-consumer
concurrency: 2
batch-size: 20
maxlen: 50000
poll-timeout: 1000
acknowledge-timeout: 30000
max-retries: 3
initial-retry-delay: 1000
exponential-backoff: true
dead-letter-stream: soda-events-dead-letter
idempotency:
enabled: true
redis-key-prefix: soda-events-idempotency
expire-time: 86400
retry-interval: 100
scoda:
ddd:
cqrs:
async:
core-pool-size: 16
max-pool-size: 32
queue-capacity: 200
thread-name-prefix: "cqrs-async-"Configuration quick reference
| Key | Type | Default |
|---|---|---|
soda.event.bus-type | String | spring |
soda.event.serialization.circular-reference-handler | enum | IGNORE |
soda.event.serialization.fail-on-self-references | boolean | false |
soda.event.redis.topic | String | soda-events |
soda.event.redis.host | String | localhost |
soda.event.redis.port | int | 6379 |
soda.event.redis.password | String | None |
soda.event.redis.database | int | 0 |
soda.event.redis.stream.group-name | String | soda-events-group |
soda.event.redis.stream.consumer-name | String | soda-events-consumer-{server.port} |
soda.event.redis.stream.concurrency | int | 1 |
soda.event.redis.stream.batch-size | int | 10 |
soda.event.redis.stream.maxlen | long | 10000 |
soda.event.redis.stream.poll-timeout | long | 1000 |
soda.event.redis.stream.acknowledge-timeout | long | 30000 |
soda.event.redis.stream.max-retries | int | 3 |
soda.event.redis.stream.initial-retry-delay | long | 1000 |
soda.event.redis.stream.exponential-backoff | boolean | true |
soda.event.redis.stream.dead-letter-stream | String | soda-events-dead-letter |
soda.event.redis.stream.idempotency.enabled | boolean | false |
soda.event.redis.stream.idempotency.redis-key-prefix | String | soda-events-idempotency |
soda.event.redis.stream.idempotency.expire-time | long | 86400 |
soda.event.redis.stream.idempotency.retry-interval | long | 100 |
scoda.ddd.cqrs.async.core-pool-size | int | 8 |
scoda.ddd.cqrs.async.max-pool-size | int | 16 |
scoda.ddd.cqrs.async.queue-capacity | int | 100 |
scoda.ddd.cqrs.async.thread-name-prefix | String | cqrs-async- |
Next steps
- The mechanics behind each option: Event Bus & Aspect
- How to replace the framework's defaults: Extension Points
- Which settings change on upgrade: Upgrade from v16