- Clarify Requirements First: Before starting any change, ensure the requirement is fully understood. Ask the user follow‑up questions if anything is ambiguous. Multiple clarification questions are allowed.
- Obtain Acceptance of Action Plan: Always draft a detailed, verbose implementation plan (steps, design decisions, test strategy) and present it to the user. Do not begin any code changes until the user explicitly approves the plan.
- Test‑Driven Development (TDD) Cycle:
- Write comprehensive unit/integration tests following project conventions.
- Implement the minimum production code needed to satisfy the tests.
- Refactor while keeping all tests green.
- Repeat – add more tests, expand implementation, ensure clean, modular code.
- Never Cut Corners: Do not add dummy implementations merely to make tests pass. Follow SOLID, Open‑Closed, KISS, and hexagonal architecture principles.
- Modular Design: Keep new code modular, well‑encapsulated, and adhere to the existing layered architecture.
- Documentation & Review: Update relevant documentation, comments, and ensure code quality matches a senior developer’s standards.
- Project Overview
- Build, Lint & Test Commands
- Running a Single Test
- Code Style & Conventions
- Imports
- Formatting & Layout
- Types & Nullability
- Naming Conventions
- Error Handling
- Reactive & Coroutine Practices
- Architecture & Design Patterns
- Testing Guidelines
- Performance & Back‑pressure
- Commit & PR Best Practices
- Additional Resources
- Preference: Prefer Kotlin coroutines over Project Reactor for asynchronous handling. Agents should convert reactive streams to coroutines where feasible, while maintaining back‑pressure support.
- Language: Kotlin 2.2.21 (JVM target)
- Framework: Spring Boot 4.x (reactive stack)
- Database: R2DBC (PostgreSQL) with Liquibase migrations
- Messaging: Kafka
- Testing: JUnit 5, Kotest, MockK, Spring Test, WebTestClient, Testcontainers
- Build Tool: Gradle 8 (Groovy DSL)
- Containerisation: Docker & Docker‑Compose (Postgres)
The codebase follows a hexagonal / clean‑architecture flavour – core domain logic lives in src/main/kotlin/com/softeno/template/app/... and adapters (web, db, kafka) are separate packages.
| Goal | Command | Description |
|---|---|---|
| Compile | ./gradlew clean build |
Full clean compile, runs tests, creates JAR. |
| Run Application | ./gradlew bootRun |
Starts the Spring Boot app (uses application.properties). |
| Package JAR | ./gradlew bootJar |
Produces an executable JAR in build/libs. |
| Run Checks Only | ./gradlew check |
Executes linting, static analysis, and tests without building the JAR. |
| Static Analysis | ./gradlew detektMain detektTest |
Runs Detekt (Kotlin linter) on source and test code. |
| Formatting | ./gradlew spotlessApply |
Applies Spotless formatting (Kotlin + Gradle). |
| Run All Tests | ./gradlew test |
Executes unit + integration tests. |
| Run Integration Tests Only | ./gradlew integrationTest (if defined) |
Use custom source set; otherwise filter with tags. |
| Run Tests with Coverage | ./gradlew jacocoTestReport |
Generates a JaCoCo coverage report under build/reports/jacoco. |
| Run a Single Test | See section below. |
Kotlin test classes are discovered by the test task. To run a single test method or class, use the Gradle --tests filter:
# Run a single test class
./gradlew test --tests "com.softeno.template.app.permission.service.PermissionServiceTest"
# Run a single test method (full qualified name)
./gradlew test --tests "com.softeno.template.app.permission.service.PermissionServiceTest.shouldPersistAndRetrievePermission"Tip for agents: When a failing test is identified, re‑run it with the same filter to verify the fix quickly.
The following rules are non‑negotiable for any automated agent. Violations will cause the CI lint step to fail.
- Alphabetical order, grouped by:
kotlin.*,java.*,org.*,com.*. - One blank line between groups.
- Use single‑type imports (
import java.util.UUID) – avoid wildcard*imports. - Remove unused imports – Detekt’s
UnusedImportsrule enforces this.
import com.softeno.template.app.permission.api.PermissionController
import com.softeno.template.app.permission.service.PermissionService
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping- Indentation: 4 spaces, no tabs.
- Line length: max 120 characters.
- Braces: K&R style – opening brace on same line.
- Trailing commas in multi‑line collections/parameter lists (Kotlin 1.9 supports it).
- Blank lines: separate logical sections (e.g., imports, class header, properties, functions).
- Spotless is the single source of truth – run
./gradlew spotlessApplybefore committing.
- Prefer non‑nullable types. Use
?only when the value can legitimately be absent. - When a nullable value is required, employ
requireNotNullor?.let {}to avoid NPEs. - Leverage Kotlin’s type‑aliases for complex reactive types, e.g.:
typealias Flux<T> = reactor.core.publisher.Flux<T>for readability. - Use value classes (
@JvmInline) for simple wrappers (e.g.,PermissionId).
| Element | Convention |
|---|---|
| Packages | lower‑case, dot‑separated (com.softeno.template.app.permission) |
| Classes / Interfaces | PascalCase (PermissionService, PermissionRepository) |
| Enums | PascalCase, singular (PermissionStatus) |
| Functions | camelCase, verb‑first (createPermission, findById) |
| Properties | camelCase, descriptive (permissionId, createdAt) |
| Constants | Upper‑snake (MAX_RETRY_ATTEMPTS) |
| Test classes | <ClassUnderTest>Test (PermissionServiceTest) |
| Test methods | descriptive, should<Behavior> (shouldPersistAndRetrievePermission) |
- Never swallow exceptions; re‑throw or map to a domain‑specific exception.
- Use Spring’s
ResponseStatusExceptionfor HTTP errors, with appropriate status codes. - For reactive pipelines, prefer
onErrorMap/onErrorResumerather thantry/catch. - Create a
DomainExceptionhierarchy for business‑logic errors. - Log errors at WARN or ERROR level with structured logging (
log.error("Failed to …", e)).
- Prefer Project Reactor (
Mono,Flux) for public APIs; use Kotlin coroutines (suspend,Flow) for internal services when it simplifies code. - Back‑pressure: never
block()inside a reactive chain. UsepublishOn/subscribeOnwisely. - Convert between Reactor and Coroutines with
reactor.kotlin.coroutines.awaitSingle,asFlux(), etc. - Keep side‑effects at the end of the chain; avoid modifying shared state inside
map. - Timeouts: apply
timeout(Duration)on external calls (WebClient, R2DBC) to protect the system.
- Follow Hexagonal (Ports & Adapters): core domain (
service,mapper,model) has no Spring annotations; adapters (controller,repository,kafka) implement interfaces defined in the domain. - Use Dependency Injection (constructor injection only). No field injection.
- Apply SOLID:
- Single Responsibility – each class does one thing.
- Open/Closed – extend behavior via interfaces, avoid modifying existing classes.
- Liskov Substitution – keep contracts consistent.
- Interface Segregation – small, focused interfaces.
- Dependency Inversion – depend on abstractions.
- KISS – avoid over‑engineering; introduce a pattern only when the problem demands it.
- Common OO patterns used:
- Factory (for creating domain objects from DTOs).
- Adapter (e.g., Kafka listener to service).
- Decorator (adding logging or metrics to repositories).
- Do NOT introduce a full‑blown DDD layer unless a clear bounded context emerges.
- Unit Tests
- Focus on pure business logic – repository & controller layers are covered by integration tests.
- Use MockK for mocks/stubs; verify interactions only when they affect behavior.
- Avoid testing getters/setters or trivial data classes.
- Use
@Nestedclasses (JUnit) for grouping scenarios. - Keep test method names expressive (
shouldReturnPermissionWhenExists).
- Integration Tests
- Spin up Testcontainers for PostgreSQL & Kafka where needed.
- Use
@SpringBootTestwithwebEnvironment = RANDOM_PORTandWebTestClientfor end‑to‑end HTTP flow. - Test error paths, validation, security (401/403), and back‑pressure behaviour.
- Do not mock Spring beans; rely on the actual context to verify wiring.
- Performance / Reactive Tests
- Use
StepVerifierwithvirtualTime()for time‑based operators. - Verify
request(n)behaviour to ensure back‑pressure compliance.
- Use
- Coverage
- Target ~80% line coverage; focus on critical paths.
- Exclude generated code and third‑party libraries from coverage metrics.
- Test Data
- Use Builder pattern or Kotlin
apply {}blocks for fixture creation. - Keep fixtures immutable; share via companion objects when appropriate.
- Use Builder pattern or Kotlin
- CI Recommendations
./gradlew testmust succeed in CI../gradlew detektMain detektTest spotlessCheckmust pass.- Failing tests abort the PR.
- Reactive DB Calls: always use R2DBC non‑blocking APIs; never call
blockingJDBC. - Kafka: configure
max.poll.recordsandreactor.kafka.receiver.KafkaReceiverto respect demand. - WebClient: enable
connectionPoolandreadTimeout; reuse the same bean. - Circuit Breaker: use Resilience4j with sensible defaults (
failureRateThreshold=50,waitDurationInOpenState=30s). - Metric Collection: expose Micrometer counters for request latency and throughput.
- Avoid heavy object allocation in hot paths – reuse immutable data classes.
- Profiling: run
./gradlew bootRun --args='--spring.profiles.active=dev'and attach a profiler (YourKit, VisualVM) for bottleneck analysis.
- Atomic Commits – one logical change per commit.
- Commit Message –
{type}(scope): short descriptiontype=feat,fix,refactor,test,chore.- Example:
feat(permission): add bulk‑create endpoint.
- PR Title – concise, matches first line of commit.
- PR Description – include What, Why, How and Testing steps.
- No Secrets – never commit
.env, API keys, passwords. - Never commit or push without explicit user approval – agents must obtain clear user confirmation before executing any
git commitorgit pushcommands. - Rebase – keep a linear history; avoid merge commits.
- CI Checks – ensure
./gradlew checkpasses before merging.
- Kotlin Coding Conventions – https://kotlinlang.org/docs/coding-conventions.html
- Spring Reactive Documentation – https://docs.spring.io/spring-framework/reference/web/reactive.html
- Detekt Ruleset – https://detekt.dev/docs/rules/
- Hexagonal Architecture – https://alistair.cockburn.us/hexagonal-architecture/
- Resilience4j – https://resilience4j.readme.io/
- Testcontainers – https://www.testcontainers.org/
- Spotless Kotlin Plugin – https://github.com/diffplug/spotless/tree/main/plugin-gradle#kotlin
Generated by the AI assistant to guide automated agents working on the spring-reactive-r2dbc-template repository. Follow these rules strictly to keep the codebase clean, performant, and maintainable.