Backend server for ppotto (뽀또). Kotlin 2.3 / Spring Boot 4.1 / JDK 25 / PostgreSQL 18 / Flyway / jOOQ / Kotest. Single Gradle module.
| Command | Description |
|---|---|
./gradlew build |
Compile + tests + ktlint + detekt. Must pass before finishing any task |
./gradlew ktlintFormat |
Auto-fix code style violations |
./gradlew flywayMigrate jooqCodegen |
Apply migrations + regenerate jOOQ code (needs local DB: docker compose up -d) |
./gradlew bootRun |
Run locally (reads .env via spring-dotenv) |
./gradlew koverHtmlReport |
Test coverage report (build/reports/kover/html) |
- One top-level package under
com.github.nexters.ppotto= one domain.globalis the shared module. - Domain package layout:
photo/
├── presentation/ Controller, request/response dto
├── application/ Service (transaction boundary), QueryService
├── domain/ domain model, XxxErrorCode
└── infrastructure/ Repository (jOOQ DSLContext), external clients
- Dependency direction: presentation → application → domain ← infrastructure.
domainmust not import Spring or jOOQ. - Cross-domain access goes through the other domain's application Service only. Never touch another domain's Repository or jOOQ tables directly.
- Reads: QueryService may project directly to dto with jOOQ. Writes go through the domain model.
- Never expose jOOQ-generated POJOs/Records in API responses. Always map to dto.
- Branch: off
dev, namedfeat/이슈번호-기능간단설명(e.g.feat/1-user-board-image-entity). - PR target:
dev, notmain.mainis only updated by promotingdev.
- Format:
$operator($domain): $message— e.g.feat(photo): 사진 업로드 API 추가 - Operators:
featfixrefactorchoretestdocsstyleci - Message in Korean. Never add trailers (including Claude-Session). Commit in small, per-task units.
- No comments in code. No emojis anywhere. Documents, labels, and test names in Korean.
- Dependency versions live only in
gradle/libs.versions.toml. No version strings in build scripts. - Error codes:
PREFIX-NNN(COMMON-001,PHOTO-001). Each domain defines an enum implementingErrorCode. - API responses use the
ApiResponseenvelope. ThrowBusinessExceptionsubclasses;GlobalExceptionHandlerconverts them. - No default values in yml placeholders (
${VAR}only). Defaults live only in.env.template. New env vars must be added there. @ConfigurationPropertiesdata classes get@Validated+ jakarta validation annotations.- Validation annotations on data class constructor properties always use the
@field:use-site (@field:NotBlank val title: String); without it Hibernate Validator may not see them. Controllers take@Valid @RequestBody. - Never write a fully-qualified name (FQN) inline. Import the short name whenever there's no naming conflict.
- Primary keys for new tables:
uuid primary key default uuidv7()(Postgres 18 built-in, time-ordered). - API versioning: adopt Spring Framework 7 native API versioning when the first public API ships.
- Write
src/main/resources/db/migration/V{yyyyMMddHHmmss}__{description}.sql— timestamp versions avoid collisions between parallel branches, andout-of-order: truelets an older-versioned migration from a merged branch apply later - Run
./gradlew flywayMigrate jooqCodegen - Commit generated code in
src/generated/jooq/
| Directory | Description |
|---|---|
src/ |
Sources (see src/AGENTS.md) |
gradle/ |
Version catalog + wrapper (see gradle/AGENTS.md) |
buildSrc/ |
DB build convention plugin (see buildSrc/AGENTS.md) |
.github/ |
CI workflow + templates (see .github/AGENTS.md) |
.gitattributes |
Marks src/generated/** as linguist-generated so PR diffs collapse generated files |
Whenever you add, remove, or change files in a directory, update that directory's AGENTS.md in the same change. Keep this hierarchy accurate at all times.