2026. 7. 9. 16:01ㆍ부트캠프/TIL(Today I Learned)
DDD와 헥사고날 아키텍처를 이해하고, CI 오류를 해결한 하루
📌 오늘의 학습 키워드
- DDD(Domain-Driven Design)
- 헥사고날 아키텍처(Hexagonal Architecture)
- Kubernetes
- GitHub Actions CI/CD
- Spring @Service
- 리뷰 생성 기능 구현
- Git Local History
오늘 한 학습
오늘은 좋은 설계란 무엇인지에 대해 생각해볼 수 있었음. 오전에는 DDD와 헥사고날 아키텍처에 대한 OT를 들었고, 이후에는 리뷰 작성 기능을 구현하면서 GitHub Actions의 CI 오류까지 해결했다.
또한 Git 명령어를 잘못 입력해 작업하던 코드가 사라지는 상황도 있었지만, IntelliJ의 Local History 기능으로 작업 내용을 복구할 수 있다는 것도 새롭게 알게 되었다.
DDD(Domain Driven Design)
비즈니스 자체를 중심으로 프로그램이라는 것을 이해하게 되었다.
프로젝트가 커질수록
- Service에 모든 로직이 몰리고
- Entity는 단순 데이터 객체가 되며
- 유지보수가 어려워지고
- 새로운 기능을 추가할 때 기존 코드가 쉽게 깨질 수 있다.
DDD는 이런 문제를 해결하기 위해
데이터를 중심으로 설계하는 것이 아니라, 비즈니스 규칙을 중심으로 설계한다.
라는 철학을 가지고 있다.
특히 Fatera 프로젝트를 떠올려 보니
- 번호 생성
- 당첨 통계
- 즐겨찾기
- 사용자
- 청약 계산
모두 각각 하나의 도메인으로 나눌 수 있다는 점이 흥미로웠다.
기능을 만드는 것보다 오래 유지하는 것이 더 어렵다.
헥사고날 아키텍처
헥사고날 아키텍처는 비즈니스 코드를 어떻게 보호할 것인가에 대한 이야기.
기존 Spring 프로젝트는 보통
Controller
↓
Service
↓
Repository
↓
Database
구조를 사용한다.
하지만 Service가 JPA나 Database에 직접 의존하게 되면
DB를 변경하거나 테스트 환경을 구성할 때 많은 수정이 발생한다.
헥사고날 아키텍처에서는
비즈니스 로직
↓
Port
↓
Adapter
↓
Database
형태로 구성하여
비즈니스는 외부 기술을 전혀 알 필요가 없도록 설계한다.
Kubernetes
오늘은 Kubernetes도 간단하게 배웠다.
Docker는 컨테이너를 실행하는 기술,
Kubernetes는
- 서버 장애 복구
- 자동 확장
- 무중단 배포
- 컨테이너 관리
등을 담당하는 운영 플랫폼이라는 점을 알게 되었다.
실제 서비스에서는
- 서버가 죽거나
- 트래픽이 갑자기 증가하거나
- 새로운 버전을 배포해야 하는 상황
등이 자주 발생하기 때문에 Kubernetes가 필요하다는 것을 이해했다.
리뷰 생성 기능 구현
오늘은 리뷰 생성 기능도 구현했다.
리뷰는 아무나 작성할 수 있는 것이 아니라
다음 조건을 모두 만족해야 한다.
- 로그인한 사용자여야 한다.
- 주문한 본인만 작성 가능하다.
- 주문 상태가 COMPLETED여야 한다.
- 동일 주문에는 리뷰를 한 번만 작성할 수 있다.
이를 위해 서비스 로직에서
- 주문 조회
- 주문자 검증
- 주문 상태 검증
- 중복 리뷰 검증
- Store 조회
- Review 생성
- Store 평점 재계산
순서로 구현하였다.
@Transactional
public ReviewResponse createReview(User loginUser, ReviewCreateRequest request) {
Orders order = orderRepository.findById(request.orderId())
.orElseThrow(() -> new CustomException(ErrorCode.ORDER_NOT_FOUND));
if (!order.getUserId().equals(loginUser.getId())) {
throw new CustomException(ErrorCode.NOT_ORDER_OWNER);
}
if (order.getOrderStatus() != OrderStatusEnum.COMPLETED) {
throw new CustomException(ErrorCode.ORDER_NOT_COMPLETED);
}
if (reviewRepository.existsByOrder_IdAndDeletedAtIsNull(order.getId())) {
throw new CustomException(ErrorCode.REVIEW_ALREADY_EXISTS);
}
Store store = storeRepository.findById(order.getStoreId())
.orElseThrow(() -> new CustomException(ErrorCode.STORE_NOT_FOUND));
Review review = Review.create(
order,
loginUser,
store,
request.star(),
request.content()
);
reviewRepository.save(review);
recalculateStoreRating(store);
return new ReviewResponse(review);
}
GitHub Actions CI/CD 오류 해결
회원가입 기능과 리뷰 기능을 구현한 뒤 PR을 올렸는데
CI가 계속 실패했다.
로그를 확인해 보니
BeanDefinitionOverrideException
이 발생하고 있었다.
원인은
StoreRepository.java, Store.java
클래스가 각각 두 군데에 존재하여
Spring이 동일한 Bean을 두 번 등록하려고 했기 때문이었다.
구버전 파일을 삭제하고 import도 함께 정리했지만
CI는 계속 실패했다.
조금 더 확인해 보니
내 브랜치가 develop보다 13개의 커밋이 뒤처져 있었고
GitHub가 오래된 머지 결과를 계속 빌드하고 있었다.
최신 develop을 머지하고
삭제된 테스트 파일 충돌을 해결한 뒤 다시 Push하자
CI가 정상적으로 통과했다.
이번 경험을 통해
CI가 실패한다고 해서 항상 코드 자체가 문제인 것은 아니라는 점을 배웠다.
브랜치의 최신 상태와 머지 가능 여부도 반드시 확인해야 한다는 것을 알게 되었다.
Git Local History
오늘은 Git 명령어를 잘못 입력해서 작업한 코드가 사라지는 일도 있었다.
다행히 IntelliJ에는 Local History 기능이 있어서
작업했던 내용을 모두 복구할 수 있었다.
비슷한 상황이 발생하면 Local History를 먼저 확인해 봐야겠다고 생각했다.
Git 협업 흐름
오늘 다시 정리한 협업 순서는 다음과 같다.
Git Issue 생성
↓
브랜치 생성
↓
기능 구현
↓
add
↓
commit
↓
push
↓
Pull Request
↓
Merge
↓
develop checkout
↓
git pull origin develop
↓
다음 기능 개발
협업에서는 항상 최신 develop을 유지하는 것이 중요하다.
- DDD는 코드를 잘 작성하는 방법이 아니라 비즈니스를 잘 표현하는 설계 철학이라는 것을 배웠다.
- 헥사고날 아키텍처는 비즈니스 로직을 외부 기술과 분리하기 위한 구조라는 점을 이해했다.
- Kubernetes가 운영 환경에서 왜 필요한지 알게 되었다.
- GitHub Actions CI는 코드뿐 아니라 브랜치 상태와 머지 상황도 함께 확인해야 한다는 것을 배웠다.
- IntelliJ의 Local History 기능으로 실수로 삭제한 코드를 복구할 수 있다는 점도 새롭게 알게 되었다.
오늘 가장 어려웠던 부분은 CI 오류를 해결하는 과정이었다.
처음에는 중복 Bean 등록만 해결하면 끝날 것이라고 생각했지만, 문제가 계속 발생하면서 원인을 찾는 데 시간이 걸렸다. 결국 브랜치가 최신 develop과 동기화되지 않아 GitHub가 오래된 머지 결과를 빌드하고 있었다는 것을 확인했고, 최신 브랜치를 머지한 뒤 충돌을 해결하면서 CI를 통과시킬 수 있었다.
프로젝트를 진행하면서 직접 적용해 보며 익숙해질 필요가 있다고 느꼈다.
오늘은 기능 구현보다 설계와 협업의 중요성을 많이 느낀 하루였다.
리뷰 생성 기능을 구현하면서 "누가 언제 어떤 조건에서 리뷰를 작성할 수 있는가"와 같은 비즈니스 규칙을 코드에 담는 과정이 중요하다는 것을 체감했음. 또한 CI 오류를 해결하며 단순히 에러 메시지만 보는 것이 아니라 브랜치 상태, 머지 과정, 협업 흐름까지 함께 살펴봐야 한다는 점도 배울 수 있었다.
오늘은 기능 하나를 완성한 것보다 왜 그렇게 설계해야 하는지를 고민해 본 의미 있는 하루였다.
'부트캠프 > TIL(Today I Learned)' 카테고리의 다른 글
| [내일배움캠프_사전캠프_8기]_Days_16 TIL_ 팀 프로젝트-Day8 (0) | 2026.07.13 |
|---|---|
| [내일배움캠프_사전캠프_8기]_Days_15 TIL_ 팀 프로젝트-Day7 (0) | 2026.07.10 |
| [내일배움캠프_사전캠프_8기]_Days_13 TIL_ 팀 프로젝트-Day5 (0) | 2026.07.08 |
| [내일배움캠프_사전캠프_8기]_Days_12 TIL_ 팀 프로젝트-Day4 (0) | 2026.07.07 |
| [내일배움캠프_사전캠프_8기]_Days_11 TIL_ 팀 프로젝트-Day3 (0) | 2026.07.06 |