Skip to content

프로젝트 설정 및 초기화 (#13)#43

Merged
polynomeer merged 4 commits intomainfrom
feature/13-project-setting
Aug 9, 2025
Merged

프로젝트 설정 및 초기화 (#13)#43
polynomeer merged 4 commits intomainfrom
feature/13-project-setting

Conversation

@polynomeer
Copy link
Collaborator

@polynomeer polynomeer commented Jul 11, 2025

관련 이슈

Close #7, #13


작업 내용 요약

  • 프로젝트 기본 설정

프로젝트 구조 (멀티모듈 구조)

romanticker/
├── app-api-price/         # 시세 조회용 API 서버
├── app-api-ticker/        # 관심종목/알림 API 서버
├── app-batch-collector/   # 시세 수집 및 배치 작업 서버
│
├── domain-price/          # Price 도메인 모델 및 서비스 인터페이스
├── domain-ticker/         # Ticker 도메인 모델 및 관심종목/알림 도메인
│
├── infra-redis/           # Redis 기반 캐시, 세션, 알림 큐 연동
├── infra-timescaledb/     # TimescaleDB 기반 시계열 데이터 저장소 구현
├── infra-external/        # 외부 시세 API 연동
│
├── shared-common/         # 공통 응답, 예외, 로깅, DTO
└── shared-config/         # 공통 설정 (Web, CORS, Swagger, Security 등)

의존성 흐름

단방향 의존성 계층 구조

          [shared:common]
                 ▲
          [domain:*]
                 ▲
          [infras:*]
                 ▲
      ┌──────────────────────┐
      │                      │ 
  [app:api]             [app:batch]
  • shared: 모든 모듈에서 사용 가능한 공통 유틸, DTO, 설정
  • domain: 비즈니스 로직 인터페이스 정의, 외부 기술과 독립적
  • infra: 외부 시스템 연동, JPA/Redis 구현체 제공
  • app: 실행 가능한 Spring Boot Application (API, 배치, 웹소켓 등)

실행 흐름 예시

예: app-api-price

PriceController
↓
PriceService (domain)
↓
PriceRepository (interface)
↓
PriceRepositoryImpl (infra-timescaledb)


멀티모듈 설계 목적

  • 관심사 분리
    비즈니스 로직, 외부 연동, 실행 환경을 명확히 분리하여 유지보수성 향상
  • 독립 배포 가능성 확보
    각 실행 모듈(app-*)은 개별적으로 Docker화 및 스케일 가능
  • 공통 정책 일관성 확보
    응답 포맷, 예외 처리, 로깅 등을 shared-common에서 통합 관리

기술 스택

  • Java 21
  • Spring Boot 3.4.7
  • Spring Data JPA
  • Redis
  • TimescaleDB
  • Gradle Kotlin DSL (멀티모듈 구성)

참고자료

@polynomeer polynomeer changed the title Feature/13 project setting 프로젝트 설정 및 초기화 (#13) Jul 12, 2025
@polynomeer polynomeer requested a review from if-else-f July 12, 2025 04:59
@polynomeer polynomeer self-assigned this Jul 12, 2025
@polynomeer polynomeer added documentation Improvements or additions to documentation feature New feature or request labels Jul 12, 2025
dependencies {
implementation(project(":shared:shared-common"))
implementation(project(":shared:shared-config"))
implementation("org.springframework.boot:spring-boot-starter-web")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

web 은 무엇일까요? 왜 사용하셨나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spring-boot-starter-web은 Spring Boot 프로젝트에서 웹 애플리케이션을 빠르게 개발하기 위한 필수 의존성 모음입니다. 주로 REST API나 MVC 웹 애플리케이션을 개발할 때 사용됩니다.

이 스타터를 프로젝트에 추가하면, 스프링 부트가 클래스패스에 있는 라이브러리들을 분석해서 자동 설정해 줍니다. 예를 들어, DispatcherServlet과 같은 웹 MVC 설정이나 톰캣 서버 설정 등을 알아서 해줘서 개발자는 비즈니스 로직에만 집중할 수 있게 됩니다.

실제로 spring-boot-starter-web는 여러 라이브러리를 포함한 스타터이며, 주요 구성은 다음과 같습니다.

image
  • spring-web: 기본적인 웹 기능을 지원합니다. HTTP, REST를 지원합니다.
  • spring-webmvc: Spring MVC 프레임워크입니다. 컨트롤러, 뷰 등을 지원합니다.
  • spring-boot-starter: 공통적인 의존성으로 로그 등이 포함됩니다.
  • spring-boot-starter-json: JSON 직렬화/역직렬화를 지원합니다.
  • spring-boot-starter-tomcat: spring-boot-starter-web에서 기본 내장 웹서버입니다.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 annotation 은 무엇을 하는건가요?

Copy link
Collaborator Author

@polynomeer polynomeer Aug 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SpringBootApplication은 Spring Boot 애플리케이션의 시작 클래스에 붙이는 대표 어노테이션으로, 세 가지 기능을 합친 복합 어노테이션입니다.

@SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
  • @SpringBootConfiguration에는 @configuration이 포함되며, 해당 클래스가 스프링 설정 클래스임을 의미합니다. 컨텍스트에 추가 빈을 등록하거나 추가 구성 클래스를 가져올 수 있습니다.
  • @EnableAutoConfiguration는 Spring Boot의 자동 설정을 활성화하며, 클래스패스에 있는 라이브러리와 설정을 기반으로 자동 설정을 수행합니다.
  • @ComponentScan: 현재 패키지를 포함한 하위 패키지를 스캔하여 @Component, @Service, @Repository, @Controller 등이 붙은 클래스를 자동으로 빈으로 등록합니다.

동작 순서를 간단히 요약하면 다음과 같습니다.

  1. 어노테이션이 적용된 클래스가 있는 패키지 및 하위 패키지를 스캔합니다. 이때 @service, @repository, @controller와 다른 Spring 컴포넌트를 찾습니다.
  2. 자동 설정을 실행합니다. 클래스 패스를 스캔하고 애플리케이션에 정의된 의존성과 속정을 기반으로 빈을 자동으로 구성합니다. 예를 들어, 데이터 소스에 대한 의존성을 포함하면 Spring Boot가 자동으로 데이터 소스 빈을 구성합니다.
  3. 설정 클래스 적용합니다. 수동 Bean 정의와 자동 구성 Bean이 합쳐져서 ApplicationContext를 완성하게 됩니다.
  4. SpringApplication.run() 호출 시 내장 서버(톰캣 등)까지 자동 실행됩니다.

@if-else-f
Copy link

if-else-f commented Jul 22, 2025

romanticker/
├── app-api-price/         # 시세 조회용 API 서버
├── app-api-ticker/        # 관심종목/알림 API 서버
├── app-batch-collector/   # 시세 수집 및 배치 작업 서버
│
├── domain-price/          # Price 도메인 모델 및 서비스 인터페이스
├── domain-ticker/         # Ticker 도메인 모델 및 관심종목/알림 도메인
│
├── infra-redis/           # Redis 기반 캐시, 세션, 알림 큐 연동
├── infra-timescaledb/     # TimescaleDB 기반 시계열 데이터 저장소 구현
├── infra-external/        # 외부 시세 API 연동
│
├── shared-common/         # 공통 응답, 예외, 로깅, DTO
└── shared-config/         # 공통 설정 (Web, CORS, Swagger, Security 등)

이 구조를 보고 프로젝트 코드를 보면 상이한 점이 있습니다.
무엇보다
app, domain, infra, shared 로 나뉜것이 잘 이해가 되지 않습니다.

  1. 모든 submodule이 sibling 하지 않은 이유는 무엇일까요?
  2. shared 에 common, config 가 나뉘는 것이 이상합니다.
    • 로깅은 무엇 때문에 공통 모듈로 분리하신 건가요?
    • shared-common.error 는 common 보다 config 가 맞지 않나요? 왜 common으로 정의하셨나요?
    • shared-config 가 app, domain 모두 들어갈 수 있나요? time series DB가 아닌 RDB, document db를 사용하는 곳도 필요하지 않은 설정이 포함되나요?
    • 관심 종목만 security 가 들어가도 괜찮지 않을까요?
  3. Price 와 Ticker 도메인을 분리하신 이유는 무엇인가요?

구조가 너무 첫 술에 배부르려는 것 같습니다.
너무 방대한 것을 처음부터 시작하려고 하여 범위에 압도당하는 것 같은데요
명확하고 구현의 범위가 좁은 것을 여러개 만들고 쪼개는 편이 더 현실적일 것 같습니다.

우리는 범위가 좁은 마일스톤을 여러개 나누고 빠르게 정복하는 것이 시간을 더 효율적으로 사용하는게 아닐까 싶습니다.

@polynomeer
Copy link
Collaborator Author

  1. 우선 전체적으로 의존성 화살표를 한 방향으로 고정하려고 했습니다.
    app-* (표현/진입)domain-* (핵심 규칙)infra-* (구현체)shared-* (횡단 관심사)

    문제는 실제 코드에서는 일부 모듈이 서로 참조하거나, shared가 과하게 비대해져 sibling 관계가 흐려졌습니다. 따라서 다음과 같이 모듈 수를 줄이고, 참조 방향을 단순화 하는것으로 변경했습니다.

    • app-api-price(= 시세+검색 API) / app-batch-collector만 운영
    • domain 상위 모듈만 유지, price, ticker은 내부 패키지로 구성
    • infra 상위 모듈만 유지, redis, timescaledb, external은 내부 패키지로 구성
    • shared-*는 공통적인 최소한의 set만 남기고 나머지는 각 app으로 흡수 -> 분리의 필요성에 대해서는 진행하면서 판단할 예정입니다.
  1. shared-common / shared-config 분리 이슈

    • 로깅을 공통 모듈로 뺀 이유는 서비스 전반에 상관관계 ID, 요청/응답 필터, 표준 로그 포맷을 일괄 적용하려고 했습니다. 규격을 강제하는 효과도 있다고 생각했습니다.

    • common에는 주로 정적인 클래스나 코드가 포함됩니다. 예를 들면, 예외 클래스나 에러코드는 도메인과는 독립적인 공통 타입입니다. 유틸리티 메서드도 여기에 해당됩니다. 환경에 따라 값이 달라지지 않고, 독립적으로 테스트가 가능하며, import도 자유롭게 가능합니다. config가 common을 의존하는 것은 괜찮지만, common이 config를 의존하는 것은 막도록 설계했습니다.

      config에는 환경별로 값이 달라지는 설정이 포함됩니다. 주로 서버 시작 시 한 번 로딩되고 런타임 중에 자주 바뀌지 않는 설정 및 설정값이 해당됩니다. 스프링 기반이므로 모듈과 상관없이 공통적인 @configuration은 모두 여기에 포함됩니다. 예를 들어, 예외 매핑(ControllerAdvice), message, internationalization, serialize 규칙은 환경/프레임워크 설정(config)쪽이 맞습니다.

    • config는 프레임워크/앱 진입층 성격이 강하므로 기본적으로는 app 쪽으로 귀속시키겠습니다. shared를 상위 수준 모듈만 사용하도록 변경하고, common, config는 패키지로 구성하려고 합니다.

    • security는 필요한 도메인만 적용되는것이 좋아보입니다. 마찬가지로, config는 app에서 기본적으로 개별작성하도록 변경하겠습니다.

  2. Price와 Ticker 도메인을 나눈 이유는 아래와 같이 도메인을 정의했기 때문입니다.

    • Price: 외부 소스로부터 들어오는 불변 시계열 데이터, 대량 처리, TTL/압축/파티셔닝이 관건입니다.
    • Ticker: 사용자 행위 기반 상태(즐겨찾기/알림 규칙), 인증/인가/쿼터/알림 채널 연계가 중요합니다.

    하지만 현재 Price의 기능만 MVP로 잡아서 진행할 예정이므로 Ticker 도메인은 보류하겠습니다.

최종적인 모듈 구성 변경 형태는 다음과 같습니다.

romanticker/
├── app-api-price/         # 시세 조회용 API 서버
├── app-batch-collector/   # 시세 수집 및 배치 작업 서버
│
├── domain/          			 # Price 도메인 모델 및 서비스 인터페이스
│
├── infra/								 # Redis, TimescaleDB, External 구성 및 레파지토리 구현 포함
│
└── shared/         			 # 공통 응답, 예외, 로깅, DTO, 유틸리티

@if-else-f
Copy link

  1. 우선 전체적으로 의존성 화살표를 한 방향으로 고정하려고 했습니다.
    app-* (표현/진입)domain-* (핵심 규칙)infra-* (구현체)shared-* (횡단 관심사)
    문제는 실제 코드에서는 일부 모듈이 서로 참조하거나, shared가 과하게 비대해져 sibling 관계가 흐려졌습니다. 따라서 다음과 같이 모듈 수를 줄이고, 참조 방향을 단순화 하는것으로 변경했습니다.

    • app-api-price(= 시세+검색 API) / app-batch-collector만 운영
    • domain 상위 모듈만 유지, price, ticker은 내부 패키지로 구성
    • infra 상위 모듈만 유지, redis, timescaledb, external은 내부 패키지로 구성
    • shared-*는 공통적인 최소한의 set만 남기고 나머지는 각 app으로 흡수 -> 분리의 필요성에 대해서는 진행하면서 판단할 예정입니다.
  2. shared-common / shared-config 분리 이슈

    • 로깅을 공통 모듈로 뺀 이유는 서비스 전반에 상관관계 ID, 요청/응답 필터, 표준 로그 포맷을 일괄 적용하려고 했습니다. 규격을 강제하는 효과도 있다고 생각했습니다.
    • common에는 주로 정적인 클래스나 코드가 포함됩니다. 예를 들면, 예외 클래스나 에러코드는 도메인과는 독립적인 공통 타입입니다. 유틸리티 메서드도 여기에 해당됩니다. 환경에 따라 값이 달라지지 않고, 독립적으로 테스트가 가능하며, import도 자유롭게 가능합니다. config가 common을 의존하는 것은 괜찮지만, common이 config를 의존하는 것은 막도록 설계했습니다.
      config에는 환경별로 값이 달라지는 설정이 포함됩니다. 주로 서버 시작 시 한 번 로딩되고 런타임 중에 자주 바뀌지 않는 설정 및 설정값이 해당됩니다. 스프링 기반이므로 모듈과 상관없이 공통적인 @configuration은 모두 여기에 포함됩니다. 예를 들어, 예외 매핑(ControllerAdvice), message, internationalization, serialize 규칙은 환경/프레임워크 설정(config)쪽이 맞습니다.
    • config는 프레임워크/앱 진입층 성격이 강하므로 기본적으로는 app 쪽으로 귀속시키겠습니다. shared를 상위 수준 모듈만 사용하도록 변경하고, common, config는 패키지로 구성하려고 합니다.
    • security는 필요한 도메인만 적용되는것이 좋아보입니다. 마찬가지로, config는 app에서 기본적으로 개별작성하도록 변경하겠습니다.
  3. Price와 Ticker 도메인을 나눈 이유는 아래와 같이 도메인을 정의했기 때문입니다.

    • Price: 외부 소스로부터 들어오는 불변 시계열 데이터, 대량 처리, TTL/압축/파티셔닝이 관건입니다.
    • Ticker: 사용자 행위 기반 상태(즐겨찾기/알림 규칙), 인증/인가/쿼터/알림 채널 연계가 중요합니다.

    하지만 현재 Price의 기능만 MVP로 잡아서 진행할 예정이므로 Ticker 도메인은 보류하겠습니다.

최종적인 모듈 구성 변경 형태는 다음과 같습니다.

romanticker/
├── app-api-price/         # 시세 조회용 API 서버
├── app-batch-collector/   # 시세 수집 및 배치 작업 서버
│
├── domain/          			 # Price 도메인 모델 및 서비스 인터페이스
│
├── infra/								 # Redis, TimescaleDB, External 구성 및 레파지토리 구현 포함
│
└── shared/         			 # 공통 응답, 예외, 로깅, DTO, 유틸리티

아직 조금 과한느낌이 있지만, 이렇게 줄어든것만 해도 큰 수확이라 생각합니다.

@if-else-f
Copy link

LGTM merge 하시고 다음 단계 진행하시면 될것 같습니다.

@polynomeer polynomeer merged commit a297b5e into main Aug 9, 2025
1 check passed
@polynomeer polynomeer deleted the feature/13-project-setting branch August 9, 2025 04:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

패키지 구조 설계

2 participants