diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..307c7a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/api-app/app/src/main/resources/application-local.yml diff --git a/api-app/README.md b/api-app/README.md index ae919d9..78730a1 100644 --- a/api-app/README.md +++ b/api-app/README.md @@ -2,10 +2,15 @@ > Dabeeo 변화 탐지 시스템을 위한 백엔드 API 서버 +--- + ## 📋 프로젝트 소개 -**dabeeo-detection-api**는 공간 데이터의 변화를 탐지하고 관리하기 위한 RESTful API 서버입니다. -JTS(Java Topology Suite)를 활용한 지오메트리 데이터 처리와 PostgreSQL을 통한 공간 데이터 저장을 지원합니다. +**dabeeo-detection-api** + +본 프로젝트는 영상 데이터를 기반으로 변화 탐지를 수행하는 시스템의 백엔드 API 서버입니다. + +--- ## 🛠️ 기술 스택 @@ -20,8 +25,8 @@ JTS(Java Topology Suite)를 활용한 지오메트리 데이터 처리와 Postgr | **Connection Pool** | HikariCP | | **Build Tool** | Gradle 8.x | | **Monitoring** | Spring Boot Actuator | -| **Container** | Docker + Docker Compose | -| **CI/CD** | Jenkins | + +--- ## 🚀 시작하기 @@ -32,16 +37,20 @@ JTS(Java Topology Suite)를 활용한 지오메트리 데이터 처리와 Postgr - Gradle 8.x (또는 Gradle Wrapper 사용) - Docker & Docker Compose (선택사항) +--- + ### 로컬 환경 설정 -1. **저장소 클론** -``` +#### 1. 저장소 클론 + https://kamco.git.gs.dabeeo.com/MVPTeam/DABEEO-DETECTION-APPLICATION.git -``` -2. **데이터베이스 설정** +--- -PostgreSQL 데이터베이스를 준비하고 `src/main/resources/application-local.yml`을 생성: +#### 2. 데이터베이스 설정 + +PostgreSQL 데이터베이스를 준비하고 +`src/main/resources/application-local.yml` 파일을 생성합니다. ```yaml spring: @@ -54,9 +63,11 @@ spring: password: your_password ``` -> **참고**: `application-local.yml`은 `.gitignore`에 포함되어 있어 Git에 커밋되지 않습니다. +> ⚠️ `application-local.yml`은 `.gitignore`에 포함되어 Git에 커밋되지 않습니다. -3. **빌드 및 실행** +--- + +#### 3. 빌드 및 실행 ```bash # 빌드 @@ -65,56 +76,145 @@ spring: # 실행 (local 프로파일) ./gradlew bootRun -# 또는 JAR 파일로 실행 +# 또는 JAR 실행 java -jar build/libs/ROOT.jar ``` -서버가 시작되면 http://localhost:8080 에서 접근 가능합니다. +서버 실행 후: +http://localhost:8080 + +--- ## ⚙️ 프로파일 설정 -애플리케이션은 환경별로 다른 설정을 사용합니다: - | 프로파일 | 환경 | 포트 | 설정 파일 | |---------|------|------|-----------| -| `local` | 로컬 개발 | 8080 | `application.yml` (기본) | -| `dev` | 개발 서버 | 7100 | `application-dev.yml` | +| `local` | 로컬 개발 | 8080 | `application.yml` | +| `dev` | 개발 서버 | 8080 | `application-dev.yml` | | `prod` | 운영 서버 | 8080 | `application-prod.yml` | -### 프로파일 활성화 +--- + +### 프로파일 실행 방법 ```bash -# 개발 환경으로 실행 +# dev 실행 ./gradlew bootRun --args='--spring.profiles.active=dev' -# JAR 실행 시 +# jar 실행 java -jar build/libs/ROOT.jar --spring.profiles.active=dev ``` +--- + ## 🧪 테스트 ```bash -# 전체 테스트 실행 +# 전체 테스트 ./gradlew test -# 특정 테스트 클래스 실행 +# 특정 테스트 ./gradlew test --tests com.kamco.cd.kamcoback.KamcoBackApplicationTests -# 테스트 리포트 확인 +# 리포트 확인 open build/reports/tests/test/index.html ``` +--- + ## 📦 빌드 ```bash -# 전체 빌드 (테스트 포함) +# 전체 빌드 ./gradlew clean build -# 테스트 제외 빌드 (CI/CD에서 사용) +# 테스트 제외 ./gradlew clean build -x test -# JAR 파일만 생성 +# JAR 생성 ./gradlew bootJar ``` -빌드된 JAR 파일: `build/libs/ROOT.jar` +빌드 결과: + +build/libs/ROOT.jar + +--- + +## 📁 프로젝트 구조 +### (2026-04-08 기준) + +```bash +api-app +├── app +│ └── src/main/java/com/cd/detection +│ ├── DabeeoDetectionApiApplication.java +│ ├── config +│ ├── domain +│ │ ├── imagery +│ │ ├── labeling +│ │ ├── label +│ │ ├── model +│ │ ├── inference +│ │ ├── system +│ │ └── log +│ └── entity +│ +├── infrastructure-db-postgres +│ └── build.gradle +│ +├── build.gradle +├── settings.gradle +├── gradle/ +└── gradlew +``` + +--- + +## 🧩 아키텍처 설계 + +본 프로젝트는 API 모듈과 DB 모듈을 분리한 멀티 모듈 구조를 사용합니다. + +--- + +### 📌 모듈 구성 + +| 모듈 | 설명 | +|------|------| +| api-app | API 및 비즈니스 로직 | +| infrastructure-db-postgres | DB 접근 및 설정 | + +--- + +## 🗄️ Database Module 분리 전략 + +DB 관련 로직은 별도의 모듈(infrastructure-db-postgres)로 분리되어 관리됩니다. + +--- + +### 🎯 분리 목적 + +- 기술 의존성 분리 +- DB 교체 용이성 +- 관심사 분리 +- 확장성 확보 + +--- + +### 🔄 처리 흐름 + +Controller → Service → Repository → DB + +--- + +### 💡 설계 포인트 + +- API 모듈은 DB 기술에 직접 의존하지 않음 +- DB 관련 코드는 별도 모듈에서 관리 +- DB 변경 시 API 코드 수정 최소화 + +--- + +## 🔥 한 줄 요약 + +DB 기술 변경에 유연하게 대응하기 위해 persistence 레이어를 모듈로 분리했습니다. diff --git a/api-app/app/build.gradle b/api-app/app/build.gradle new file mode 100644 index 0000000..60ebeb3 --- /dev/null +++ b/api-app/app/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'org.springframework.boot' + id 'io.spring.dependency-management' + id 'java' +} + +dependencies { + // postgres + implementation project(':infrastructure-db-postgres') + + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' + implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta" + + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + + annotationProcessor 'org.projectlombok:lombok' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} + +bootJar { + archiveFileName = 'ROOT.jar' +} \ No newline at end of file diff --git a/api-app/src/main/java/com/cd/detection/DabeeoDetectionApiApplication.java b/api-app/app/src/main/java/com/cd/detection/DabeeoDetectionApiApplication.java similarity index 100% rename from api-app/src/main/java/com/cd/detection/DabeeoDetectionApiApplication.java rename to api-app/app/src/main/java/com/cd/detection/DabeeoDetectionApiApplication.java diff --git a/api-app/src/main/java/com/cd/detection/config/QuerydslConfig.java b/api-app/app/src/main/java/com/cd/detection/config/QuerydslConfig.java similarity index 100% rename from api-app/src/main/java/com/cd/detection/config/QuerydslConfig.java rename to api-app/app/src/main/java/com/cd/detection/config/QuerydslConfig.java diff --git a/api-app/src/main/resources/application-dev.yml b/api-app/app/src/main/resources/application-dev.yml similarity index 100% rename from api-app/src/main/resources/application-dev.yml rename to api-app/app/src/main/resources/application-dev.yml diff --git a/api-app/src/main/resources/application-prod.yml b/api-app/app/src/main/resources/application-prod.yml similarity index 100% rename from api-app/src/main/resources/application-prod.yml rename to api-app/app/src/main/resources/application-prod.yml diff --git a/api-app/src/main/resources/application.yml b/api-app/app/src/main/resources/application.yml similarity index 100% rename from api-app/src/main/resources/application.yml rename to api-app/app/src/main/resources/application.yml diff --git a/api-app/build.gradle b/api-app/build.gradle index 1555d4f..f2de0e6 100644 --- a/api-app/build.gradle +++ b/api-app/build.gradle @@ -1,12 +1,11 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.5.7' - id 'io.spring.dependency-management' version '1.1.7' + id 'io.spring.dependency-management' version '1.1.7' apply false + id 'org.springframework.boot' version '3.5.7' apply false } group = 'com.cd.detection' version = '0.0.1-SNAPSHOT' -description = 'dabeeo-detection-api' java { toolchain { @@ -18,30 +17,10 @@ repositories { mavenCentral() } -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' +subprojects { + apply plugin: 'java' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' - - implementation "com.querydsl:querydsl-jpa:5.0.0:jakarta" - - compileOnly 'org.projectlombok:lombok' - developmentOnly 'org.springframework.boot:spring-boot-devtools' - runtimeOnly 'org.postgresql:postgresql' - - annotationProcessor 'org.projectlombok:lombok' - annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" - annotationProcessor "jakarta.annotation:jakarta.annotation-api" - annotationProcessor "jakarta.persistence:jakarta.persistence-api" - - testImplementation 'org.springframework.boot:spring-boot-starter-test' -} - -tasks.named('test') { - useJUnitPlatform() -} - -bootJar { - archiveFileName = 'ROOT.jar' -} + repositories { + mavenCentral() + } +} \ No newline at end of file diff --git a/api-app/gradle/wrapper/gradle-wrapper.jar b/api-app/gradle/wrapper/gradle-wrapper.jar index d997cfc..1b33c55 100644 Binary files a/api-app/gradle/wrapper/gradle-wrapper.jar and b/api-app/gradle/wrapper/gradle-wrapper.jar differ diff --git a/api-app/gradle/wrapper/gradle-wrapper.properties b/api-app/gradle/wrapper/gradle-wrapper.properties index c61a118..ca025c8 100644 --- a/api-app/gradle/wrapper/gradle-wrapper.properties +++ b/api-app/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/api-app/gradlew b/api-app/gradlew index 739907d..23d15a9 100755 --- a/api-app/gradlew +++ b/api-app/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,6 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -171,6 +172,7 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -210,6 +212,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/api-app/gradlew.bat b/api-app/gradlew.bat index c4bdd3a..db3a6ac 100644 --- a/api-app/gradlew.bat +++ b/api-app/gradlew.bat @@ -70,10 +70,11 @@ goto fail :execute @rem Setup the command line +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/api-app/infrastructure-db-postgres/build.gradle b/api-app/infrastructure-db-postgres/build.gradle new file mode 100644 index 0000000..a2eb670 --- /dev/null +++ b/api-app/infrastructure-db-postgres/build.gradle @@ -0,0 +1,8 @@ +plugins { + id 'java' +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + runtimeOnly 'org.postgresql:postgresql' +} \ No newline at end of file diff --git a/api-app/settings.gradle b/api-app/settings.gradle index 7bdf791..c13a790 100644 --- a/api-app/settings.gradle +++ b/api-app/settings.gradle @@ -1 +1,4 @@ rootProject.name = 'dabeeo-detection-api' + +include 'app' +include 'infrastructure-db-postgres' \ No newline at end of file diff --git a/api-app/src/main/java/com/cd/detection/postgres/core/SampleCoreService.java b/api-app/src/main/java/com/cd/detection/postgres/core/SampleCoreService.java deleted file mode 100644 index 741cc20..0000000 --- a/api-app/src/main/java/com/cd/detection/postgres/core/SampleCoreService.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.cd.detection.postgres.core; - -import com.cd.detection.postgres.entity.SampleEntity; -import com.cd.detection.postgres.repository.sample.SampleRepository; -import com.cd.detection.sample.dto.SampleDto; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class SampleCoreService { - private final SampleRepository sampleRepository; - - public List getSampleList() { - return sampleRepository.getSampleList() - .stream() - .map(SampleEntity::toDto) - .toList(); - } -} diff --git a/api-app/src/main/java/com/cd/detection/postgres/entity/SampleEntity.java b/api-app/src/main/java/com/cd/detection/postgres/entity/SampleEntity.java deleted file mode 100644 index d096239..0000000 --- a/api-app/src/main/java/com/cd/detection/postgres/entity/SampleEntity.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.cd.detection.postgres.entity; - -import com.cd.detection.sample.dto.SampleDto; -import jakarta.persistence.*; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Table(name = "sample_table") -@Getter -@NoArgsConstructor -public class SampleEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - private String name; - - public SampleDto toDto() { - return new SampleDto( - this.id, - this.name); - } -} \ No newline at end of file diff --git a/api-app/src/main/java/com/cd/detection/postgres/repository/sample/SampleRepository.java b/api-app/src/main/java/com/cd/detection/postgres/repository/sample/SampleRepository.java deleted file mode 100644 index 0a75e9c..0000000 --- a/api-app/src/main/java/com/cd/detection/postgres/repository/sample/SampleRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.cd.detection.postgres.repository.sample; - -import com.cd.detection.postgres.entity.SampleEntity; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface SampleRepository extends JpaRepository, SampleRepositoryCustom { - -} diff --git a/api-app/src/main/java/com/cd/detection/postgres/repository/sample/SampleRepositoryCustom.java b/api-app/src/main/java/com/cd/detection/postgres/repository/sample/SampleRepositoryCustom.java deleted file mode 100644 index 8318206..0000000 --- a/api-app/src/main/java/com/cd/detection/postgres/repository/sample/SampleRepositoryCustom.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.cd.detection.postgres.repository.sample; - -import com.cd.detection.postgres.entity.SampleEntity; -import java.util.List; -import java.util.Optional; - -public interface SampleRepositoryCustom { - - // 조회 - Optional getSample(Long id); - - // 리스트 조회 - List getSampleList(); -} diff --git a/api-app/src/main/java/com/cd/detection/postgres/repository/sample/SampleRepositoryImpl.java b/api-app/src/main/java/com/cd/detection/postgres/repository/sample/SampleRepositoryImpl.java deleted file mode 100644 index 9cad202..0000000 --- a/api-app/src/main/java/com/cd/detection/postgres/repository/sample/SampleRepositoryImpl.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.cd.detection.postgres.repository.sample; - -import com.cd.detection.postgres.entity.QSampleEntity; -import com.cd.detection.postgres.entity.SampleEntity; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.util.List; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class SampleRepositoryImpl implements SampleRepositoryCustom { - - private final JPAQueryFactory queryFactory; - - @Override - public Optional getSample(Long id) { - QSampleEntity sample = QSampleEntity.sampleEntity; - return Optional.ofNullable(queryFactory.selectFrom(sample).where(sample.id.eq(id)).fetchOne()); - } - - @Override - public List getSampleList() { - QSampleEntity sample = QSampleEntity.sampleEntity; - return queryFactory.selectFrom(sample).fetch(); - } -} diff --git a/api-app/src/main/java/com/cd/detection/sample/SampleController.java b/api-app/src/main/java/com/cd/detection/sample/SampleController.java deleted file mode 100644 index 3fd3460..0000000 --- a/api-app/src/main/java/com/cd/detection/sample/SampleController.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.cd.detection.sample; - -import com.cd.detection.sample.dto.SampleDto; -import com.cd.detection.sample.service.SampleService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/sample") -public class SampleController { - private final SampleService sampleService; - - @Operation(summary = "샘플 목록 조회", description = "샘플 목록 조회") - @ApiResponses( - value = { - @ApiResponse( - responseCode = "200", - description = "조회 성공", - content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = SampleDto.class))), - @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), - @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) - }) - @GetMapping - public List getSampleList() { - return sampleService.getSampleList(); - } -} diff --git a/api-app/src/main/java/com/cd/detection/sample/dto/SampleDto.java b/api-app/src/main/java/com/cd/detection/sample/dto/SampleDto.java deleted file mode 100644 index b98a6b9..0000000 --- a/api-app/src/main/java/com/cd/detection/sample/dto/SampleDto.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cd.detection.sample.dto; - -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -public class SampleDto { - private Long id; - private String name; - - public SampleDto(Long id, String name) { - this.id = id; - this.name = name; - } -} diff --git a/api-app/src/main/java/com/cd/detection/sample/service/SampleService.java b/api-app/src/main/java/com/cd/detection/sample/service/SampleService.java deleted file mode 100644 index fd3ddd2..0000000 --- a/api-app/src/main/java/com/cd/detection/sample/service/SampleService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.cd.detection.sample.service; - -import com.cd.detection.postgres.core.SampleCoreService; -import com.cd.detection.postgres.repository.sample.SampleRepository; -import com.cd.detection.sample.dto.SampleDto; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -@Service -@RequiredArgsConstructor -public class SampleService { - - private final SampleCoreService sampleCoreService; - - public List getSampleList() { - return sampleCoreService.getSampleList(); - } -} diff --git a/api-app/src/main/resources/application-local.yml b/api-app/src/main/resources/application-local.yml deleted file mode 100644 index 8eaeef8..0000000 --- a/api-app/src/main/resources/application-local.yml +++ /dev/null @@ -1,5 +0,0 @@ -spring: - datasource: - url: jdbc:postgresql://localhost:5432/dabeeo_detection_dev - username: dabeeo_detection - password: 1234 \ No newline at end of file