8.3 KiB
AGENTS.md — Coding Agent Guide for RBCS
Project Overview
RBCS (Remote Build Cache Server) is a Kotlin/Java multi-module Gradle project built on Netty.
It serves as a remote build cache for Gradle and Maven. Java is used for the public API layer
(with Lombok); Kotlin is used for all implementation modules. All modules use JPMS
(module-info.java). The project is built on Netty with Java 25.
Modules: rbcs-api, rbcs-common, rbcs-server, rbcs-server-memcache, rbcs-client,
rbcs-cli, rbcs-servlet, docker
Build Commands
./gradlew build # Full build: compile + test + assemble
./gradlew assemble # Build JARs without running tests
./gradlew compileJava compileKotlin # Compile only
./gradlew clean # Clean build outputs
./gradlew :rbcs-server:compileKotlin # Compile a single module
./gradlew -q version # Print project version
Test Commands
./gradlew test # Run all tests (all modules)
./gradlew :rbcs-server:test # Run tests for a single module
./gradlew :rbcs-server:test --tests "net.woggioni.rbcs.server.test.NoAuthServerTest"
# Run a single test class
./gradlew :rbcs-server:test --tests "net.woggioni.rbcs.server.test.NoAuthServerTest.putWithNoAuthorizationHeader"
# Run a single test method
./gradlew test --tests "*TlsServer*" # Run tests matching a pattern
./gradlew :rbcs-server:jacocoTestReport # Generate code coverage (rbcs-server only)
Test framework: JUnit 5 (Jupiter). Tests are integration-style — they start real Netty
servers and use java.net.http.HttpClient to make HTTP requests. Netty leak detection is
set to PARANOID in test configurations.
Test locations: src/test/kotlin/ and src/test/java/ following standard Gradle layout.
Test resources (XML configs, logback) are in src/test/resources/.
Lint / Format
No linting or formatting tools are configured (no Checkstyle, Detekt, ktlint, Spotless, or similar). Follow the existing code style described below.
Code Style Guidelines
Language Split
- Java for
rbcs-api(public API consumed by Java clients) with Lombok annotations - Kotlin for all implementation modules
module-info.javain every module (JPMS)
Import Ordering (Kotlin)
Three groups separated by blank lines, each alphabetically sorted:
- External/third-party (
io.netty.*,org.slf4j.*) - Java standard library (
java.*,javax.*) - Internal project (
net.woggioni.rbcs.*)
Import aliases are rare; used only to resolve conflicts:
import io.netty.util.concurrent.Future as NettyFuture
Naming Conventions
| Element | Convention | Examples |
|---|---|---|
| Classes/interfaces | PascalCase | RemoteBuildCacheServer, CacheHandler |
| Abstract classes | Abstract prefix |
AbstractServerTest, AbstractNettyHttpAuthenticator |
| Functions/methods | camelCase | loadConfiguration(), sendShutdownSignal() |
| Variables/properties | camelCase | bucketManager, sslContext |
| Constants | SCREAMING_SNAKE_CASE | SSL_HANDLER_NAME, RBCS_NAMESPACE_URI |
| Handler names | val NAME = ...::class.java.name in companion object |
|
| Packages | lowercase dot-separated | net.woggioni.rbcs.server.throttling |
| Enum values | PascalCase | Role.Reader, Role.Writer |
| Kotlin files | PascalCase matching primary class; lowercase for files with only top-level functions |
Error Handling
- Custom unchecked exception hierarchy rooted at
RbcsException extends RuntimeException - Domain subclasses:
CacheException,ConfigurationException,ContentTooLargeException - Async errors propagated via
CompletableFuture.completeExceptionally() - Synchronous errors thrown directly:
throw IllegalArgumentException(...) - Kotlin null safety idioms preferred over null checks:
?.let,?:,takeIf ExceptionHandlermaps exception types to HTTP responses via exhaustivewhen
Type Annotations
- Kotlin: Heavy use of type inference for local variables. Explicit types required on:
- Class properties:
private val sslContext: SslContext? - Non-trivial return types:
fun run(): ServerHandle lateinit varin tests:protected lateinit var cfg: Configuration
- Class properties:
- Java: Lombok
@Valuefor immutable data classes; modern pattern matching withinstanceof - No
typealiasdeclarations are used in this project
Async Patterns
- Primary abstraction:
CompletableFuture<T>— no Kotlin coroutines - Netty event-driven callbacks (
ChannelFuture,GenericFutureListener) - Custom
AsyncCloseableinterface wrapsCompletableFuture<Void>for async shutdown - Retry logic uses
CompletableFuturecomposition with exponential backoff + jitter - Virtual threads used selectively (background GC, configurable event executors)
- Connection pooling via Netty
FixedChannelPool
Logging
- SLF4J via custom Kotlin lazy-evaluation extension functions (defined in
rbcs-common) - Logger creation:
private val log = createLogger<ClassName>()(in companion object) - Lazy log calls:
log.debug { "message with ${interpolation}" }(lambda only evaluated if enabled) - Channel-aware variants:
log.debug(ctx) { "message" }(adds MDC: channel-id, remote-address) - Java classes use Lombok
@Slf4j - String templates with
${}for Kotlin log messages
Kotlin Idioms
applyas builder pattern:ServerBootstrap().apply { group(...); option(...) }alsofor side effects,letfor transformation,runfor scoping- Trailing commas in constructor parameter lists and multi-line function calls
sealed class/sealed interfacefor algebraic types (e.g.,OperationOutcome,Authentication)data classfor value types;companion objectfor static members and constants- No trailing semicolons
Java Idioms (API module)
- Lombok annotations:
@Value,@Getter,@RequiredArgsConstructor,@EqualsAndHashCode.Include sealed interfacewithfinalpermitted subtypes (e.g.,CacheMessage)@FunctionalInterfaceon single-method interfaces- JPMS
module-info.javain every module withrequires,exports,uses,provides
Testing Patterns
@TestInstance(Lifecycle.PER_CLASS)— single instance per test class@TestMethodOrder(MethodOrderer.OrderAnnotation)with@Order(n)for sequential execution- Abstract base class hierarchy:
AbstractServerTest→AbstractBasicAuthServerTest→ concrete - Server lifecycle in
@BeforeAll/@AfterAll(start/stop real Netty server) @TempDirfor temporary directories@ParameterizedTestwith@ValueSourceand@ArgumentsSourcefor parameterized tests- Assertions via
org.junit.jupiter.api.Assertions(assertEquals,assertArrayEquals) - No mocking framework — all tests are integration-style against real servers
Documentation
Minimal doc comments in the codebase. Inline comments used sparingly for clarification. When adding new public API, follow existing style — doc comments are not enforced but are welcome on complex logic.
Module Dependency Graph
rbcs-api → (standalone, Lombok, Netty types)
rbcs-common → Netty, Kotlin stdlib
rbcs-server → rbcs-api, rbcs-common
rbcs-server-memcache → rbcs-api, rbcs-common
rbcs-client → rbcs-api, rbcs-common
rbcs-cli → rbcs-client, rbcs-server (Picocli for CLI)
rbcs-servlet → rbcs-api, rbcs-common (Jakarta Servlet/CDI)
docker → rbcs-cli, rbcs-server-memcache
Plugin System
Cache backends use the ServiceLoader pattern via JPMS provides/uses directives.
To add a new cache provider, implement CacheProvider<T extends Configuration.Cache> and
register it in your module's module-info.java.
Configuration
- XML-based with XSD schema validation
- Schemas in
src/main/resources/.../schema/*.xsd - Default config loaded from JPMS resources:
jpms://net.woggioni.rbcs.server/...