본문 바로가기
PJT

GPT랑 앱 만들기 3 - server 셋업 (Kotlin + Spring)

by 정고정 2025. 4. 20.
반응형

 

이번 프로젝트에서는 미감을 버리기로 했다. 

나한테 클라이언트는 돈이 안 드는 집꾸미기 같은 거다. 

 

아는 게 없으니까 예쁜 거랑 나 쓰기 편한 거에만 집착하기 시작하는데, 능력은 없어서 시간은 엄청 오래 걸린다. 

그런데 두 달 뒤면 질려서 새 인테리어로 바꾸고 싶어지고 

돈이 안 드니까 일단 바꾼다. 

바꾸는데 시간이 엄청 걸린다. 

그러고 서버를 짜고 돌아와서 보면 또 질려서 새 인테리어로 바꾸고 싶어지고

돈이 안 드니까 일단 바꾸고

 

그러니까 클라이언트 와꾸부터 잡기 시작하면 한도 끝도 없어질 것 같았다. 

그리고 유저 화면 대강 그려서 스펙 뽑았으면 서버부터 짜는 게 맞다. 

 

Spring Boot 3.2에 Kotlin을 물려서 쓸 거다.

그리고 맞다. 

나는 코틀린을 아직도 못 쓴다. 

취업 이후로 내 서버 개발 능력은 0으로 무한히 수렴하는 중이다. 
대신 기획이나 사업 부서랑 맨날 싸우면서도 저녁에 같이 술 마시러 다닐 수 있는 방법은 상황 따라 10개씩 말해 줄 수 있다. 대신 싸워드리는 알바 있었으면 좋겠다. 

연락 주세요. 

 

코드 짜는 건 참 좋은데 지피티한테 시킬 수가 없다. 

지피티가 어떤 dependency를 무슨 버전으로 물려뒀는지 내가 인지하고 있어야 하는데 나는 독서를 후루룩하는 편이라 지피티가 말해 준 것도 사실 다 안 읽고 요점만 대강 뽑는다. 

AI가 뽑아 준 문장은 0kcal 독서 같은 거라 집중하고 싶지 않다. 

그리고 얘한테 일일히 요구사항 불러 줄 시간에 내가 대강 짜는 게 더 빠르고 입맛에 맞다. 

 

 

 

물론 DDL을 엔티티로 옮기는 건 별개다. 

해 줘

 

 

 

오늘의 작업곡 시드다. 

적당히 빨라서 손도 빨라지고 자동 추천재생 걸어놓으면 one ok rock 거쳐서 sum41까지 넘어간다.

여기에 태그를 붙인다면 #ellegarden #japan #rock #2016summer #run 같은 게 붙을 거다 

 

단체 프로젝트라면 api 설계를 더 하고 들어가겠지만 이건 개인프로젝트니까 그러다가는 질린다. 

냅다 짜면서 막 바꿀 거니까 문서는 swagger로 뗀다. 

 

 

build.gradle.kts

plugins {
    kotlin("jvm") version "1.9.0"
    kotlin("plugin.spring") version "1.9.0"
    id("org.springframework.boot") version "3.2.0"
    id("io.spring.dependency-management") version "1.1.7"
    kotlin("plugin.jpa") version "1.9.25"
    kotlin("kapt") version "1.9.0"
}

allOpen {
    annotation("jakarta.persistence.Entity")
    annotation("jakarta.persistence.Embeddable")
    annotation("jakarta.persistence.MappedSuperclass")
}

group = "com.gojung"
version = "0.0.1-SNAPSHOT"

val querydslVersion = "5.0.0" // 최신 버전 확인 후 설정

java {
    toolchain {
       languageVersion = JavaLanguageVersion.of(17)
    }
}

configurations {
    compileOnly {
       extendsFrom(configurations.annotationProcessor.get())
    }
}

repositories {
    mavenCentral()
}

dependencies {
    // Spring Boot Web + Validation
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-validation")

    // Data
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    runtimeOnly("org.postgresql:postgresql")
    // querydsl
    implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
    implementation("com.querydsl:querydsl-apt:5.0.0:jakarta")
    implementation("jakarta.persistence:jakarta.persistence-api")
    implementation("jakarta.annotation:jakarta.annotation-api")

    kapt("com.querydsl:querydsl-apt:5.0.0:jakarta")
    kapt("org.springframework.boot:spring-boot-configuration-processor")


    // H2 for local
    runtimeOnly("com.h2database:h2")

    // Kotlin support
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

    // lombok
    compileOnly("org.projectlombok:lombok")
    annotationProcessor("org.projectlombok:lombok")

    // YouTube API 호출을 위한 HTTP 클라이언트
    implementation("org.springframework.boot:spring-boot-starter-webflux") // WebClient 사용 목적

    //swagger
    implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0")



    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
    testImplementation("org.mockito.kotlin:mockito-kotlin:4.0.0")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.withType<JavaCompile> {
    options.compilerArgs.add("-Aquerydsl.suppressAll=true") // 경고 표시를 숨길 수 있습니다
}

kotlin {
    compilerOptions {
       freeCompilerArgs.addAll("-Xjsr305=strict")
    }
}

allOpen {
    annotation("jakarta.persistence.Entity")
    annotation("jakarta.persistence.MappedSuperclass")
    annotation("jakarta.persistence.Embeddable")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

 

딱 필요한 것만 넣어두고 나중에 물리기

 

Kotlin 문법상 생성자 주입은 다음과 같은 식이다. 

불변이면 val이고 변수로 쓸 거면 var인데 생성자 주입할 때는 당연히 val로 들어간다. 

 

@RequestMapping("/api/v1/media")
@RestController
class MediaController(
    private val mediaService: MediaService
) {
	... Controller 
}

 

함수는 이렇게 작성한다. 

@Service
class MediaService(
    private val mediaRepository: MediaRepository
) {

    fun findAllMedia() : List<Media> {
        return mediaRepository.findAll();
    }

}

 

만일 리턴에 null을 허용한다면 리턴 타입 뒤에 물음표만 달아주면 된다. 

대강 신나보이고 좋다고 생각한다. 

 

Entity랑 repository도 작성했고 기본적인 엔드포인트도 몇 개 파 줬다. 

로컬에서는 인메모리db를 쓰고 있는데 솔직히 도커로 올려서 쓰는 게 취향이고.. 매번 테스트 데이터 넣어주기 귀찮으니까 flyway를 물리긴 해야 하지만... 좀 귀찮다. 

혼자 프로젝트 할 때는 ADHD처럼 굴어도 돼서 좋다.

이제는 youtube data api를 조금 파 봐야 할 것 같다. 

반응형

댓글