[TDD](TDD기반 서비스 개발 후) 회고 및 객체지향 설계 고민

2025. 7. 12. 23:05·공부일기../Java

✅ 잘한 점 요약

  1. 예외 처리
    • ResponseEntityExceptionHandler 오버라이딩 잘함 → 예외 상황 정리 잘되어 있음.
  2. 테스트 작성
    • PR에 의도 명확히 작성, MockMvc 활용 잘함.
    • @WebMvcTest의 한계(테스트 컨텍스트 재사용 이슈)-> 추가 공부하긴해야함....!! 
  3. Mock과 실제 객체에 대한 고민
    • 테스트 설계 시 의미 있는 Mock/실제 객체 사용 구분 잘했음.

🔍 공부/개선이 필요한 부분 요약

1. 객체지향 설계 원칙

  • 책임과 역할의 분리 고민:
    • 잔액을 증가시키는 책임은 누구에게?
    • PointConstants 같은 클래스는 정말 필요했는가?
      ➤ 📚 참고: OOP와 책임 분리

초기에는 UserPoint 도메인 내에서 잔고 확인 등의 로직을 도메인 스스로 처리하도록 구현했다.
하지만 테스트를 작성하면서 동일한 숫자 값(예: 사용 제한 횟수 등)을 반복적으로 쓰게 되어, 이를 별도 상수 클래스로 분리해 관리하게 되었다.

처음엔 책임을 분리해야 한다는 원칙을 지키려 했지만, 오히려 지나치게 외부로 분산시키면서 도메인이 가진 핵심 규칙을 한눈에 파악하기 어려워졌다는 점을 느꼈다.
이로 인해 나중에 도메인 로직이 수정될 경우, 관련 상수나 규칙이 흩어져 있어 변경에 유연하게 대응하지 못할 수 있다는 문제점을 인식하게 되었다.

책임을 분리한다는 원칙에만 집중하다 보니 실제 책임의 응집도를 놓쳤고 그 결과 도메인이 스스로 판단해야 할 규칙을 외부로 빼내는 실수를 했던 것 같다.


2. 입력 데이터 유효성 분리 시야

  • 입력값 검증을 어떻게 구분하고 처리할 것인가?
    ➤ 📚 참고: 유효성 검증 개념

유효성 검증은 입력 시점(컨트롤러)에서 해야 할 것과 도메인 내부의 비즈니스 규칙으로 처리해야 할 것을 구분해야 한다는 점은 인지하고 있었다.

하지만 예를 들어 “하루 2회 사용 제한” 같은 검증은 어노테이션 기반의 커스텀 검증을 구현해야 했고 시간적 제약으로 인해 컨트롤러 단에서 임시로 처리하게 되었다..ㅋ
이는 비즈니스 규칙을 외부로 노출시키는 결과였고 추후 리팩토링 대상이라고 판단하고 있다.


3. DTO 관련 개선

  • 네이밍 구체화 필요: dto는 계층 이동 의미가 모호함.
  • DTO의 필드들은 final로 불변성 유지 → @Setter 제거.
  • DTO는 필요한 것만 정의: JPA에서는 과도하게 만들면 비효율적임.

초기에는 컨트롤러에서 유효성 검증을 위해 상황별로 각각의 DTO를 만들어야 한다고 생각해서 CRUD 기능마다 별도의 DTO를 생성해야 하는줄알았다.
하지만 공부를 진행하면서 `@Validated`와 Validation Group을 활용하면 하나의 DTO 내에서 필드별로 검증 조건을 나눌 수 있다는 점을 알게 되었고 앞으로는 불필요한 중복 생성을 줄이고 필요한 만큼만 DTO를 정의하려고 한다.

(추가 ! 하지만 실무에서는 그룹설정하는게 좀 번거로운작업? 이기때문에 각각 DTO를 설계하긴 한다구한다! )

 

또한 DTO는 계층 간 데이터를 전달하는 역할이므로 클래스 명이나 필드 명도 역할이 명확하게 드러나도록 구체적으로 작성하는 것이 중요하다는 것을 배웠다.
외부로부터의 입력값은 변하지 않도록 final로 선언하고 @Setter를 제거해 불변성을 보장하는 것이 바람직하다는 점도 이해하게 되었다.

JPA 환경에서는 DTO를 과하게 분리하거나 복잡하게 설계하면 오히려 유지보수나 성능에 불리할 수 있다는 점도 기억하고 필요한 필드만 포함해 효율적으로 구성하려 한다


4. Error 코드 관리 개선

  • PointErrorCode와 PointErrorMsg → 한 클래스로 응집하는 것이 유지보수에 좋음.

5. 주석 사용에 대한 고민

  • 무의미한 TODO 제거, 의미 있는 주석만 남기기.

6. 트랜잭션에 대한 이해

  • 이 어떤 상황에서 필요한지, 위험 요소는 뭔지 학습 필요.

이번 과제를 진행하면서 `@Transactional`을 충전과 히스토리 저장 로직에 자연스럽게 붙였지만 사실 현재 구조는 인메모리 기반이라 트랜잭션이 실제로는 동작하지 않는다는 것을 뒤늦게 인지했다.
충전과 이력 저장이 하나의 논리적 작업 단위이기 때문에 하나라도 실패하면 전체를 롤백해야 한다는 개념은 이해하고 있었지만 실제 환경과 어노테이션의 의미를 분리해서 판단하지 못했던 것 같다.

(추가! 트랜잭션 템플릿이라는것이 있는데 이것을 사용하면 트랜잭션을 좀 더 세밀하게 제어 할 수 있다. 조금 더 공부해서 포스팅을 하겠다!)

 

이를 계기로 트랜잭션에 대해 더 깊이 학습하게 되었고 다음과 같은 중요한 내용을 알게 되었다.

  1. `@Transactional`은 기본적으로 RuntimeException만 롤백 대상이다.
    `checked exception`이 발생한 경우에는 롤백되지 않기 때문에, 이때는 명시적으로 `rollbackFor = Exception.class` 설정을 추가해줘야 한다.
@Transactional(rollbackFor = Exception.class)

 

2.하나의 클래스 내에서 메서드끼리 내부 호출(self-invocation)을 하게 되면, `@Transactional`이 프록시를 우회하게 되어 트랜잭션이 적용되지 않는 문제가 발생한다.
이를 방지하려면 트랜잭션이 필요한 메서드는 별도의 Bean으로 분리하여 호출하는 방식이 필요하다.

이전까지는 단순히 "트랜잭션은 하나의 작업을 묶는 것" 정도로만 이해하고 있었는데

이번 경험을 통해 트랜잭션이 어떤 환경에서 작동하고 언제 기대한 대로 작동하지 않는지까지 고려할 수 있게 되었다.

 

스프링은 트랜잭션 처리 시 안전성 확보를 위해 “예상하지 못한 에러(RuntimeException)”가 발생하면 롤백되도록 설계가 되어있다. 

반대로 개발자가 의도적으로 처리할 수 있는 예외(Checked Exception)는 롤백할지 말지 직접 판단하라는 의미로 기본적으로는 롤백 안 된다.

 

롤백이 하고싶다면 아래와같은 코드를 작성해야한다. 

@Transactional(rollbackFor = Exception.class)
public void doSomething() throws Exception {
    throw new Exception("Checked 예외 발생");
}

7. JUnit 테스트 기법 익히기

  • assertAll, ParameterizedTest, .hasSize() 등 기능 학습해 보기.

8. 테스트 구조 개선

  • 현재 PointServiceTest는 의존 객체 많아지면 쉽게 깨짐 → 구조 고민해보기.

9. @Valid vs @Validated 차이 학습

  • 실제로 동작 방식이 다름, 언제 어떤 걸 써야 할지 판단 필요.

둘 다 유효성 검증(Validation)을 한다는점은 동일하지만 `@Validated`는 스프링 확장판이고 `@Valid`는 표준 JSR-303이라고한다.

항목 `@Valid` `@Validated`
패키지 javax.validation.Valid org.springframework.validation.annotation.Validated
그룹 기능 ❌ 없음 ✅ Validation Group 지원
대상 범위 단일 객체 검증 그룹 지정 및 계층적/부분 검증
주로 사용되는 상황 대부분의 단순 검증 그룹 검증이 필요한 복잡한 상황
내부 동작 `validator.validate(object)` 호출됨 (그룹 X) `validator.validate(object, SomeGroup.class)` 호출됨 → 그룹 기반 유효성 검사 실행
 사용위치 특정 ArgumentResolver를 통해 진행되기때문에 컨트롤러의 메소드 유효성만 검증 가능 AOP기반으로 스프링 빈의 유효성 검증을 위해 사용되며 클래스에는 @Validated를 , 메소드에는 @Valid를 붙여줌
발생에러 `MethidArgumentNotValidException` `ConstraintViolationException` (메소드에서)

10. 코드 개행 스타일

  • 코드 라인 맞추는 개행은 요즘 트렌드에서 지양됨 → 필요 없는 정렬은 줄이기.

'공부일기.. > Java' 카테고리의 다른 글

[JUnit5] @EnabledIfEnvironmentVariable 이해하기  (0) 2025.09.05
[동시성] 자바 동시성 문제 정리: synchronized, DB 락, Redis 분산락 비교  (5) 2025.08.16
[TDD] JUnit 테스트 입문 – Mockito로 실제 객체 vs Mock 비교  (0) 2025.07.11
[TDD] TDD 테스트 방법론  (0) 2025.07.11
스트림(Stream) / Optional<T> / 람다  (0) 2025.07.03
'공부일기../Java' 카테고리의 다른 글
  • [JUnit5] @EnabledIfEnvironmentVariable 이해하기
  • [동시성] 자바 동시성 문제 정리: synchronized, DB 락, Redis 분산락 비교
  • [TDD] JUnit 테스트 입문 – Mockito로 실제 객체 vs Mock 비교
  • [TDD] TDD 테스트 방법론
s0-0mzzang
s0-0mzzang
공부한것을 기록합니다...
  • s0-0mzzang
    승민이의..개발일기..🐰
    s0-0mzzang
  • 전체
    오늘
    어제
    • 전체~ (108)
      • 마음가짐..! (10)
      • 공부일기.. (76)
        • weekly-log (6)
        • Spring (19)
        • Java (18)
        • DataBase (10)
        • git (2)
        • JPA (6)
        • kafka (1)
        • Backend Architecture (3)
        • Troubleshooting (삽질..ㅋ) (2)
        • Cloud (1)
        • Docker (2)
        • 알고리즘 (1)
        • 리액트 (2)
        • Infra (3)
      • 하루일기.. (22)
        • 그림일기 (8)
        • 생각일기 (14)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 깃허브
  • 공지사항

  • 인기 글

  • 태그

    spring boot
    인프라 기초
    ERD
    다짐
    BufferedReader
    리팩토링
    항해플러스
    JPA
    SpringBoot
    스프링부트
    항해99
    Paging
    React
    ADC 환경
    spring
    TDD
    swagger
    StringTokenizer
    자바
    MySQL
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
s0-0mzzang
[TDD](TDD기반 서비스 개발 후) 회고 및 객체지향 설계 고민
상단으로

티스토리툴바