Code style, naming, and documentation standards for the Bidder backend.
Custom plugins in buildSrc/:
| Plugin | Purpose |
|---|---|
ru.savbros.kotlin-conventions | Base Kotlin + JVM 21 |
ru.savbros.application-conventions | Main app with Spring Boot |
ru.savbros.service-conventions | Service modules (WebFlux, R2DBC) |
ru.savbros.library-conventions | Shared libraries |
ru.savbros.client-conventions | External API clients |
Dependency rules:
*-service-impl*-service-impl depends on *-service-api| Element | Convention |
|---|---|
| Packages | ru.savbros.bidder.<domain>.<layer> |
| Domain models | Plain data classes in api/model/ |
| DTOs | Suffix: Dto, Request, Response. Simple class names must be globally unique (see DTO naming) |
| Entities | Suffix: Entity |
| Converters | Extension functions: fun X.toY() |
| DAOs | Interface in dao/api/, impl in dao/impl/ |
Never create DTO classes with the same simple name in different modules. SpringDoc uses the simple class name as the OpenAPI schema key, so two classes named AccountDto in different packages will collide — one silently shadows the other, producing incorrect Swagger documentation.
If a domain-specific DTO duplicates an existing name, prefix it with the domain context (e.g., SearchAccountDto instead of AccountDto in the campaign-search module).
Each Kotlin file contains exactly one public class/interface/object. Exception: Tightly coupled classes (e.g., sealed class with subclasses).
*-service-api/
api/service/ # Public interfaces
api/model/ # Domain models
api/dto/ # Request/Response DTOs
*-service-impl/
config/ # @Configuration classes
controller/ # REST controllers
converter/ # Entity <-> Model converters
dao/api/ # DAO interfaces
dao/impl/ # DAO implementations
dao/entity/ # R2DBC entities
dao/repository/ # Spring Data repositories
scheduler/ # @Scheduled jobs
security/ # Authorization policies
service/ # Service implementations
service/, dao/, config/)# CORRECT
service/sync/SyncServiceImpl.kt
service/import/ImportServiceImpl.kt
# INCORRECT
service/sync/SyncServiceImpl.kt
service/SomeOtherService.kt # BAD: flat file alongside subpackage
Document:
Style:
Do NOT document:
Never write inline comments except for:
Instead use:
Use kotlin.Result<T> with expression syntax:
override suspend fun syncAll(): Result<Unit> = runCatching {
accounts.forEach { syncAccount(it) }
}.onFailure { e ->
logger.error(e) { "Failed: ${e.message}" }
}
Guidelines:
kotlin.Result<T> (not custom types)= runCatching { }.onSuccess { } and .onFailure { } for side effectsresult.isSuccess / result.isFailure
result.getOrThrow()
result.getOrNull()
result.getOrElse { default }
See bidder-e2e-tests/INTEGRATION_TESTING_GUIDE.md
Key conventions:
runTest { } for coroutinesUUID.randomUUID()withContext(userFactory.createCallerContextFromAccessToken(token))