Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
387 changes: 387 additions & 0 deletions keyword/chapter04/keyword.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,387 @@
# Q1. 아키텍처 구조란?

> 아키텍처 구조란 소프트웨어를 구성하는 요소들이 어떻게 나뉘고, 서로 어떻게 상호작용하는지에 대한 설계 구조이다. 즉, "코드를 어떻게 나눠서 관리할 것인가" 에 대한 약속이다.

---

### 왜 사용할까?

**아키텍처 구조 없이 코드를 설계 ⇒ 스파게티 코드**

하나의 파일에서 요청/응답 처리, DB 접근, 비즈니스 로직 수행을 전부 수행한다.

1. 코드가 길어질수록 읽기 어려워짐
2. 한 부분을 수정했을 때 다른 부분에 영향을 많이 줌
3. 팀원들이 특정 기능이 어디에 작성돼 있는지 확인하기 어려움

**아키텍처 구조를 사용한 코드 설계 ⇒ Good!!**

1. 각 계층이 본인 역할만 담당 (단일 책임 원칙)
2. 한 부분을 수정해도 다른 부분 영향 최소화 (개방 폐쇄 원칙)
3. 팀원들이 코드를 보기가 매우 수월해짐
4. 유지보수 하기 수월해짐

---

### 대표적인 패키지 구조 2가지

Spring에서는 기본적으로 `Controller` → `Service` → `Repository` 구조가 기본이다.

**1. 계층형 아키텍처 구조**

기능보다 역할을 기준으로 나눈 구조이다. `dto` / `controller` / `service` / `repository` / `domain` 등

```
com.inhaeval.backend
├── config
├── controller
├── domain
│ ├── Course
│ ├── CourseSlot
│ ├── EmailVerification
│ ├── Inquiry
│ ├── Member
│ ├── Notice
│ ├── PhoneVerification
│ ├── PointHistory
│ ├── Review
│ ├── ReviewLike
│ └── SavedTimetable
├── dto
├── exception
├── repository
├── security
├── service
├── util
├── InhaEvalApplication
└── SpringConfig
```
- 프로젝트 규모가 작을 때
- 도메인 수가 적을 때
- 팀 규모가 작을 때
- 빠르게 개발해야 할 때

**2. 도메인형 아키텍처 구조**

역할보다 기능을 기준으로 나눈 구조이다. 각 도메인을 먼저 분류하고, 그 안에서 각각의 `Controller`, `Service`, `Repository` 등을 생성한다.

- 프로젝트 규모가 클 때
- 도메인 수가 많을 때
- 팀원이 도메인 별로 파트를 나눠 작업할 때
---
# Q.2 Swagger란?

> REST API를 자동으로 문서화하고 테스트할 수 있는 도구
> `@RestController`, `@GetMapping` 등 Spring 어노테이션과 DTO 구조를 읽어서 API 명세서를 자동으로 생성해준다.

---

### 왜 사용할까?

**API 명세서가 필요하기 때문!**

백엔드 개발자와 프론트엔드 개발자는 API를 통해 개발을 진행한다. '이 API는 어떤 URL로 요청을 할지', '어떤 데이터를 넣을지(DTO)', '응답은 어떻게 받을지'에 대해 의논하고 결정해야 하는데, 이러한 것들을 정리해 놓은 것이 'API 명세서'이다.

---

### Swagger 외 다른 방법?

**1. 노션 or 구글 문서**
- 가독성 좋게 작성 가능
- 명세서를 직접 작성해야 하므로 오타, 누락, 연결 불일치 등 실수 가능

**2. Postman**
- 코드랑 연동 없음 (아예 독립적인 도구)
- API 목록 직접 생성
- API 변경 시 명세서 직접 변경
- URL, HTTP 메서드 등 직접 입력해서 테스트

반면 **Swagger ↔ Spring 연동**을 통해 코드 작성 시 명세서 자동 생성, API 변경 시 명세서 자동 변경 기능을 사용할 수 있고, 브라우저에서 직접 API 호출 + 응답 확인이 가능하다.

-URL, HTTP 메서드, DTO 구조는 자동으로 채워지고, 실제 값(토큰, Body 데이터 등)만 입력 후 바로 테스트 가능

---
# Q.3 도메인형 아키텍처란?

> 역할보다 기능을 기준으로 나눈 구조이다. 각 도메인을 먼저 분류하고, 그 안에서 각각의 `Controller`, `Service`, `Repository`, `Dto` 등을 생성한다.

---

### 도메인형 구조 설계 예시

```
domain/
member/
controller/
converter/
dto/
entity/
mapping/ ← 중간 테이블 Entity
enums/
exception/
code/
repository/
service/
mission/
...
review/
...
store/
...
global/
config/
exception/
response/
```

---

### 설계 시 유의 사항

**1. 도메인 분리 기준**

모든 테이블을 무조건 독립 도메인으로 만들지 않는다.

- **독립 도메인으로 분리** : 독립적인 비즈니스 로직이 있거나, 여러 도메인에서 참조될 때
- **상위 도메인에 포함** : 단독으로 쓰이는 로직이 없고, 특정 도메인의 일부로서 기능할 때

**2. 다대다 관계에서 중간 테이블 배치**

비즈니스 로직의 주도권을 갖는 쪽에 배치한다. 주도권을 갖는 도메인의 `Entity`에 `mapping` 패키지를 생성하고 그 안에 넣는다.

> ⚠️ 양쪽 도메인의 Entity에 모두 넣으면 중복 발생. 반드시 주도권을 갖는 도메인 쪽에만 넣도록 해야 한다.

---

### 도메인형 구조 장점

- **응집도 향상** : member 관련 코드는 전부 member 폴더 안에 있으므로 한 곳만 집중할 수 있다.
- **팀 협업 시 용이** : 각 도메인별로 업무를 나눠 개발을 진행하는 상황일 때 계층형 구조보다 작업이 수월하다.

### 도메인형 구조 단점

- 계층형 구조보다 초기 설계에 시간을 더 투자해야 한다.
- 도메인 경계가 애매한 경우 어느 도메인에 넣을지 판단해야 한다.

---
# Q.4 DDD vs 도메인형 아키텍처

> DDD는 Eric Evans의 "Domain Driven Design"이라는 책에서 소개된 개념으로, 복잡한 도메인 모델을 설계하고 구현하기 위한 방법론이다. DDD는 도메인 전문가와 개발자가 **협력**하여 도메인 모델을 정의하고, 이를 기반으로 소프트웨어를 설계하는 것을 목표로 한다.

- **DDD** = 설계 철학
- **도메인형 아키텍처** = 패키지 구조 (파일을 도메인 기준으로 분류)

**DDD = 도메인 주도 설계 = 도메인 영역의 언어와 개념을 소프트웨어 설계의 중심에 두자!**

'도메인' == 소프트웨어가 해결하고자 하는 영역

---

### 💡 용어 쉽게 이해하기

**상황 가정** : "마라토너와 개발자가 협력하여 러닝 서비스 애플리케이션을 만들기로 했다."

1. 마라토너는 러닝 지식이 많지만, 소프트웨어 설계 지식이 없다.
2. 개발자는 소프트웨어 설계 지식이 있지만, 러닝 지식이 없다.

좋은 소프트웨어를 만들기 위해서는 서로 소통이 잘 되어야 하고, 특정 개념에 대한 생각의 일치가 필요하다.

**[원래 방식]**

개발자들은 보통 데이터베이스 테이블부터 설계하며, 기본적으로 `user`, `speed`, `distance` 테이블 등을 만들고 필드를 정의한다. 이 접근법은 실제로 마라토너들이 어떤 용어를 사용하는지, 그들의 직업 세계가 어떻게 돌아가는지 깊은 이해 없이 시스템이 설계될 수 있다.

**[DDD 방식]**

개발자가 시스템 설계를 시작하기 전에 마라토너와 대화하며 러닝 도메인에 대한 언어, 배경지식, 개념 등을 배운다.

- 마라토너들이 훈련할 때 자주 쓰는 용어로는 '페이스', '케이던스', '젖산역치', 'VO2 max' 등이 있다.
- 마라토너들의 전반적인 훈련 종류, 훈련 과정, 대회 과정 등을 학습한다.

이런 용어와 개념에 대해 학습하고 소프트웨어 설계에 반영한다. 이렇게 하면 마라토너와 개발자들이 같은 언어로 소통하게 되므로 오해의 가능성이 줄어들고 잘못된 설계를 할 확률이 줄어든다.

---

### DDD의 핵심 개념 5가지

**1. 유비쿼터스 언어 (공통 언어)**

개발팀 + 비즈니스팀이 함께 사용하는 공통 용어집으로, 모든 대화, 문서, 코드에서 일관되게 사용된다.

**2. 바운디드 컨텍스트**

규모가 큰 조직에서는 같은 용어라도 부서마다, 상황(컨텍스트)마다 다른 의미를 가질 수 있다. 이러한 개념적 경계를 바운디드 컨텍스트라고 부른다.

> ex) '환자'라는 단어
> - 접수처 : 진료 예약 대상
> - 진료실 : 치료가 필요한 아픈 사람
> - 약국 : 처방전 가지고 있는 사람

**3. 엔티티와 값 객체**

도메인 모델의 구성 요소를 두 가지로 분리한다.

- **엔티티** = 고유한 정체성으로, 언제나 동일하게 식별됨 ex) 주민등록번호
- **값 객체** = 속성의 값만으로 식별되며, 모두 같으면 동일한 것으로 간주함 ex) 천원 지폐는 모두 동일한 것으로 간주함

**4. 애그리게이트 & 애그리게이트 루트**

애그리게이트는 함께 취급되어야 할 객체들의 집합이다. (마치 한 그룹처럼)
각 집합에는 애그리게이트 루트라는 대표가 있고, 외부에서는 이 대표를 통해서만 집합 속 구성원에게 접근할 수 있다.

> ex) 주문서 = 애그리게이트 루트 / 주문서 속 주문 항목들 = 구성원

**5. 도메인 서비스**

가끔 특정 개념이 특정 객체에 자연스럽게 포함되지 못하는 경우가 있다. 이런 경우 도메인 서비스로 분리한다.

> ex) 은행 계좌 간 '송금'은 출금 계좌, 입금 계좌 둘 중 어느 한 쪽에만 속하지 않기 때문에 '송금 서비스'라는 별도의 서비스를 만들어 관리한다.

---

### DDD의 핵심 목적

- **소통 개선** ⇒ "아.. 이거 말한 게 아닌데요.."와 같은 상황 발생 줄이기
- **변경 시 유연한 대처** ⇒ 요구사항이 변경될 때 어디를 수정할지 빠르게 확인 가능
- **복잡성 관리** ⇒ 큰 시스템을 바운디드 컨텍스트로 나누어 관리

---

### DDD는 언제나 최고인가?

| 상황 | 적합 여부 |
|---|---|
| 크게 복잡하지 않은 시스템 | ❌ 부적합 |
| 초기 시간 투자가 부담스러울 때 | ❌ 부적합 |
| 복잡한 비즈니스 로직, 대규모, 장기 프로젝트 | ✅ 적합 |

---
# Q.5 왜 DTO를 사용하는가?

> **DTO란?** Data Transfer Object의 약자로, 클라이언트 ↔ 서버가 필요한 데이터만 선별적으로 주고 받기 위해 사용되는 객체

---

### 💡 면접 비유로 이해하기

UMC-10th 면접 볼 때 "임원진 ↔ 나" 가 이야기를 주고 받는 상황과 아주 적절하다.

그 면접에 맞는 [필수 항목 / 선택 항목 / 면접 내용 / 면접 목적 / 상황 / 분위기] 등에 맞게 적절한 이야기를 꺼내 전달해야 한다.

예를 들어, '자기소개' 단계에서 나에 대한 모든 정보(출생지, 혈액형, 좋아하는 음식, 가족관계, 학교, 성적, 오늘 먹은 아침 메뉴, 이상형)를 전부 이야기할 필요가 있을까?
자기소개 상황에 맞는 필요한 정보만, 상황마다 다른 정보를 전달해야 한다.

> ex) 관심 분야, 평소 성격, 학과 등

| 면접 상황 | DTO 개념 |
|---|---|
| 면접관 | 클라이언트 |
| 나 | 서버 |
| 면접에서 전달하는 정보 | DTO |
| 나에 대한 모든 정보 | Entity |
| 면접 상황 및 목적 | API의 목적 |

---

### 1. 민감한 정보 보호

Entity는 DB 테이블과 직접적으로 매핑되는 객체이기 때문에, Entity 안에는 민감한 정보가 들어있는 경우가 많다.

> ex) 비밀번호, 주민번호, 휴대폰 번호 등

이러한 민감 정보를 모두 포함해서 클라이언트에게 전달할 필요는 없다. 필요한 데이터만 선별해서 데이터 전달 객체에 담아 전송한다. = **DTO**

---

### 2. 엔티티와 응답 구조의 분리

클라이언트로 넘겨줘야 할 데이터는 API마다 모두 다를 수 있다. 때문에 엔티티를 그대로 반환값으로 사용하면 유지보수가 힘들다.

만약 개발 중 DB 스키마를 변경해야 하는 상황이 발생했을 때, DTO 없이 엔티티 그대로 클라이언트에 전달하고 있다면:

```
Entity 변경 → 클라이언트 응답도 자동으로 변경 → 프론트엔드 코드도 수정 필요
```

반면에 DTO를 사용한다면:

```
Entity 변경 → DTO에서 흡수 → 클라이언트까지 영향 전파 차단
```

---

### 3. API 엔드포인트별 맞춤형 설계

'비밀번호 변경 상황'일 때 변경 과정에서 단계별로 요구하는 데이터가 모두 다르다.

1. **인증번호 전송** : 전화번호 / 이메일
2. **본인 검증** : 전화번호 / 인증번호
3. **비밀번호 초기화** : 전화번호 / 새 비밀번호 / 새 비밀번호 확인

각 API에 맞게 필요한 데이터만 분류해서 각 DTO를 설계해 클라이언트와 서버가 주고 받는다.

---
# Q.6 컨버터는 왜 사용하는가?

> **컨버터란?** DTO ↔ Entity 간 변환 로직을 한 곳에 모아놓은 클래스이다. `Service`나 `Controller` 여기저기에 흩어져 있는 변환 로직을 `Converter`라는 클래스로 분리해서 재사용성 & 가독성을 높이기 위해 사용한다.
>
> `@Converter` : JPA에서 특정 데이터 타입을 다른 타입으로 변환하는 기능을 가진 어노테이션

---

### 1. 컨버터가 없을 때

변환 로직이 `Service` 안에 직접 작성된다.

```java
public void signup(SignupRequest request) {
// DTO → Entity 변환 로직이 Service 안에 섞여있음
Member member = Member.builder()
.email(request.getEmail())
.password(encodedPassword)
.nickname(request.getNickname())
.build();
memberRepository.save(member);
}
```

**문제점**
- `Service` 계층에서 비즈니스 로직 + 변환 로직을 동시에 담당 → 단일 책임 원칙 위반
- 같은 변환 로직이 `Service` 계층 내 중복 코드 발생
- 변환 로직이 수정되면 중복 코드 모두 수정해야 함

---

### 2. 컨버터가 있을 때

변환 로직을 별도 클래스로 분리한다.

```java
// MemberConverter.java
public static Member toMember(SignupRequest request, String encodedPassword) {
return Member.builder()
.email(request.getEmail())
.password(encodedPassword)
.nickname(request.getNickname())
.build();
}

// MemberService.java
public void signup(SignupRequest request) {
String encodedPw = passwordEncoder.encode(request.getPassword());
Member member = MemberConverter.toMember(request, encodedPw); // 변환은 Converter에게
memberRepository.save(member);
}
```

**장점**
- `Service` 계층은 비즈니스 로직만, `Converter`는 변환 로직만 ⇒ 단일 책임 원칙 준수
- 변환 로직이 여러 곳에서 필요할 때 `Converter` 호출 ⇒ 중복 코드 제거
- 유지보수 용이 ⇒ 수정 시 `Converter` 코드만 수정하면 됨
- 가독성 향상 ⇒ `Service` 계층 확인 시 비즈니스 로직에만 집중할 수 있음

---

### Converter는 항상 무조건 사용하는가?

> 변환 로직이 단순하거나 한 곳에서만 쓰이는 경우는 `Service` 계층에 직접 작성하기도 한다.