스파르타 심화 과정에서 모놀리식 DDD 구조를 처음 접하며 배운 것들을 정리했습니다.
처음 스프링부트를 배울 때는 3 레이어드 아키텍처로 배웠다.
3 레이어드 모놀리식 DDD
──────────────────────────────
Controller → Controller (그대로)
Service → UseCase (흐름만 담당)
→ Domain (핵심 규칙 담당)
Repository → Repository (그대로)
결국 DDD는 3 레이어드에서 Service를 UseCase와 Domain으로 더 세분화한 것이다.
처음부터 DDD로 배우지 않고 3 레이어드로 전체 흐름을 익힌 뒤 DDD로 넘어오는 게 맞는 순서였다.
"사용자가 시스템으로 하는 행동 하나"
라면 끓이기에 비유하면:
라면 끓이기 유스케이스:
1. 물을 끓인다
2. 스프를 넣는다
3. 면을 넣는다
4. 계란을 넣는다
코드로 보면:
@Service
@Transactional
public class ReviewUseCase {
public void createReview(ReviewRequestDto request, Long userId) {
// 1. 주문 조회
// 2. 배달 완료 확인
// 3. 주문자 일치 확인
// 4. 리뷰 생성
// 5. DB 저장
}
}
순서가 곧 비즈니스 규칙이다. UseCase는 이 흐름을 조율하는 역할만 한다.
"여러 DB 작업을 하나로 묶는 것" — 전부 성공하거나 전부 실패하거나
계좌이체 예시:
1. A 계좌에서 10만원 차감 ✅
2. B 계좌에 10만원 추가 💥 오류 발생!
트랜잭션 없으면 → A 돈만 사라짐 (데이터 망가짐)
트랜잭션 있으면 → 1번도 자동으로 취소 (롤백)
@Transactional // 이 어노테이션 하나로 자동 롤백!
public void order(Long userId, Long itemId, int quantity) {
orderRepository.save(order); // 성공
pointService.addPoint(userId); // 성공
smsService.sendMessage(userId);// 💥 실패 → 위 두 개도 롤백!
}
파이썬 SQLAlchemy의 session.commit() / session.rollback() 을 자동으로 처리해주는 것이다.
SQL을 직접 쓰지 않고 DB를 다룰 수 있게 해주는 것
JPA → 표준 명세 (설계도)
Hibernate → JPA의 실제 구현체
Spring Data JPA → Hibernate를 더 편하게 쓰게 해주는 프레임워크
// JPA 없이
String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
// ... 수십 줄
// JPA 있으면
userRepository.findById(id); // 끝!
파이썬의 SQLAlchemy와 동일한 개념이다.
new 대신 메서드로 객체를 만드는 패턴
// ❌ 일반 생성자 - 뭐가 뭔지 모름
Account account = new Account("김철수", 1000, false);
// ✅ 정적 팩토리 - 의도가 명확함
Account account = Account.createSavingsAccount("김철수", 1000);
static 이라서 객체 없이 클래스이름.메서드이름() 으로 바로 호출 가능하다.
public class User {
protected User() {} // 외부에서 new User() 못 하게 막음
public static User create(String name, String email) {
User user = new User();
user.name = name;
user.email = email;
return user;
}
}
// 사용할 때
User user = User.create("김철수", "kim@email.com");
파이썬의 @staticmethod 와 완전히 동일한 개념이다.
"이 서비스만의 규칙과 계산"
배달앱 비즈니스 로직:
- 주문 금액이 12,000원 이상이면 배달비 무료
- 리뷰는 배달 완료 후에만 작성 가능
- 포인트는 결제금액의 1% 적립
비즈니스 로직이 아닌 것:
- 화면에 버튼 보여주기 → UI 로직
- DB에 저장하기 → 인프라 로직
- HTTP 요청 받기 → 네트워크 로직
DDD에서는 비즈니스 로직을 Service가 아닌 Domain Entity 안에 넣는 게 핵심이다.
// ✅ DDD 방식 - 규칙이 Entity 안에!
public class Review {
public static Review create(String content, int rating) {
if (content == null || content.isBlank()) {
throw new BusinessException(ErrorCode.REVIEW_CONTENT_EMPTY);
}
if (rating < 1 || rating > 5) {
throw new BusinessException(ErrorCode.INVALID_RATING);
}
// ...
}
}
리뷰 작성 후 상점 평점을 업데이트할 때 두 가지 방법이 있다.
// ❌ 강한 결합 - 리뷰가 Store 내부를 직접 알아야 함
public void createReview(...) {
reviewRepository.save(review);
store.updateTotalRating(rating); // 리뷰가 Store를 직접 건드림
}
// ✅ 느슨한 결합 - 이벤트만 던지고 끝
public void createReview(...) <{
reviewRepository.save(review);
eventPublisher.publish(new ReviewCreatedEvent(storeId, rating));
// 리뷰는 Store가 어디있는지 전혀 모름!
}
// Store 쪽에서 알아서 처리
@EventListener
public void onReviewCreated(ReviewCreatedEvent event) {
store.updateTotalRating(event.getRating());
}
유튜브 알림에 비유하면:
크리에이터가 영상 올림 (이벤트 발행)
↓
구독자한테 알림 울림 (이벤트 리스너)
크리에이터는 구독자가 누군지 몰라도 알림은 자동으로 울림!
DDD의 핵심 개념 중 하나. 관련 있는 값들을 의미있게 묶은 객체다.
// ❌ 다 때려넣기
public class Review {
private Long id;
private Long userId;
private String nickname;
private Long orderId;
private Long storeId;
private String content;
private int rating;
}
// ✅ 관련된 것끼리 묶기
public class Review {
@EmbeddedId
private ReviewId id;
@Embedded
private Reviewer reviewer; // 리뷰어 관련 묶음
@Embedded
private ReviewOrderInfo info; // 주문 관련 묶음
@Embedded
private ReviewContent content; // 내용 관련 묶음
}
@Embedded 를 쓰면 JPA가 자동으로 펼쳐서 하나의 테이블에 저장해준다.
코드에선 객체로 묶여있지만 DB에선 하나의 테이블!
소프트 딜리트란? DB에서 실제로 삭제하지 않고 삭제 시간만 기록하는 것
왜 쓰냐면:
- 삭제된 데이터도 나중에 필요할 수 있음
- 실수로 삭제해도 복구 가능
@Entity
@SQLRestriction("deleted_at IS NULL") // Entity에 붙임
public class Review {
private LocalDateTime deletedAt;
}
// 조회할 때 자동으로 조건 추가됨
reviewRepository.findAll();
// → SELECT * FROM reviews WHERE deleted_at IS NULL
DB에서 특정 컬럼을 빠르게 찾게 해주는 것
책의 목차와 같다.
목차 없으면 처음부터 끝까지 찾아야 하고, 목차 있으면 바로 페이지 찾아간다.
@Table(name="P_REVIEW", indexes = {
@Index(
name="idx_review_order_id",
columnList = "order_id, deleted_at",
unique = true
)
})
columnList = "order_id, deleted_at" → 이 두 컬럼을 묶어서 인덱스 생성unique = true → 이 조합이 유일해야 함 (하나의 주문에 리뷰 하나만!)3 레이어드로 전체 흐름 이해
↓
DDD로 역할을 더 명확하게 분리
기초가 있으면 DDD는 "Service를 더 정교하게 쪼갠 것" 으로 이해할 수 있다.
"왜?" 를 계속 물어보는 습관이 가장 중요한 것 같다. 이번 기회에 완벽하게 DDD구조를 파악 할 예정이다.