Compare commits

..

2 Commits

Author SHA1 Message Date
8a24288f1e Merge remote-tracking branch 'origin/develop' into develop 2026-04-08 16:30:40 +09:00
49447c9065 패키지 구조 수정 및 추가 2026-04-08 16:30:28 +09:00
24 changed files with 189 additions and 236 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/api-app/app/src/main/resources/application-local.yml

View File

@@ -2,10 +2,15 @@
> Dabeeo 변화 탐지 시스템을 위한 백엔드 API 서버 > Dabeeo 변화 탐지 시스템을 위한 백엔드 API 서버
---
## 📋 프로젝트 소개 ## 📋 프로젝트 소개
**dabeeo-detection-api**는 공간 데이터의 변화를 탐지하고 관리하기 위한 RESTful API 서버입니다. **dabeeo-detection-api**
JTS(Java Topology Suite)를 활용한 지오메트리 데이터 처리와 PostgreSQL을 통한 공간 데이터 저장을 지원합니다.
본 프로젝트는 영상 데이터를 기반으로 변화 탐지를 수행하는 시스템의 백엔드 API 서버입니다.
---
## 🛠️ 기술 스택 ## 🛠️ 기술 스택
@@ -20,8 +25,8 @@ JTS(Java Topology Suite)를 활용한 지오메트리 데이터 처리와 Postgr
| **Connection Pool** | HikariCP | | **Connection Pool** | HikariCP |
| **Build Tool** | Gradle 8.x | | **Build Tool** | Gradle 8.x |
| **Monitoring** | Spring Boot Actuator | | **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 사용) - Gradle 8.x (또는 Gradle Wrapper 사용)
- Docker & Docker Compose (선택사항) - Docker & Docker Compose (선택사항)
---
### 로컬 환경 설정 ### 로컬 환경 설정
1. **저장소 클론** #### 1. 저장소 클론
```
https://kamco.git.gs.dabeeo.com/MVPTeam/DABEEO-DETECTION-APPLICATION.git 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 ```yaml
spring: spring:
@@ -54,9 +63,11 @@ spring:
password: your_password password: your_password
``` ```
> **참고**: `application-local.yml`은 `.gitignore`에 포함되어 있어 Git에 커밋되지 않습니다. > ⚠️ `application-local.yml`은 `.gitignore`에 포함되어 Git에 커밋되지 않습니다.
3. **빌드 및 실행** ---
#### 3. 빌드 및 실행
```bash ```bash
# 빌드 # 빌드
@@ -65,56 +76,145 @@ spring:
# 실행 (local 프로파일) # 실행 (local 프로파일)
./gradlew bootRun ./gradlew bootRun
# 또는 JAR 파일로 실행 # 또는 JAR 실행
java -jar build/libs/ROOT.jar java -jar build/libs/ROOT.jar
``` ```
서버가 시작되면 http://localhost:8080 에서 접근 가능합니다. 서버 실행 후:
http://localhost:8080
---
## ⚙️ 프로파일 설정 ## ⚙️ 프로파일 설정
애플리케이션은 환경별로 다른 설정을 사용합니다:
| 프로파일 | 환경 | 포트 | 설정 파일 | | 프로파일 | 환경 | 포트 | 설정 파일 |
|---------|------|------|-----------| |---------|------|------|-----------|
| `local` | 로컬 개발 | 8080 | `application.yml` (기본) | | `local` | 로컬 개발 | 8080 | `application.yml` |
| `dev` | 개발 서버 | 7100 | `application-dev.yml` | | `dev` | 개발 서버 | 8080 | `application-dev.yml` |
| `prod` | 운영 서버 | 8080 | `application-prod.yml` | | `prod` | 운영 서버 | 8080 | `application-prod.yml` |
### 프로파일 활성화 ---
### 프로파일 실행 방법
```bash ```bash
# 개발 환경으로 실행 # dev 실행
./gradlew bootRun --args='--spring.profiles.active=dev' ./gradlew bootRun --args='--spring.profiles.active=dev'
# JAR 실행 # jar 실행
java -jar build/libs/ROOT.jar --spring.profiles.active=dev java -jar build/libs/ROOT.jar --spring.profiles.active=dev
``` ```
---
## 🧪 테스트 ## 🧪 테스트
```bash ```bash
# 전체 테스트 실행 # 전체 테스트
./gradlew test ./gradlew test
# 특정 테스트 클래스 실행 # 특정 테스트
./gradlew test --tests com.kamco.cd.kamcoback.KamcoBackApplicationTests ./gradlew test --tests com.kamco.cd.kamcoback.KamcoBackApplicationTests
# 테스트 리포트 확인 # 리포트 확인
open build/reports/tests/test/index.html open build/reports/tests/test/index.html
``` ```
---
## 📦 빌드 ## 📦 빌드
```bash ```bash
# 전체 빌드 (테스트 포함) # 전체 빌드
./gradlew clean build ./gradlew clean build
# 테스트 제외 빌드 (CI/CD에서 사용) # 테스트 제외
./gradlew clean build -x test ./gradlew clean build -x test
# JAR 파일만 생성 # JAR 생성
./gradlew bootJar ./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 레이어를 모듈로 분리했습니다.

34
api-app/app/build.gradle Normal file
View File

@@ -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'
}

View File

@@ -1,12 +1,11 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.5.7' id 'io.spring.dependency-management' version '1.1.7' apply false
id 'io.spring.dependency-management' version '1.1.7' id 'org.springframework.boot' version '3.5.7' apply false
} }
group = 'com.cd.detection' group = 'com.cd.detection'
version = '0.0.1-SNAPSHOT' version = '0.0.1-SNAPSHOT'
description = 'dabeeo-detection-api'
java { java {
toolchain { toolchain {
@@ -18,30 +17,10 @@ repositories {
mavenCentral() mavenCentral()
} }
dependencies { subprojects {
implementation 'org.springframework.boot:spring-boot-starter-web' apply plugin: 'java'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' repositories {
mavenCentral()
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'
} }

Binary file not shown.

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists 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 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

7
api-app/gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015 the original authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (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. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -114,6 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM. # 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 # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -210,6 +212,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@" "$@"

3
api-app/gradlew.bat vendored
View File

@@ -70,10 +70,11 @@ goto fail
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=
@rem Execute Gradle @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 :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell

View File

@@ -0,0 +1,8 @@
plugins {
id 'java'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'org.postgresql:postgresql'
}

View File

@@ -1 +1,4 @@
rootProject.name = 'dabeeo-detection-api' rootProject.name = 'dabeeo-detection-api'
include 'app'
include 'infrastructure-db-postgres'

View File

@@ -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<SampleDto> getSampleList() {
return sampleRepository.getSampleList()
.stream()
.map(SampleEntity::toDto)
.toList();
}
}

View File

@@ -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);
}
}

View File

@@ -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<SampleEntity, Long>, SampleRepositoryCustom {
}

View File

@@ -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<SampleEntity> getSample(Long id);
// 리스트 조회
List<SampleEntity> getSampleList();
}

View File

@@ -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<SampleEntity> getSample(Long id) {
QSampleEntity sample = QSampleEntity.sampleEntity;
return Optional.ofNullable(queryFactory.selectFrom(sample).where(sample.id.eq(id)).fetchOne());
}
@Override
public List<SampleEntity> getSampleList() {
QSampleEntity sample = QSampleEntity.sampleEntity;
return queryFactory.selectFrom(sample).fetch();
}
}

View File

@@ -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<SampleDto> getSampleList() {
return sampleService.getSampleList();
}
}

View File

@@ -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;
}
}

View File

@@ -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<SampleDto> getSampleList() {
return sampleCoreService.getSampleList();
}
}

View File

@@ -1,5 +0,0 @@
spring:
datasource:
url: jdbc:postgresql://localhost:5432/dabeeo_detection_dev
username: dabeeo_detection
password: 1234