Skip to content

Bluetape4k Projects Part 4: data and infrastructure

Small robotic builders connect service code with database, Redis, messaging, and observability modules
Operational systems usually live outside the service. Their failures do not always stay there.

This is Part 4 of the bluetape4k-projects series. Part 3 covered bytes, serializers, HTTP/RPC, and encryption boundaries. This post starts after bytes reach their destination: reading from databases, using Redis, sending broker messages, recording metrics and traces, and controlling failure with resilience policies.

The shared trait is contact with external systems. In code this may look like await, Flow, transaction, cache.get, or send, but the real path also includes connection pools, timeouts, retries, codecs, backpressure, broker partitions, and observability backends. Module selection is therefore less about “which library do we like?” and more about “which execution model and failure boundary should service code expose?”

Service code connects to data, Redis/cache, messaging/infra, coroutine path, resilience, observability, and external systems
Part 4 modules align execution model, codec choices, failure policy, and observability between service code and external systems.

The data/ modules are not a new persistence abstraction. They are thin helpers for using JDBC, R2DBC, Hibernate, Hibernate Reactive, MongoDB, and Cassandra with less repeated Kotlin code.

bluetape4k-jdbc covers DataSource and Connection usage, nullable ResultSet getters, row mapping, and batch/update helpers. It fits paths where synchronous DB access is explicit and blocking is acceptable.

val users = dataSource.runQuery("SELECT id, name FROM users WHERE active = ?", true) { rs ->
rs.toList { row ->
User(row.getLong("id"), row.getString("name"))
}
}

bluetape4k-r2dbc makes reactive database access easier in coroutine and Flow code. DatabaseClient extensions, binding helpers, awaitSingle, and flow helpers are the core pieces. R2dbcPoolConfig also documents high-throughput presets, bounded pending acquire queues, and acquire timeouts with benchmark context. Pool sizing is not “make it large”; it depends on DB connection limits and connection hold time.

val posts = databaseClient
.sql("SELECT * FROM posts WHERE author_id = :authorId")
.bind("authorId", authorId)
.fetch()
.flow { row, _ -> Post(row.get("id") as Long, row.get("title") as String) }

The Hibernate module reduces ORM boilerplate in Kotlin: EntityManager.save, findAs, countAll, entity base classes, converters, and Querydsl helpers. For Spring Boot 4 migration it also includes testing boundaries such as a TestEntityManager shim. ORM improves productivity, but it can blur transaction and lazy-loading boundaries. This module reduces repetition without hiding those boundaries.

MongoDB and Cassandra modules follow the same pattern for reactive/coroutine access. The MongoDB module includes coroutine driver extensions, mongoClient {} DSL, findFirst, exists, upsert, findAsFlow, documentOf {}, and aggregation DSL support.

Redis and Cache: Codecs, Coroutines, Near Cache

Section titled “Redis and Cache: Codecs, Coroutines, Near Cache”

Redis starts at the infra/redis umbrella, but the real choices are Lettuce and Redisson. Most services should choose the client model they need instead of pulling both by habit.

Lettuce is strong when async commands and coroutine adapters matter. Representative helpers are RedisFuture.awaitSuspending(), withPipeline {} for batch flushes, and Collection<RedisFuture>.awaitAll(). Codecs are also part of the design. LettuceBinaryCodecs combines JDK/Kryo/Fory serializers with GZip/LZ4/Snappy/Zstd compression. Use JSON codecs for human-readable values and Fory/LZ4-style profiles for private cache values.

Redisson is stronger for distributed objects and coordination primitives: RMap, RLocalCachedMap, locks, semaphores, batch/transaction DSLs, memoizers, and Near Cache. FastFory codecs carry a wire-format warning and belong at clear volatile boundaries such as caches, not long-lived storage.

Cache modules have their own deep-dive series, so this post only places them on the map. cache-core defines JCache, SuspendCache, NearCache, and memoizer contracts; Hazelcast, Redisson, and Lettuce providers plug in underneath. For the deeper cache abstraction, local/distributed cache, and Near Cache flow, start with Bluetape4k Cache Part 1: cache module. Hibernate second-level cache support lives in hibernate-cache-lettuce with a Caffeine L1 plus Redis L2 structure.

Infrastructure modules are mostly external-system adapters.

AreaModuleRole
Messagingkafka, kafka4, nats, pulsarClient DSLs, coroutine/schema helpers, and a separate Spring Kafka 4.x line
SearchelasticsearchElasticsearch Java API client DSL and coroutine support
Rate limitbucket4jToken-bucket rate limiting
MetricsmicrometerMetric instrumentation helpers
TracingopentelemetryDistributed tracing helpers
Resilienceresilience4jCoroutine retry, circuit breaker, rate limiter, timeout, and coroutine cache

The point is not to attach every operational module everywhere. Attach one per failure path. If HTTP client timeouts, Resilience4j timeouts, and database timeouts all tell different stories, the system is not safer; it is harder to explain.

When selecting Part 4 modules, start with execution model before backend type.

Service boundaryStart withSelection rule
Synchronous SQL with an existing JDBC driverbluetape4k-jdbcBlocking path with explicit synchronous transactions
Reactive DB and coroutine service codebluetape4k-r2dbcFlow, suspend queries, and bounded pool pressure matter
JPA/ORM domain modelbluetape4k-hibernateEntity lifecycle, converters, query helpers, and Spring Boot 4 test shims matter
Redis command pathbluetape4k-lettuceAsync commands, coroutine adapters, pipelines, and codec choices matter
Redis distributed objects/locks/cachebluetape4k-redissonLocks, semaphores, local cached maps, and memoizers matter
Local/distributed cache abstractioncache-*Provider replacement, Near Cache, and suspend cache contracts matter
Messaging/search/observabilityinfra/*Broker, search, metric, or tracing adapter path

First decide whether service code exposes synchronous APIs, suspend APIs, or Flow. Then choose the external system. Finally add retry, timeout, metrics, and tracing. If that order is reversed, infrastructure starts shaping service design before the service boundary is clear.

Data and infrastructure modules organize the point where service code meets external systems. Databases, Redis, brokers, metrics, traces, and retries are all useful tools, and they all fail in their own ways. These modules do not remove failure. They make failure boundaries similar enough to test, observe, and explain.

Next, we move to utilities and adoption path. Small tools often solve the first real problem before a larger adapter is needed.

Comments

Leave a note or reaction with your GitHub account.