|
미니멀리즘적인 UI/UX를 통해 빠르고 정확하게 정보를 공유하는 웹 커뮤니티 서비스입니다. 불필요한 복잡함은 전부 덜어내고 정보(Information)와 관심사에만 집중할 수 있어요. |
Java, Spring Boot 기반 REST API 서버
- 개발 기간 : 2025.10. - 2025. 12. (2개월)
- 개발 인원 및 담당 역할 : 1인 (기획, 디자인, 개발)
ktb3-theta-full-community-be/
├── build.gradle, settings.gradle
├── gradle/ , gradlew* # Gradle Wrapper
├── upload/ # 샘플 업로드 파일 (articles, profiles)
├── src/
│ ├── main/
│ │ ├── java/ktb/week4/community/
│ │ │ ├── domain/
│ │ │ │ ├── article | comment | like | user # controller/service/repository/entity/dto/validator/
loader/policy
│ │ │ ├── security/ # JWT, principal, filter, handler 등 보안 구성
│ │ │ └── global/ # 공통 config, exception, apiPayload, converter,
resolver, util, file
│ │ └── resources/
│ │ ├── application.yml # DB/로그/업로드 경로 설정
│ │ ├── static/ # 정적 리소스
│ └── test/java/ktb/week4/community/ # domain별 단위/통합 테스트
│ ├── domain/*/entity # 엔티티 단위 테스트
│ └── auth | file | comment | like | user | article
└── build/ # 생성물(클래스, 리소스, 리포트)
문제 상황
Controller에 Swagger 어노테이션과 controller 로직이 섞여있음 Service에 검증 로직이 중복되어 코드 가독성이 떨어짐
해결 방안
- 인터페이스 분리
- Swagger 관련 설정과 Validation 어노테이션을 모두
ApiSpecification인터페이스로 이동시키고, 구현체(Controller)에서는 순수 로직만 남김.
- Swagger 관련 설정과 Validation 어노테이션을 모두
- Service 계층화
- 비즈니스 로직과 검증 로직이 섞이는 것을 막기 위해
Loader(조회),Validator(검증),Policy(정책) 클래스로 역할을 명확히 분리.
- 비즈니스 로직과 검증 로직이 섞이는 것을 막기 위해
성과
- API 명세 변경 시 비즈니스 로직을 건드리지 않아도 되는 유지보수성 확보
- Controller와 Service 클래스의 코드 라인 수 감소 및 응집도 향상
문제 상황
- 게시글 목록 조회 시 작성자 정보를 가져오기 위해 게시글 개수(N)만큼
SELECT * FROM user쿼리가 추가 발생하는 N+1 문제 발생 - 엔티티에
@ColumnDefault를 설정했음에도Hibernate가null값을 명시적으로 삽입하여 DB 기본값이 적용되지 않는 문제 발생
해결 방안
- Fetch Join 적용
@EntityGraph(attributePaths = {"user"})를 적용,LEFT OUTER JOIN을 통해 한 번의 쿼리로 연관 데이터를 조회
- DynamicInsert 도입 후 취소
- @DynamicInsert
를 적용하여null인 필드는INSERT쿼리에서 제외시켜 DB가 설정된Default Value`를 자동으로 채움 - 하지만 DynamicInsert는 미리 준비된 쿼리를 사용하지 않기에 성능이 떨어짐이 예상 → nullable로 허용하고 프론트엔드 단에서 따로 처리하게 수정
- @DynamicInsert
성과
- 게시글 조회 쿼리 수 N+1회 → 1회로 단축
- 불필요한 더미 데이터 생성 방지 및 데이터 무결성 확보
문제 상황
프론트엔드에서 x-www-form-urlencoded가 아닌 JSON 형식으로 로그인 데이터를 보내면서, Spring Security의 기본 formLogin()을 사용할 수 없게 됨
해결 방안
- Custom Filter 구현
- AbstractAuthenticationProcessingFilter
를 상속받은JsonAuthenticationFilter를 구현하여ObjectMapper`로 JSON 데이터를 파싱하도록 변경
- AbstractAuthenticationProcessingFilter
- EntryPoint 도입
- 인증 실패 시 403 Forbidden이 아닌 명확한 401 응답을 위해
AuthenticationEntryPoint를 구현하여 공통 응답 포맷(ApiResponse)을 반환하도록 설정
- 인증 실패 시 403 Forbidden이 아닌 명확한 401 응답을 위해
성과
- REST API 표준을 준수하는 인증 흐름 완성
고려사항
테스트 코드에 대해 고찰하고 유의미한 테스트 코드를 작성하고자 노력
추가적으로 아래 사항을 고려함
- 테스트 편의를 위해 프로덕션 코드(엔티티, 설정 등)를 오염시키지 않을 것
- 테스트 실행 시간이 CI 파이프라인에 부담이 되지 않도록 성능을 고려할 것
성과
스스로 정한 기준에 맞추어 테스트 코드 작성
테스트 커버리지 91% 달성, 총 81개 테스트 작성
과정을 블로그 글(테스트 코드 - 유의미한 테스트와 커버리지 사이의 균형)에 작성
문제 상황
초기에는 User 엔티티에 테스트용 생성자를 추가하여 엔티티를 생성하였음
하지만 이는 setter 추가와 유사하게 엔티티 무결성을 해칠 수 있고, 프로덕션 코드에 테스트 관련 코드가 섞이는 문제가 발생
테스트 범위가 확장되면서 엔티티 생성 로직이 여러 테스트에 중복되었고 필드가 추가/변경될 때마다 다수의 테스트 코드를 수정해야 했기에 유지보수성이 떨어짐을 느낌
해결 방안
여러 엔티티 생성 방식을 비교 후, 테스트 전용 Builder 패턴을 사용하기로 결정
| 방법 | 설명 | 장점 | 단점 |
|---|---|---|---|
@Builder |
엔티티에 Builder 직접 적용 | 가독성 우수 | 테스트 요구가 프로덕션 코드에 반영되어 테스트 오염 발생 |
| 테스트 유틸 (Factory Method) | 정적 메서드로 엔티티 생성 | 공통 로직 집중 가능 | 필드 조합 증가 시 메서드 수가 빠르게 늘어남 |
| 테스트 전용 Builder 클래스 | 테스트 영역에만 존재하는 Builder 구현 | 필드 추가/변경 시 해당 Builder만 수정하면 됨 | 엔티티마다 별도 Builder 클래스 필요 |
| Object Mother | 특정 상태의 엔티티를 반환 | normalUser()처럼 의도가 이름에 드러남 |
케이스가 늘어날수록 관리 복잡도 증가 |
@Sql |
SQL 스크립트로 테스트 데이터 로딩 | 실제 DB 값과 완전히 동일한 데이터로 통합 테스트 가능 | SQL 기반이라 가독성이 떨어지고 수정이 번거로움 |
Repository / EntityManager.persist |
실제 영속성 컨텍스트를 통해 엔티티 생성 | JPA 설정까지 함께 검증 가능 | 무겁고, 테스트 실행 시간이 느려짐 |
- 필드 변경이 잦은 엔티티 특성에 맞춰, 테스트 코드만 수정하도록 책임을 분리
- 엔티티 무결성을 해치지 않으면서도 테스트 독립성과 가독성을 확보
고려사항
- 테스트 전용 Builder는 테스트 소스셋(test)에만 존재하도록 분리하여 프로덕션 코드 오염 방지
- 공통으로 자주 사용하는 기본값(default)을 Builder 내부에 정의해, 테스트 코드에서는 필요한 값만 덮어쓰도록 설계
성과
- 엔티티 필드 변경 시 테스트 전용 Builder만 수정하면 되므로, 테스트 수정 범위가 크게 줄어듦
- 테스트 코드에서 엔티티 생성부가 단순해지므로 가독성 향상
- 엔티티 무결성을 유지할 수 있게 됨
문제 상황
- 컨트롤러 테스트에서 전체 애플리케이션 컨텍스트를 로딩하면서, 불필요한 빈까지 함께 올라가 테스트 실행 시간이 길어짐
- JSON 직렬화/역직렬화용 라이브러리(
Object Mapper) 초기화 오버헤드도 테스트 성능에 영향을 줄 수 있음을 고려
해결 방안
- 컨트롤러 단 테스트를
@WebMvcTest기반으로 분리하여 Web 계층만 로딩, 불필요한 빈 로딩 제거 - JSON 직렬화 라이브러리를 싱글톤으로 사용하여 테스트 환경에서의 직렬화 비용을 줄이고자 함
고려사항
@WebMvcTest으로 Web 계층 관련 빈만 로딩하고 필요한 의존성(서비스, 리포지토리 등)은@MockitoBean등으로 주입
성과
테스트 실행 시간 3.240s → 2.730s로 약 16% 개선
- 저장소 클론:
git clone <repo-url> - 백엔드 디렉터리 이동:
cd ktb3-theta-full-community-be - 환경 준비: JDK 21, MySQL 데이터베이스
ktb_community생성 후src/main/resources/application.yml의 접속 정보 확인/수정 - 애플리케이션 실행:
./gradlew bootRun - 선택 실행: 테스트
./gradlew test, 문서(UI)http://localhost:8080/swagger-ui.html확인

