백엔드 · 1장 스프링 부트 · 아키텍처

1장 · 스프링 부트 & 레이어드 아키텍처

백엔드의 큰 그림부터 잡습니다. 스프링 부트?가 무엇인지, 빈·의존성 주입?은 어떻게 동작하는지, 코드가 어떤 ?으로 나뉘고 한 요청이 어떻게 흐르는지.

스프링 부트란?

Java로 백엔드 서버를 만드는 표준 프레임워크입니다. 서버 구동·DB 연결·보안 같은 복잡한 설정을 자동으로(auto-configuration) 잡아줘서, 우리는 업무 코드에 집중할 수 있어요. 이 프로젝트는 Spring Boot 3.3.1 / Java 17, 포트 8084에서 돕니다.

스프링 부트는 "옵션이 가득 끼워진 완성차". 엔진·배선(설정)을 직접 조립하지 않고, 키만 꽂으면(main 실행) 바로 달립니다.
// study-backend/src/main/java/gsc/study/StudyApplication.java
@SpringBootApplication            // 자동 설정 + 컴포넌트 스캔을 한 번에 켜는 표시
public class StudyApplication {
    public static void main(String[] args) {
        SpringApplication.run(StudyApplication.class, args);  // 서버 시작
    }
}

빈 & 의존성 주입 (DI)

스프링이 만들어 관리하는 객체를 빈(Bean)이라 합니다. 어떤 클래스가 다른 객체(예: UserRepo)가 필요하면 직접 new 하지 않고, 스프링이 대신 넣어줍니다(의존성 주입). 보통 생성자 주입을 쓰며, Lombok @RequiredArgsConstructorfinal 필드용 생성자를 자동으로 만들어 줍니다.

@RestController
@RequiredArgsConstructor               // final 필드를 받는 생성자 자동 생성 → 생성자 주입
@RequestMapping("/api/users")
public class UserAccountEndpoint {
    private final UserRepo userRepo;            // 스프링이 주입
    private final PasswordEncoder passwordEncoder;
}
왜 직접 new 안 하나요?

스프링이 객체의 생성·연결·생명주기를 관리하면, 테스트할 때 가짜 객체로 바꿔 끼우거나(모킹) 구현을 교체하기 쉬워집니다. 이것이 제어의 역전(IoC)입니다.

패키지 구조

코드는 역할별로 나뉩니다. 루트 패키지는 com.example.study.

패키지역할예시
config/설정 (보안·QueryDSL·감사·Jackson)SecurityConfig, WebSecurityConfig
core/model/entity/엔티티? (DB 테이블 매핑)User, Role, Menu
core/repo/저장/조회/삭제 (Command)UserRepo, RoleRepo
core/query/복잡한 조회 (QueryDSL?)UserRepoQueryImpl
core/service/업무 로직 (이 프로젝트는 얇음)JwtTokenProvider
shared/공통 베이스·예외·유틸BaseAuditEntity, BizRuleException
web/endpoint/REST 컨트롤러? + 전역 예외UserAccountEndpoint
web/dto/요청/응답 DTO?UserCreateReq, UserDTO
web/filter/HTTP 필터 (JWT 인증)JwtAuthenticationFilter

레이어드 아키텍처 & 요청 흐름

일반적으로 백엔드는 컨트롤러 → 서비스 → 리포지토리로 책임을 나눕니다. 이 프로젝트는 한발 더 나아가 읽기/쓰기를 분리한 흔적이 있어요 — 쓰기·기본 CRUD는 core/repo(Command), 복잡한 조회는 core/query(Query)로 나눕니다.

// shared/base/BaseRepo.java — "Command와 Query를 구분하기 위해 crud만 제공"
@NoRepositoryBean
@EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.USE_DECLARED_QUERY)
public interface BaseRepo<T, ID> extends CrudRepository<T, ID> { }
이 프로젝트의 현실 — 얇은 서비스

"컨트롤러 → 서비스 → 리포지토리" 3계층이 정석이지만, 이 학습 프로젝트는 서비스 층이 얇아서 UserAccountEndpoint·RoleEndpoint는 서비스를 거치지 않고 리포지토리를 직접 호출합니다. 개념은 3계층으로 이해하되, 코드에선 서비스가 생략된 곳이 많다는 점을 기억하세요.

한 요청이 거치는 길 (개요)

HTTP 요청
  → JwtAuthenticationFilter   (web/filter)  : JWT 검증 → 인증정보 저장
  → SecurityFilterChain       (config)      : 공개/인증 경로 판단
  → @RestController           (web/endpoint): @Valid로 요청 DTO 검증
  → core/repo (Command) 또는 core/query (QueryDSL)  : DB 접근
  → 엔티티 → 응답 DTO 변환 (DTO.fromEntity)
  → JSON 응답
(예외 발생 시 → GlobalExceptionHandler → 표준 에러 응답)

→ 이 흐름을 프론트까지 이어 단계별로 보기: 연동 1장 · 전체 요청 흐름

이 구조가 실제로 어디에
  • 새 기능 추가 — 보통 엔티티 → 리포지토리 → (서비스) → 컨트롤러 → DTO 순으로 한 층씩 만든다
  • 버그 추적 — 화면 문제면 컨트롤러/DTO, 데이터 문제면 리포지토리/쿼리 층부터 본다
  • 역할 분리 — 단순 저장은 core/repo, 검색·필터·페이징은 core/query

설정 파일 살펴보기

실행 옵션은 application.properties, 의존성·빌드는 build.gradle에 있습니다.

# application.properties (발췌)
server.port=8084
spring.datasource.url=jdbc:postgresql://localhost:5432/studydb?currentSchema=lds
spring.liquibase.change-log=classpath:/db/changelog/changelog-index.yaml   # 스키마는 Liquibase가 관리
app.jwt.expirationMs=43200000             # 액세스 토큰 12시간
app.jwt.refreshExpirationMs=604800000     # 리프레시 7일
영속성 기술이 4개?

build.gradle에는 JPA·QueryDSL·jOOQ·MyBatis가 모두 있지만, 실제로 쿼리하는 코드는 JPA + QueryDSL뿐입니다. jOOQ는 코드 생성까지만, MyBatis는 설정만 되어 있어요(회사 스택을 따라 자리만 마련). 자세히는 4장에서.