32장. 백엔드 애플리케이션의 객체 나누기
지금까지 코틀린 문법을 두루 배웠습니다.
이제 그 문법을 “구조“로 엮을 차례입니다.
11부에서는 지금까지 배운 것을 모아
백엔드 프로그램의 뼈대를 만들어 봅니다.
중요한 점을 먼저 밝힙니다.
이 부에서는 웹 프레임워크(스프링)를 쓰지 않습니다.
순수 코틀린만으로 백엔드의 “구조“를 익힙니다.
프레임워크는 나중에 배웁니다.
그 전에, 프레임워크가 왜 그런 구조를 권하는지
직접 손으로 만들며 이해하는 것이 목표입니다.
32.1 회원과 주문 예제 설계
이 부에서 계속 쓸 예제는
“회원이 상품을 주문하는” 간단한 서비스입니다.
필요한 개념을 먼저 떠올려 봅시다.
- 회원(User)
- 상품(Product)
- 주문(Order)
그리고 이들을 다루는 동작이 필요합니다.
- 회원 등록
- 상품 등록과 조회
- 주문 생성과 조회
이 개념들을
서로 다른 “역할“의 객체로 나눌 것입니다.
32.2 Domain Object
가장 중심에 있는 것이 도메인 객체(Domain Object)입니다.
도메인 객체는
“우리 서비스의 핵심 개념“을 표현합니다.
회원, 상품, 주문이 여기에 해당합니다.
class User(
val id: Long,
val name: String,
val email: String
)
도메인 객체는 서비스의 심장입니다.
데이터베이스나 화면 같은
바깥 세계의 사정에 휘둘리지 않아야 합니다.
(도메인 설계는 34장에서 깊게 다룹니다.)
32.3 Request와 Response
바깥에서 들어오는 요청과
바깥으로 나가는 응답도 객체로 표현합니다.
- Request : 들어오는 데이터
- Response : 나가는 데이터
이런 객체를 통틀어
DTO(Data Transfer Object)라고 부릅니다.
data class CreateUserRequest(
val name: String,
val email: String
)
data class UserResponse(
val id: Long,
val name: String
)
왜 도메인 객체를 그대로 쓰지 않고
따로 DTO를 만들까요?
바깥에 보여 줄 정보와
내부에서 다루는 정보가 다르기 때문입니다.
(이 이유는 33장에서 자세히 다룹니다.)
32.4 Service
Service는
“실제 일을 처리하는” 객체입니다.
여러 도메인 객체와 저장소를 엮어
하나의 기능을 완성합니다.
class UserService(
private val userRepository: UserRepository
) {
fun register(request: CreateUserRequest): UserResponse {
val user = User(
id = 0,
name = request.name,
email = request.email
)
val saved = userRepository.save(user)
return UserResponse(saved.id, saved.name)
}
}
Service는 “흐름“을 담당합니다.
“요청을 받아 → 도메인을 만들고 → 저장하고 → 응답을 돌려준다“는
줄거리를 여기서 씁니다.
32.5 Repository
Repository는
“데이터를 저장하고 꺼내는” 역할입니다.
핵심은 인터페이스로 정의한다는 점입니다. (8장)
interface UserRepository {
fun save(user: User): User
fun findById(id: Long): User?
}
이렇게 해 두면
저장 방식(메모리, 데이터베이스 등)이 바뀌어도
Service 코드는 그대로 둘 수 있습니다.
(Repository 설계는 35장에서 다룹니다.)
32.6 객체의 역할과 책임
지금까지 나눈 객체들의 관계를
그림으로 정리하면 이렇습니다.
요청(Request)
│
▼
Service ← 흐름을 조율
│
├─▶ Domain Object ← 핵심 개념과 규칙
│
└─▶ Repository ← 저장/조회
│
▼
응답(Response)
각 객체는 하나의 책임만 집니다.
- DTO : 데이터를 실어 나른다
- Domain : 핵심 규칙을 담는다
- Service : 흐름을 조율한다
- Repository : 저장하고 꺼낸다
하나의 객체가 하나의 일만 하면,
코드가 이해하기 쉽고 고치기도 쉽습니다.
이렇게 역할을 나누는 것을
관심사의 분리(Separation of Concerns)라고 합니다.
32.7 패키지 구성하기
역할이 나뉘면
파일도 그 역할대로 폴더에 묶는 것이 좋습니다.
(패키지는 1장에서 배웠습니다.)
com.example.shop
├─ domain User, Product, Order
├─ dto
│ ├─ request CreateUserRequest ...
│ └─ response UserResponse ...
├─ repository UserRepository ...
└─ service UserService ...
이렇게 나누면
“회원 관련 코드가 어디 있지?“를 찾기 쉽습니다.
패키지 구조는 곧
프로젝트의 지도가 됩니다.
32장을 마치며
이 장에서 우리는 다음을 배웠습니다.
- 백엔드 객체를 역할별로 나누는 이유
- Domain, DTO, Service, Repository의 역할
- 요청에서 응답까지의 흐름
- 관심사의 분리라는 원칙
- 역할에 맞춘 패키지 구성
다음 장부터는 이 각 역할을
하나씩 깊이 있게 설계해 봅니다.
먼저 DTO입니다.