soda docs v17.0.1

Configuration Reference

soda-core's configuration falls into two blocks:

PrefixClassPurpose
soda.event.*com.hibuka.soda.bus.configuration.EventPropertiesEvent bus, Redis Stream, serialization
scoda.ddd.cqrs.async.*com.hibuka.soda.bus.configuration.AsyncConfigCQRS async thread pool
Warning

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

ItemValue
Keysoda.event.bus-type
TypeString
Defaultspring
Validation@NotBlank (must not be blank)

Values:

ValueEffect
simpleIn-process event bus, SimpleEventBus
redisRedis Streams event bus, RedisStreamEventBus
springNot implemented in the current version; don't use
Important

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

ItemValue
Keysoda.event.spring
TypeSpringProperties
DefaultEmpty 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.

KeyTypeDefaultValidation
soda.event.serialization.circular-reference-handlerCircularReferenceHandler enumIGNORE@NotNull
soda.event.serialization.fail-on-self-referencesbooleanfalseNone

The three values of CircularReferenceHandler:

ValueMeaning
IGNOREIgnore circular references (default)
ERRORThrow when a circular reference is detected
RETAINPreserve circular references via JSON references
Note

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

ItemValue
Keysoda.event.redis.topic
TypeString
Defaultsoda-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

ItemValue
Keysoda.event.redis.host
TypeString
Defaultlocalhost
Validation@NotBlank

soda.event.redis.port

ItemValue
Keysoda.event.redis.port
Typeint
Default6379
Validation@Positive

soda.event.redis.password

ItemValue
Keysoda.event.redis.password
TypeString
DefaultNone (null)
ValidationNone

soda.event.redis.database

ItemValue
Keysoda.event.redis.database
Typeint
Default0
Validation@PositiveOrZero

soda.event.redis.stream

Redis Stream consumption parameters.

soda.event.redis.stream.group-name

ItemValue
Keysoda.event.redis.stream.group-name
TypeString
Defaultsoda-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

ItemValue
Keysoda.event.redis.stream.consumer-name
TypeString
Defaultsoda-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

ItemValue
Keysoda.event.redis.stream.concurrency
Typeint
Default1
Validation@Positive

Concurrent consumer threads. One Redis Stream subscription per thread.

soda.event.redis.stream.batch-size

ItemValue
Keysoda.event.redis.stream.batch-size
Typeint
Default10
Validation@Positive

Maximum entries pulled per XREADGROUP.

soda.event.redis.stream.maxlen

ItemValue
Keysoda.event.redis.stream.maxlen
Typelong
Default10000
Validation@Positive

Maximum stream length. Older messages get trimmed beyond it.

Warning

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

ItemValue
Keysoda.event.redis.stream.poll-timeout
Typelong (milliseconds)
Default1000
Validation@Positive

Blocking timeout for pulls.

soda.event.redis.stream.acknowledge-timeout

ItemValue
Keysoda.event.redis.stream.acknowledge-timeout
Typelong (milliseconds)
Default30000
Validation@Positive

Acknowledgement timeout.

Note

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

ItemValue
Keysoda.event.redis.stream.max-retries
Typeint
Default3
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

ItemValue
Keysoda.event.redis.stream.initial-retry-delay
Typelong (milliseconds)
Default1000
Validation@Positive

Delay before the first retry.

soda.event.redis.stream.exponential-backoff

ItemValue
Keysoda.event.redis.stream.exponential-backoff
Typeboolean
Defaulttrue
ValidationNone

Whether to use exponential backoff. When enabled the delay is computed as:

java
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

ItemValue
Keysoda.event.redis.stream.dead-letter-stream
TypeString
Defaultsoda-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:

java
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

ItemValue
Keysoda.event.redis.stream.idempotency.enabled
Typeboolean
Defaultfalse
ValidationNone

Whether idempotency is enabled. Recommended for cross-service scenarios.

soda.event.redis.stream.idempotency.redis-key-prefix

ItemValue
Keysoda.event.redis.stream.idempotency.redis-key-prefix
TypeString
Defaultsoda-events-idempotency
Validation@NotBlank

Redis key prefix for idempotency state. The full key is prefix:eventId.

soda.event.redis.stream.idempotency.expire-time

ItemValue
Keysoda.event.redis.stream.idempotency.expire-time
Typelong (seconds)
Default86400 (24 hours)
Validation@Positive

Expiry for idempotency state.

soda.event.redis.stream.idempotency.retry-interval

ItemValue
Keysoda.event.redis.stream.idempotency.retry-interval
Typelong (milliseconds)
Default100
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).

KeyTypeDefaultValidation
scoda.ddd.cqrs.async.core-pool-sizeint8@PositiveOrZero
scoda.ddd.cqrs.async.max-pool-sizeint16@Positive
scoda.ddd.cqrs.async.queue-capacityint100@PositiveOrZero
scoda.ddd.cqrs.async.thread-name-prefixStringcqrs-async-@NotBlank
KeyMeaning
core-pool-sizeCore thread count
max-pool-sizeMaximum thread count
queue-capacityQueue capacity
thread-name-prefixThread name prefix

The pool's other parameters are hard-coded (not configurable):

ItemValue
keepAliveSeconds60
waitForTasksToCompleteOnShutdowntrue
awaitTerminationSeconds60
Rejection policyRun on the caller thread (the CallerRunsPolicy idea)

Full example

yaml
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

KeyTypeDefault
soda.event.bus-typeStringspring
soda.event.serialization.circular-reference-handlerenumIGNORE
soda.event.serialization.fail-on-self-referencesbooleanfalse
soda.event.redis.topicStringsoda-events
soda.event.redis.hostStringlocalhost
soda.event.redis.portint6379
soda.event.redis.passwordStringNone
soda.event.redis.databaseint0
soda.event.redis.stream.group-nameStringsoda-events-group
soda.event.redis.stream.consumer-nameStringsoda-events-consumer-{server.port}
soda.event.redis.stream.concurrencyint1
soda.event.redis.stream.batch-sizeint10
soda.event.redis.stream.maxlenlong10000
soda.event.redis.stream.poll-timeoutlong1000
soda.event.redis.stream.acknowledge-timeoutlong30000
soda.event.redis.stream.max-retriesint3
soda.event.redis.stream.initial-retry-delaylong1000
soda.event.redis.stream.exponential-backoffbooleantrue
soda.event.redis.stream.dead-letter-streamStringsoda-events-dead-letter
soda.event.redis.stream.idempotency.enabledbooleanfalse
soda.event.redis.stream.idempotency.redis-key-prefixStringsoda-events-idempotency
soda.event.redis.stream.idempotency.expire-timelong86400
soda.event.redis.stream.idempotency.retry-intervallong100
scoda.ddd.cqrs.async.core-pool-sizeint8
scoda.ddd.cqrs.async.max-pool-sizeint16
scoda.ddd.cqrs.async.queue-capacityint100
scoda.ddd.cqrs.async.thread-name-prefixStringcqrs-async-

Next steps