설계 문서를 어떻게 작성할지 고민


설계를 시작하면서 막혔던 문제가 있습니다.
요구사항명세, 클래스 다이어그램, 시퀀스 다이어그램, ERD…

이 모든 문서들을 작성하면서 느낀 의문이 있었습니다:

“이 문서들은 서로 다른 정보를 담고 있는 걸까? 아니면 같은 내용을 다르게 표현하는 걸까?”

처음엔 각 문서가 독립적으로 새로운 정보를 담아야 한다고 생각했습니다.
하지만 요구사항부터 시작해 클래스 다이어그램, 시퀀스 다이어그램, ERD까지 작성하다 보니 패턴이 보였습니다.

모든 문서가 결국 같은 요구사항을 다른 언어, 다른 관점으로 표현하고 있었던 것입니다.
그 관점의 차이를 이해하니, 각 문서를 어떻게 작성해야 할지 명확해졌습니다.

요구사항명세는 누가 읽을까?


“이 요구사항명세를 누가 읽을까?”

요구사항명세는 다양한 사람들이 읽습니다:

  • PM/기획자: 기능이 제대로 정의되었는지 확인
  • 개발자: 어떻게 구현할지 고민
  • QA: 테스트 케이스를 만들기 위해
  • 다른 팀의 개발자: 우리 시스템이 어떻게 동작하는지 이해하기 위해

각자 다른 관점을 가지고 있었습니다.

PM/기획자는

  • “사용자가 포인트를 사용해 상품을 주문할 수 있나?”를 확인합니다.
  • 비즈니스 플로우가 맞는지만 본다.

QA는

  • “포인트가 없을 때는 어떻게 되지?”라고 질문합니다.
  • 예외 케이스를 찾아 체크리스트를 만든다.

개발자는

  • “이 요구사항을 동시에 여러 사용자가 요청하면 동시성 문제가 생기지 않을까?”라고 고민합니다.
  • 요구사항을 만족하기 위해 어떤 기술적 문제를 해결해야 할지 본다.

같은 문장을 읽었는데도, 모두 다른 질문을 던지고 있었던 것입니다.

이걸 깨달으니 요구사항명세를 작성할 때 고민해야 할 것이 명확해졌습니다:

“이 문서를 읽는 모든 사람이 같은 이해를 할 수 있도록, 충분히 명확하게 작성해야 한다는 것입니다.”

클래스 다이어그램은 책임 분배의 시작


요구사항명세를 읽다 보면 개발자는 어떻게 코드를 완성해야할지 생각합니다.
그리고 객체지향 프로그래밍을 좋아하는 Java 개발자라면 당연히 아래와 같은 고민을 하게됩니다.

요구사항을 만족하는 과정에서 어떤 책임들이 필요하고, 그 책임을 누가 가져야 할까?

이 질문에 답하는 과정이 바로 클래스 다이어그램을 그리는 것이라고 생각했습니다.

요구사항에서 객체와 책임 찾기

요구사항명세를 읽으며 각 행동에 대해 “누가 이 책임을 가져야 할까?”를 계속 물었습니다.

예를 들어, 주문 기능의 요구사항을 보면:

- 사용자가 상품을 주문한다
- 주문 시 사용자의 포인트를 차감한다
- 포인트가 부족하면 주문이 실패한다
- 상품의 재고가 감소한다
- 재고가 부족하면 주문이 실패한다

이 요구사항들을 읽으면서, 저는 이렇게 생각했습니다:

요구사항 누가 책임질까? 메서드는?
포인트를 차감한다 UserPoint 객체 deduct(amount)
포인트가 부족하면 실패한다 UserPoint 객체 isEnoughPoint(amount)
상품의 재고가 감소한다 Product 객체 decreaseStock(quantity)
재고가 부족하면 실패한다 Product 객체 hasEnoughStock(quantity)
주문 정보가 저장된다 Order 객체 create(user, product, quantity)

이렇게 각 요구사항을 객체와 책임과 협력관계로 매핑하는 과정이 바로 클래스 다이어그램을 그리는 것입니다.
위 책임과 협력을 다이어그램으로 표현하면 아래와 같습니다.
클래스다이어그램

요구사항의 각 행동을 객체에 할당하다 보니, 객체들 간의 협력 구조가 자연스럽게 드러났습니다.

주문 생성 요청
  ↓
Order 객체가 생성되기 전에
  ↓
UserPoint는 "포인트가 충분한가?"를 판단
Product는 "재고가 충분한가?"를 판단
  ↓
모두 충분하면
  ↓
UserPoint는 "포인트를 차감"
Product는 "재고를 감소"
Order는 "주문 정보를 저장"

책임 분배의 장점

이렇게 책임을 분배함으로써 아래와 같은 장점을 얻을 수 있습니다.

  1. 각 객체의 책임이 명확해진다
    • 포인트 로직이 변경되어도 UserPoint만 수정하면 된다
    • Product의 재고 로직이 변경되어도 Product만 수정하면 된다
  2. 재사용성이 높아진다
    • UserPoint의 isEnoughPoint() 메서드는 주문뿐만 아니라 다른 기능에서도 사용할 수 있다
    • Product의 hasEnoughStock() 메서드도 마찬가지다
  3. 테스트하기 쉬워진다
    • UserPoint를 단독으로 테스트할 수 있다
    • Product를 단독으로 테스트할 수 있다
    • 각 객체의 책임에 대한 테스트가 명확해진다
  4. 협력 구조가 명확해진다
    • 누가 누구와 협력하는지 다이어그램에서 바로 볼 수 있다
    • 새로운 팀원도 이 관계도를 보고 코드 흐름을 빠르게 이해할 수 있다

객체들을 이렇게 설계함으로써, 서로 협력하면서 비즈니스 문제를 해결하고 변경에 유연한 구조를 만들 수 있습니다.

시퀀스 다이어그램은 인프라의 협력


클래스 다이어그램에서는 도메인 객체들이 어떤 책임을 가지는지 정했습니다.
하지만 실제 코드는 이 도메인 객체들만으로 이루어지지 않습니다.

시퀀스 다이어그램의 역할

클래스 다이어그램이 “도메인 객체들이 어떤 책임을 가질 것인가“를 정의했다면,
시퀀스 다이어그램은 “API, Service, Repository 같은 구성 요소가 어떻게 협력하여 도메인 객체를 활용할 것인가“를 보여줍니다.

계층의 책임

시퀀스 다이어그램을 설계하면서 각각의 계층의 책임을 아래와 같이 정의 했습니다:

  • API 계층: HTTP 요청을 받고 응답을 반환하는 역할만 한다. Service를 호출한다.
  • Service 계층: 비즈니스 로직의 흐름을 조율한다. Repository에서 도메인 객체를 조회하고, 도메인 객체의 메서드를 호출하여 비즈니스 규칙을 검증하고 실행한다.
  • Repository 계층: 데이터베이스에서 도메인 객체를 조회하고 저장한다.

이제 이 계층들이 요구사항을 만족하기 위해 어떻게 협력하는지 표현할 수 있습니다.

요구사항: “사용자가 상품을 주문하면, 포인트를 차감하고 재고를 감소시킨 후 주문을 저장한다”

이 요구사항을 만족하기 위해, 인프라는 다음과 같은 흐름으로 협력해야 합니다:

1. API가 HTTP 요청을 받는다
   ↓
2. Service가 "주문하려는 사용자의 포인트"를 Repository에서 조회한다
   ↓
3. Service가 UserPoint 객체에 "포인트가 충분한가?"를 질문한다
   (도메인 객체가 자신의 상태를 판단)
   ↓
4. Service가 "주문하려는 상품"을 Repository에서 조회한다
   ↓
5. Service가 Product 객체에 "재고가 충분한가?"를 질문한다
   (도메인 객체가 자신의 상태를 판단)
   ↓
6. 모두 충분하면, Service가 도메인 객체들에게 실행을 요청한다:
   - UserPoint에게 "포인트 차감"을 요청
   - Product에게 "재고 감소"를 요청
   - Order를 생성하고 Repository에 저장
   ↓
7. API가 결과를 HTTP 응답으로 반환한다

ERD는 도메인을 데이터로 표현하는 것


지금까지는 도메인 관점에서만 설계했습니다.

클래스 다이어그램으로 객체들과 그들의 책임을 정했고, 시퀀스 다이어그램으로 각 계층이 협력하는 흐름을 그렸습니다.

하지만 실제로 요구사항을 만족하려면 데이터를 어디에 어떻게 저장할지도 정해야 합니다.
클래스 다이어그램과 ERD는 같은 요구사항을 다른 관점에서 보는 것입니다.

도메인의 책임과 데이터베이스 테이블의 매핑

클래스 다이어그램의 객체들의 책임은 아래와 같았습니다.

요구사항 누가 책임질까? 메서드는?
포인트를 차감한다 UserPoint 객체 deduct(amount)
포인트가 부족하면 실패한다 UserPoint 객체 isEnoughPoint(amount)
상품의 재고가 감소한다 Product 객체 decreaseStock(quantity)
재고가 부족하면 실패한다 Product 객체 hasEnoughStock(quantity)
주문 정보가 저장된다 Order 객체 create(user, product, quantity)

여기서 주목할 점은, 클래스 다이어그램에서 정의한 책임이 ERD의 관계와 컬럼으로 나타난다는 것입니다.

  • UserPoint가 “포인트 상태를 알고 차감할 책임”을 가졌다 → user_points.balance 컬럼이 필요하다
  • Product가 “재고 상태를 알고 감소시킬 책임”을 가졌다 → products.stock 컬럼이 필요하다
  • Order가 “주문 정보를 생성할 책임”을 가졌다 → orders 테이블이 필요하다

정리


설계 문서는 같은 요구사항을 다른 관점에서 보는 것

설계 과정을 정리하면, 각 문서의 역할이 명확해집니다:

  1. 요구사항명세: “사용자 입장에서 무엇을 할 수 있어야 하는가?”
  2. 클래스 다이어그램: “그것을 만족하기 위해 어떤 책임이 필요하고, 누가 그 책임을 가져야 하는가?”
  3. 시퀀스 다이어그램: “실제 코드에서 각 계층이 어떻게 도메인 객체들과 협력하는가?”
  4. ERD: “그 책임을 완수하기 위해 어떤 데이터가 필요하고, 어떻게 저장되어야 하는가?”

결국 모든 문서는 같은 요구사항을 다른 언어, 다른 관점에서 표현하고 있습니다.

배운 점

각 문서를 작성할 때마다 던져야 할 질문이 있습니다:

  • 요구사항명세: “이 문서를 읽는 모든 사람이 같은 이해를 할 수 있을까?”
  • 클래스 다이어그램: “이 책임은 누가 가져야 할까?” → 여기서 나온 답이 코드의 형태를 결정합니다
  • 시퀀스 다이어그램: “각 계층이 이 책임을 완수하기 위해 어떤 순서로 협력해야 할까?”
  • ERD: “이 책임을 완수하기 위해 어떤 데이터가 필요할까?”

이 질문들에 제대로 답할 때, 비로소 일관된 설계가 완성된다고 생각이 됐습니다.
그리고 그 질문들에 대한 답이 곧 좋은 설계가 된다는 걸 배웠습니다.