새길은 2025 새만금 공공데이터 공모전 신규데이터 생산 부문에 참가한 웹 개발 프로젝트 출품작입니다.
사용자 맞춤형 문화 콘텐츠를 추천하고, 결과를 지도에 시각화하는 설문조사 데이터 수집 플랫폼입니다.
Java의 가상 스레드로 단일 프로세스에서 데이터 수집과 소비를 병렬 처리하며, 전략 패턴과 팩토리 메소드 패턴으로 유연성과 확장성을 갖췄습니다.
전체 구조는 Spring Boot의 DI / 비동기 처리를 기반으로 한 경량화된 저비용 & 고성능 서버 아키텍처를 지향합니다.
- 🌐 배포 링크 : 설문조사 참여하기
- 🛠️ 사용 스택 : Java 21, Spring Boot, Spring Data JPA, Spring Caffeine Cache, JUnit5, JMH, MySQL, Docker, Github Actions, AWS, CLOVA Studio 임베딩v1
- 👥 참여 인원 : 총 4명 (BE 1, FE 2, UX/UI 1)
- ⏳ 개발 기간 : 2025.06 ~ 2025.07
- 📖 개발 일지 : 노션 참고
- 단일 서버 프로세스 내에서 데이터 수집 파이프라인과 소비 처리를 멀티 스레드로 구현
- API 호출 및 웹 크롤링 기반의 데이터 수집기 할당에 팩토리 메소드 패턴 적용
- 유실율, 변환 속도를 고려한 임베딩 벡터 Float값 Byte 타입 변환 저장
- 데이터 수집 스레드 상태 모니터링 및 수집기별 결과 디스코드 알람봇 구축
- 수집 데이터 갱신 과정에서 데이터 조회 및 소비 안전을 위한 스왑 뷰 전략 도입
- 서비스 기능별 단위 테스트코드 작성 100% 달성
- 배포 시, 외부 API 및 크롤링 사전 연결 점검 테스트 구축
- JMH 성능 계측으로 아키텍처 설계 근거 확보 및 처리 효율 3.5배 향상
- Dockerhub와 AWS EC2 인스턴스 연동 처리
- Github Actions 스크립트 기반 테스트 및 배포 자동화
- AWS IAM 권한 위임 스크립트를 통한 CI/CD 과정에서 EC2 IP 보안 확보
펼쳐보기📁
src
├── jmh # 자바 벤치마킹 하네스
│ └── java
│ └── org
│ └── jun
│ └── saemangeum
│ └── benchmark
│ └── process
│ ├── ContentCollectServiceBenchmark.java # 데이터 수집 벤치마크
│ └── MockContentCollectServiceBenchmark.java
├── main # 메인 애플리케이션
│ ├── java
│ │ └── org
│ │ └── jun
│ │ └── saemangeum
│ │ ├── SaemangeumApplication.java
│ │ ├── consume # 데이터 소비 3계층 아키텍처
│ │ │ ├── config
│ │ │ │ └── CoordinateConfig.java
│ │ │ ├── controller
│ │ │ │ └── SurveyController.java
│ │ │ ├── domain
│ │ │ │ ├── dto
│ │ │ │ │ ├── AverageRequest.java
│ │ │ │ │ ├── Coordinate.java
│ │ │ │ │ ├── KakaoResponse.java
│ │ │ │ │ ├── RecommendationResponse.java
│ │ │ │ │ ├── SurveyCreateRequest.java
│ │ │ │ │ └── SurveyUpdateRequest.java
│ │ │ │ ├── entity
│ │ │ │ │ ├── RecommendationLog.java
│ │ │ │ │ └── Survey.java
│ │ │ │ └── swap
│ │ │ │ ├── ContentView.java
│ │ │ │ └── VectorView.java
│ │ │ ├── repository
│ │ │ │ ├── entity
│ │ │ │ │ ├── RecommendationLogRepository.java
│ │ │ │ │ └── SurveyRepository.java
│ │ │ │ └── swap
│ │ │ │ ├── ContentViewRepository.java
│ │ │ │ └── VectorViewRepository.java
│ │ │ ├── service
│ │ │ │ ├── application
│ │ │ │ │ └── SurveyRecommendationService.java
│ │ │ │ ├── domain
│ │ │ │ │ ├── RecommendationLogService.java
│ │ │ │ │ └── SurveyService.java
│ │ │ │ ├── strategy
│ │ │ │ │ ├── EmbeddingVectorStrategy.java
│ │ │ │ │ ├── StrategyContextHolder.java
│ │ │ │ │ ├── TableEmbeddingVectorStrategy.java
│ │ │ │ │ └── ViewEmbeddingVectorStrategy.java
│ │ │ │ └── swap
│ │ │ │ └── SwapViewService.java
│ │ │ └── util
│ │ │ ├── CoordinateCalculator.java
│ │ │ └── ViewJdbcUtil.java
│ │ │
│ │ ├── global # 공통 도메인 및 설정정보
│ │ │ ├── cache
│ │ │ │ ├── CacheExpirePolicy.java
│ │ │ │ ├── CacheNames.java
│ │ │ │ └── CacheType.java
│ │ │ ├── config
│ │ │ │ ├── CacheConfig.java
│ │ │ │ ├── GlobalExceptionHandler.java
│ │ │ │ ├── Initializer.java
│ │ │ │ └── WebConfig.java
│ │ │ ├── controller
│ │ │ │ └── DeployController.java
│ │ │ ├── domain
│ │ │ │ ├── Category.java
│ │ │ │ ├── CollectSource.java
│ │ │ │ ├── Content.java
│ │ │ │ ├── Count.java
│ │ │ │ ├── IContent.java
│ │ │ │ └── Vector.java
│ │ │ ├── exception
│ │ │ │ ├── ClientIdException.java
│ │ │ │ ├── ErrorResponse.java
│ │ │ │ └── SatisfactionsException.java
│ │ │ ├── repository
│ │ │ │ ├── ContentRepository.java
│ │ │ │ ├── CountRepository.java
│ │ │ │ └── VectorRepository.java
│ │ │ └── service
│ │ │ ├── ContentService.java
│ │ │ ├── CountService.java
│ │ │ └── VectorService.java
│ │ │
│ │ └── pipeline # 데이터 수집 및 전처리 파이프라인
│ │ ├── application
│ │ │ ├── alarm
│ │ │ │ ├── AlarmBuilder.java
│ │ │ │ └── AlarmProcess.java
│ │ │ ├── collect
│ │ │ │ ├── api
│ │ │ │ │ ├── GimjeCultureCollector.java
│ │ │ │ │ ├── GunsanCultureCollector.java
│ │ │ │ │ ├── SmgEventCollector.java
│ │ │ │ │ └── SmgFestivalCollector.java
│ │ │ │ ├── base
│ │ │ │ │ ├── CheckedSupplier.java
│ │ │ │ │ ├── CrawlingCollector.java
│ │ │ │ │ ├── OpenApiCollector.java
│ │ │ │ │ └── Refiner.java
│ │ │ │ └── crawl
│ │ │ │ ├── ArchipelagoCollector.java
│ │ │ │ ├── BuanCultureCollector.java
│ │ │ │ ├── City.java
│ │ │ │ ├── CityTourCollector.java
│ │ │ │ ├── GunsanFestivalCollector.java
│ │ │ │ └── SeawallTourCollector.java
│ │ │ ├── dto
│ │ │ │ ├── ErrorResponse.java
│ │ │ │ ├── Event.java
│ │ │ │ ├── EventResponse.java
│ │ │ │ ├── Festival.java
│ │ │ │ ├── FestivalResponse.java
│ │ │ │ ├── GimjeCulture.java
│ │ │ │ ├── GimjeCultureResponse.java
│ │ │ │ ├── GunsanCulture.java
│ │ │ │ ├── GunsanCultureResponse.java
│ │ │ │ └── RefinedDataDTO.java
│ │ │ ├── schedule
│ │ │ │ └── PipelineScheduler.java
│ │ │ ├── service
│ │ │ │ ├── ContentCollectService.java
│ │ │ │ ├── DataCountUpdateService.java
│ │ │ │ ├── EmbeddingVectorService.java
│ │ │ │ └── PipelineService.java
│ │ │ └── util
│ │ │ ├── TitleDuplicateChecker.java
│ │ │ └── VectorCalculator.java
│ │ ├── domain
│ │ │ ├── entity
│ │ │ │ ├── Alarm.java
│ │ │ │ ├── AlarmMessage.java
│ │ │ │ ├── AlarmPayload.java
│ │ │ │ └── AlarmType.java
│ │ │ └── service
│ │ │ └── AlarmService.java
│ │ ├── infrastructure
│ │ │ ├── api
│ │ │ │ ├── OpenApiClient.java
│ │ │ │ └── VectorClient.java
│ │ │ ├── config
│ │ │ │ ├── AsyncConfig.java
│ │ │ │ ├── DiscordConfig.java
│ │ │ │ ├── OpenApiConfig.java
│ │ │ │ ├── SeleniumConfig.java
│ │ │ │ └── VectorConfig.java
│ │ │ ├── dto
│ │ │ │ ├── EmbeddingJob.java
│ │ │ │ ├── EmbeddingRequest.java
│ │ │ │ └── EmbeddingResponse.java
│ │ │ └── queue
│ │ │ ├── EmbeddingJobQueue.java
│ │ │ ├── EmbeddingWorker.java
│ │ │ └── EmbeddingWorkerService.java
│ │ └── presentation
│ │ └── DiscordMessageAlarm.java
│ │
│ └── resources
│ └── application.yml # 서버 설정 프로퍼티
│
└── test # 단위 테스트 및 통합 테스트
└── java
└── org
└── jun
└── saemangeum
├── SaemangeumApplicationTests.java
├── collect
│ ├── CrawlingCollectorTest.java
│ ├── FallbackTest.java
│ ├── OpenApiCollectorTest.java
│ └── TitleDuplicateCheckerTest.java
├── connect
│ ├── CoordinateTest.java
│ ├── EmbeddingTest.java
│ ├── MonitoringTest.java
│ ├── OpenApiTest.java
│ └── SeleniumTest.java
└── service
├── ContentCollectServiceTest.java
├── DataUpdateTest.java
├── StrategyConcurrencyTest.java
├── StrategyPatternTest.java
└── ViewFunctionTest.java