[Spring Boot / 백엔드] 스프링 부트 프로젝트 세팅하기 - 아키텍처 구조와 Swagger 정리

백엔드 개발을 공부하다 보면 기능 구현 자체도 중요하지만, 그 전에 프로젝트를 어떤 구조로 시작할지 고민하게 됩니다.

 

처음에는 저도 스프링 프로젝트를 생성하고, 필요한 클래스만 빠르게 추가하면 된다고 생각했습니다.

 

그런데 직접 프로젝트를 진행해보니 어떤 구조로 파일을 나누는지, 설정 파일을 어떻게 관리하는지, 그리고 API 문서를 어떤 방식으로 공유하는지에 따라 협업 난이도와 유지보수성이 꽤 크게 달라진다는 걸 느꼈습니다.

 

특히 프로젝트 규모가 커질수록 “기능이 돌아가기만 하는 코드”보다 “다른 사람이 봐도 이해하기 쉬운 구조”가 훨씬 중요하다는 점을 실감했습니다.

 

그래서 이번 글에서는 워크북 내용을 바탕으로 아키텍처 구조가 왜 필요한지, 도메인형 아키텍처는 어떤 특징이 있는지, 그리고 스프링 부트 프로젝트를 세팅하면서 Swagger까지 연결하는 과정을 정리해보려고 합니다.

 

이번 글에서 다룰 내용은 다음과 같습니다.

 

  • 아키텍처 구조란 무엇인가
  • 왜 프로젝트 구조 설계가 중요한가
  • 계층형 구조와 도메인형 구조
  • 스프링 부트 프로젝트 기본 세팅
  • application.yml과 환경 변수 설정
  • 패키지 구조를 잡을 때 주의할 점
  • Swagger란 무엇이고 왜 사용하는가
  • Swagger 설정 방법

 

아키텍처 구조란?

 

아키텍처 구조는 쉽게 말하면 프로젝트의 뼈대를 어떻게 설계할지 정하는 것이라고 볼 수 있습니다.

 

조금 더 풀어서 말하면,

 

  • 어떤 역할을 어떤 계층이 맡을지
  • 파일과 패키지를 어떤 기준으로 나눌지
  • 기능이 늘어나도 구조가 쉽게 무너지지 않게 할지

 

를 미리 정해두는 작업입니다.

 

처음에는 작은 프로젝트라서 구조가 크게 중요하지 않아 보일 수 있습니다. 하지만 기능이 하나둘 추가되고, 기획이 바뀌고, 다른 사람과 협업하게 되면 구조가 잘 잡혀 있는 프로젝트와 그렇지 않은 프로젝트의 차이가 확실히 드러납니다.

 

결국 아키텍처 구조는 지금 당장의 편의성보다 이후의 수정과 확장까지 고려하는 설계라고 느꼈습니다.

 


 

왜 아키텍처 구조를 설계해야 할까?

아키텍처 구조를 설계하는 가장 큰 이유는 결국 유지보수 때문입니다.

 

예를 들어 처음에는 A 기능만 필요해서 코드를 한곳에 몰아서 작성했다고 가정해보겠습니다. 그런데 나중에 B 기능이 추가되고, A 기능 안의 세부 로직도 수정해야 하는 상황이 생기면 구조가 정리되지 않은 코드는 수정 범위가 점점 커지게 됩니다.

 

반대로 역할이 분리되어 있고 의존성이 낮은 구조라면 문제가 생겼을 때 해당 부분만 집중해서 수정할 수 있습니다.

 

즉, 구조 설계를 잘하면 다음과 같은 장점이 있습니다.

 

 

1. 이해하기 쉬워진다

프로젝트를 처음 보는 사람도 “어디에 어떤 코드가 있을지” 예측하기 쉬워집니다.

 

 

2. 관심사를 분리할 수 있다

요청을 처리하는 부분, 비즈니스 로직, 데이터 접근 로직을 나누면 각 부분의 책임이 명확해집니다.

 

 

3. 수정과 확장에 유리하다

한 부분의 변경이 다른 부분에 큰 영향을 주지 않도록 만들 수 있습니다.

 

결국 좋은 아키텍처 구조는 코드를 잘 짜는 기술이라기보다 변화에 강한 프로젝트를 만드는 방식에 가깝다고 느꼈습니다.

 


 

어떤 아키텍처 구조가 있을까?

 

워크북에서는 대표적으로 두 가지를 소개합니다.

 

 

1. 계층형 구조

계층형 구조는 controller, service, repository, entity처럼 역할(계층) 을 기준으로 파일을 나누는 방식입니다.

 

예를 들면 다음과 같은 구조입니다.

controller/
service/
repository/
entity/
dto/

이 방식은 처음 배우는 입장에서는 직관적이고 익숙합니다. 다만 프로젝트 규모가 커지면 기능별로 관련 파일이 흩어져서 하나의 도메인을 한 번에 파악하기 어려울 수도 있습니다.

 

 

2. 도메인형 구조

도메인형 구조는 회원, 미션, 리뷰처럼 비즈니스 도메인을 기준으로 묶는 방식입니다.

 

예를 들면 다음과 같은 식입니다.

domain/
  member/
  mission/
  review/

그리고 각 도메인 안에 다시 controller, service, repository, entity 등을 둡니다.

 

즉, “회원 관련 코드”가 모두 member 아래에 모이기 때문에 도메인 단위로 코드를 이해하고 관리하기가 더 편해집니다.

 


 

도메인형 아키텍처를 선택한 이유

 

이번 워크북에서는 데모데이 프로젝트 특성과 역할 분담을 고려해서 도메인형 아키텍처 구조를 사용합니다.

 

개인적으로도 이 방식이 꽤 실용적이라고 느꼈습니다.

 

예를 들어 프로젝트에서 큰 도메인이 다음과 같다고 해보겠습니다.

 

  • 사용자
  • 미션
  • 리뷰

 

그렇다면 구조를 다음처럼 나눌 수 있습니다.

java/com/example/umc10th/
├── domain
│   ├── member
│   │   ├── controller
│   │   ├── converter
│   │   ├── dto
│   │   ├── entity
│   │   ├── enums
│   │   ├── exception
│   │   ├── repository
│   │   └── service
│   ├── mission
│   └── review
├── global
└── Umc10thApplication

이 구조의 장점은 분명합니다.

 

  • 특정 기능과 관련된 코드가 한곳에 모여 있다
  • 담당 도메인별로 작업 분리가 쉽다
  • 프로젝트가 커져도 도메인 단위로 확장하기 좋다

 

물론 무조건 도메인형 구조가 정답은 아닙니다. 프로젝트 크기, 팀 규모, 복잡도에 따라 다를 수 있습니다.

 

그래도 이번처럼 도메인별 독립성이 중요하고, 기능 확장을 고려해야 하는 상황에서는 도메인형 구조가 꽤 잘 맞는다고 느꼈습니다.

 

 


 

아키텍처 구조를 설계할 때 중요한 원칙

 

어떤 구조를 선택하든 공통적으로 중요한 원칙이 있습니다.

 

 

1. 단일 책임 원칙

각 계층과 객체는 하나의 역할에 집중해야 합니다.

 

  • Controller는 요청과 응답 처리
  • Service는 비즈니스 로직 처리
  • Repository는 DB 접근 처리

 

처럼 역할이 분리되어야 합니다.

 

 

2. 모듈화

하나의 기능을 하나의 모듈로 관리하면 필요한 부분만 수정하거나 재사용하기 쉬워집니다.

 

 

3. 낮은 의존성

한 부분이 바뀌더라도 다른 부분에 영향이 최소화되도록 설계해야 합니다.

 

이런 원칙들은 결국 객체지향 설계의 기본 철학과도 닿아 있다고 생각합니다.

 


 

스프링 부트 프로젝트 세팅하기

 

프로젝트 세팅은 크게 두 가지 방식으로 진행할 수 있습니다.

 

  • start.spring.io를 이용하는 방법
  • IntelliJ에서 직접 생성하는 방법

 

기본적으로는 둘 다 결과는 비슷하고, 필요한 의존성을 추가해서 프로젝트를 시작하면 됩니다.

 

이번 워크북에서는 Gradle 기반으로 프로젝트를 생성하고, 이후 설정 파일을 application.properties 대신 application.yml로 변경합니다.

 

그 이유는 설정이 많아질수록 yml 형식이 계층 구조를 표현하기 더 편하기 때문입니다.

 


 

application.yml 설정하기

 

스프링 프로젝트를 생성한 뒤에는 DB 연결과 JPA 관련 설정을 추가해야 합니다.

 

예시는 다음과 같습니다.

spring:
  application:
    name: "프로젝트명"

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: ${DB_URL}
    username: ${DB_USER}
    password: ${DB_PW}

  jpa:
    database: mysql
    database-platform: org.hibernate.dialect.MySQLDialect
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true

 

여기서 중요한 점은 DB URL, USER, PASSWORD 같은 민감한 정보는 직접 코드에 하드코딩하지 않고 환경 변수로 분리했다는 점입니다.

 

이런 방식의 장점은 다음과 같습니다.

 

  • 민감한 정보가 코드에 그대로 남지 않는다
  • 로컬, 개발, 운영 환경별로 설정을 분리하기 쉽다
  • 깃허브 같은 공개 저장소에 비밀번호가 노출될 위험을 줄일 수 있다

 


 

민감 정보는 왜 꼭 분리해야 할까?

 

워크북에서도 강조하는 부분이지만, 민감한 정보는 정말 조심해야 합니다.

 

예를 들어 DB 계정 정보나 API 키를 실수로 퍼블릭 저장소에 올리면

 

  • 외부에서 DB에 접근할 수 있고
  • API 키가 무단 사용될 수 있고
  • 비용이 발생하거나 서비스에 문제가 생길 수도 있습니다

 

그래서 환경 변수를 쓰지 않고 application.yml에 직접 값을 넣었다면 해당 파일을 .gitignore에 추가해야 합니다.

 

반대로 .env 파일이나 환경 변수로 분리했다면 그 파일을 깃에서 제외해야 합니다.

 

즉, 프로젝트 세팅에서 가장 기본적이지만 중요한 습관은 민감 정보를 코드와 분리하는 것이라고 느꼈습니다.

 


 

패키지 구조에서 주의할 점

 

스프링 부트에서 패키지 구조를 잡을 때 생각보다 중요한 포인트가 하나 있습니다.

 

바로 메인 애플리케이션 클래스의 위치입니다.

 

예를 들어 메인 클래스가

com.example.umc10th.Umc10thApplication

에 있다면, 스프링은 기본적으로 이 패키지를 기준으로 하위 패키지를 스캔합니다.

 

즉,

 

  • 같은 위치
  • 혹은 그 하위 패키지

 

에 있는 클래스들은 인식하지만, 상위나 전혀 다른 위치에 있는 클래스는 인식하지 못할 수 있습니다.

 

그래서 Controller, Service, Repository 등을 만들 때는 반드시 메인 애플리케이션 클래스 기준으로 동일하거나 하위 패키지에 두어야 합니다.

 

이 부분은 처음에 실수하기 쉬운 포인트라서 프로젝트를 시작할 때 구조를 먼저 잡아두는 게 정말 중요하다고 느꼈습니다.

 


 

Swagger란?

 

Swagger는 개발한 API 목록을 확인하고 테스트할 수 있게 도와주는 도구입니다.

 

백엔드 개발을 하다 보면 API를 만들고 끝나는 것이 아니라 그 API를 다른 사람도 이해할 수 있게 설명해야 합니다.

 

이때 Swagger를 사용하면

 

  • 어떤 API가 있는지
  • 어떤 요청값을 받는지
  • 어떤 응답을 반환하는지

 

를 한눈에 확인할 수 있습니다.

 

즉, Swagger는 API 문서를 자동화하고, 직접 테스트까지 해볼 수 있는 도구라고 볼 수 있습니다.

 


 

Swagger는 왜 필요할까?

 

개인적으로 Swagger의 가장 큰 장점은 “코드와 실제 문서 간의 거리”를 줄여준다는 점이라고 느꼈습니다.

 

문서를 수동으로 작성하면 초기에는 잘 정리되어 있어도 기능이 바뀔 때마다 계속 수정해줘야 합니다.

 

하지만 Swagger는 구현한 API를 기준으로 문서를 보여주기 때문에 실제 동작과 문서가 어긋날 가능성을 줄일 수 있습니다.

 

또한 프론트엔드나 팀원 입장에서는 직접 요청을 날려보면서 API를 확인할 수 있어서 협업에도 큰 도움이 됩니다.

 


 

Swagger 설정하기

 

Swagger를 적용하려면 먼저 의존성을 추가해야 합니다.

 

build.gradle의 dependencies에 다음을 넣어줍니다.

implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.1'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.1'

그다음 전역 설정 파일을 만들어 Swagger 설정을 추가합니다.

 

예를 들어 global/config/SwaggerConfig.java에 다음처럼 작성할 수 있습니다.

@Configuration
public class SwaggerConfig {

    @Bean
    public OpenAPI swagger() {
        Info info = new Info()
                .title("UMC10th")
                .description("10기 Swagger")
                .version("0.0.1");

        String securityScheme = "JWT TOKEN";
        SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityScheme);

        Components components = new Components()
                .addSecuritySchemes(securityScheme, new SecurityScheme()
                        .name(securityScheme)
                        .type(SecurityScheme.Type.HTTP)
                        .scheme("Bearer")
                        .bearerFormat("JWT"));

        return new OpenAPI()
                .info(info)
                .addServersItem(new Server().url("/"))
                .addSecurityItem(securityRequirement)
                .components(components);
    }
}

여기서 눈에 띄는 부분은 JWT 관련 설정입니다.

 

아직 인증 기능을 구현하지 않았더라도 추후 토큰 기반 인증을 사용할 예정이라면 이런 식으로 보안 스키마를 미리 정의해둘 수 있습니다.

 

 


 

Swagger 실행하기

 

설정이 끝난 뒤 프로젝트를 실행하고 다음 주소로 접속하면 Swagger UI를 확인할 수 있습니다.

http://localhost:8080/swagger-ui/index.html

이 페이지에서는

 

  • 등록된 API 목록 확인
  • 요청 파라미터 입력
  • 실제 API 호출 테스트

 

까지 한 번에 해볼 수 있습니다.

 

즉, Swagger는 단순 문서가 아니라 백엔드 개발 중간중간 API를 검증하는 도구로도 꽤 유용합니다.

 


 

프로젝트 세팅에서 느낀 핵심 포인트

 

이번 워크북을 정리하면서 느낀 핵심은 프로젝트 세팅은 단순히 “처음 한 번 하는 초기 작업”이 아니라는 점이었습니다.

 

처음 구조를 어떻게 잡느냐에 따라

 

  • 이후 기능 추가가 쉬워질 수도 있고
  • 패키지가 꼬일 수도 있고
  • 협업이 편해질 수도 있고
  • 문서 관리가 훨씬 수월해질 수도 있습니다

 

특히 인상 깊었던 부분은 두 가지였습니다.

 

첫 번째는 도메인 중심으로 구조를 나누면 프로젝트를 비즈니스 기준으로 이해하기 쉬워진다는 점입니다.

 

두 번째는 Swagger 같은 도구를 붙이면 API를 구현하고 검증하는 과정이 훨씬 명확해진다는 점입니다.

 


 

마무리

 

이번 내용을 정리하면서 느낀 것은 스프링 부트 프로젝트를 세팅하는 과정은 단순히 프로젝트를 생성하고 실행하는 데서 끝나지 않는다는 점이었습니다.

 

어떤 아키텍처 구조를 선택할지, 어떻게 역할을 분리할지, 민감한 설정 정보를 어떻게 관리할지, 그리고 API를 어떤 방식으로 문서화할지까지 모두가 결국 좋은 프로젝트를 만들기 위한 기초 작업이라고 생각합니다.

 

처음에는 controller, service, repository를 그냥 관습처럼 나누고 Swagger도 “문서 보는 도구” 정도로만 이해했는데, 이번 정리를 하면서 왜 이런 구조와 도구가 필요한지 조금 더 선명하게 이해할 수 있었습니다.

 

특히 백엔드 개발은 기능 구현만 잘한다고 끝나는 것이 아니라 다른 사람이 이해하기 쉬운 구조를 만들고, 협업할 수 있는 환경을 함께 준비하는 것도 중요하다고 느꼈습니다.

 

앞으로 프로젝트를 진행할 때도 단순히 돌아가는 코드에만 집중하기보다 구조와 문서화까지 함께 신경 쓰는 습관을 들여야겠다고 생각했습니다.