Development Conventions

Code style, naming, and documentation standards for the Bidder backend.

Gradle Build Conventions

Custom plugins in buildSrc/:

PluginPurpose
ru.savbros.kotlin-conventionsBase Kotlin + JVM 21
ru.savbros.application-conventionsMain app with Spring Boot
ru.savbros.service-conventionsService modules (WebFlux, R2DBC)
ru.savbros.library-conventionsShared libraries
ru.savbros.client-conventionsExternal API clients

Dependency rules:


Naming Conventions

ElementConvention
Packagesru.savbros.bidder.<domain>.<layer>
Domain modelsPlain data classes in api/model/
DTOsSuffix: Dto, Request, Response. Simple class names must be globally unique (see DTO naming)
EntitiesSuffix: Entity
ConvertersExtension functions: fun X.toY()
DAOsInterface in dao/api/, impl in dao/impl/

DTO Naming

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).


Code Structuring

One File = One Class

Each Kotlin file contains exactly one public class/interface/object. Exception: Tightly coupled classes (e.g., sealed class with subclasses).

Service Module Structure

*-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

Package Rules

  1. Top-level packages are type-based (service/, dao/, config/)
  2. Feature subpackages allowed when classes exceed comfort limits
  3. Never mix feature subpackages with flat files at same level
# CORRECT
service/sync/SyncServiceImpl.kt
service/import/ImportServiceImpl.kt

# INCORRECT
service/sync/SyncServiceImpl.kt
service/SomeOtherService.kt    # BAD: flat file alongside subpackage

Documentation Standards

KDoc Requirements

Document:

Style:

Do NOT document:

Comments Policy

Never write inline comments except for:

Instead use:


Kotlin Code Style

Result Handling

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:

Result Extraction

result.isSuccess / result.isFailure
result.getOrThrow()
result.getOrNull()
result.getOrElse { default }

Testing

Unit Tests

E2E Tests

See bidder-e2e-tests/INTEGRATION_TESTING_GUIDE.md

Key conventions: