반응형
1. 레이어드 아키텍처 개념
: 레이어드 아키텍처는 전통적인 애플리케이션 구조로, 관심사의 분리
(Separation of Concerns)에 기반해 계층을 나누는 패턴.
2. 계층(Layer) 구조

1) Presentation Layer (Controller, UI)
- 사용자의 요청을 받아서 응용 계층(비지니스 계층)에 전달
- DTO 변환, HTTP 응답 처리
2) Business Layer (Service)
- 비즈니스 로직 처리
- 유스케이스 실행
- 트랜젝션 경계설정
- repository 호출
3) Infrastructure Layer (Repository(구현체), 외부 API 등 연결)
- DB CRUD 처리
- 외부 API 호출
- 메시지 브로커, 캐시 등 외부 시스템 처리
com.example
├─ presentation
│ ├─ UserController.kt
│ └─ CouponController.kt
│
├─ service
│ ├─ UserService.kt
│ └─ CouponService.kt
│
├─ repository
│ ├─ UserRepository.kt
│ └─ CouponRepository.kt
│
└─ common
└─ exception
3. 예제 코드
: 레이어드 아키텍처 기반으로 “회원 가입 & 쿠폰 발급”로직을 가볍게 구현단하면?
[요구사항]
- 사용자가 회원 가입을 하면, 환영 쿠폰을 1장 발급한다.
- 회원 가입 시 이메일 중복 검증이 필요하다.
- 가입 완료 후 이벤트 로그를 남긴다.
1) Controller
package com.example.user.presentation
import com.example.user.application.UserService
import org.springframework.web.bind.annotation.*
data class UserRegisterRequest(val email: String, val name: String)
data class UserResponse(val id: Long?, val email: String, val name: String)
@RestController
@RequestMapping("/users")
class UserController(
private val userService: UserService
) {
@PostMapping("/register")
fun register(@RequestBody request: UserRegisterRequest): UserResponse {
val user = userService.registerUser(request.email, request.name)
return UserResponse(user.id, user.email, user.name)
}
}
2) Service
@Service
class UserService(
private val userRepository: UserJpaRepository,
private val couponRepository: CouponRepository
) {
@Transactional
fun registerUser(email: String, name: String): User {
// 1. 이메일 중복 체크
if (userRepository.existsByEmail(email)) {
throw IllegalArgumentException("이미 존재하는 이메일입니다.")
}
// 2. 도메인 객체 생성 + 검증
val user = User(email = email, name = name).apply {
validateEmail()
}
// 3. 유저 저장
val savedUser = userRepository
.save(UserEntity.fromDomain(user))
.toDomain()
// 4. 환영 쿠폰 발급 (같은 서비스 내에서 repo 직접 사용)
val coupon = Coupon.issueWelcomeCoupon(savedUser.id!!)
couponRepository.save(CouponEntity.fromDomain(coupon))
return savedUser
}
}
3) Repository
@Repository
class UserRepository(
private val userJpaRepository: UserJpaRepository
) : UserRepository {
override fun existsByEmail(email: String): Boolean =
userJpaRepository.existsByEmail(email)
override fun save(user: User): User =
userJpaRepository
.save(UserEntity.fromDomain(user))
}
4. 주요 단점
"정통적인 레이어드 사용법이며, 실무에 바로 사용하기에는 여러 가지 문제점을 안고 있는것이 보입니다."
"테스트할 때 단위 테스트와 통합 테스트 경계가 모호"
- Service가 외부 자원(DB, API 등)에 직접 의존하는 구조
- 단위테스트의 경우 외부자원없이 순수 로직만 검증해야하만, service가 DB에 직접 의존하기때문에,
통합 테스트의 성격이 됨
- Mocking 이 어려움
"유스케이스 흐름이 Layered 구조에 제한"
- 여러 Layer를 거치면서 단일 유스케이스의 흐름이 분산됨
- Service에서 여러 도메인 호출 → 트랜잭션 관리, 예외 처리, 외부 호출이 섞임
→ Fat Service 문제 발생
"외부 시스템 교체가 어려움"
- Infrastructure Layer(DB, Kafka, REST API 등)가 Business Layer와 강하게 결합될 수 있음
- DB 교체, 메시징 시스템 변경 시 Service 코드 일부 수정 필요 → 유연성 낮음
"규모가 커지면 서비스가 항상 비대해짐"
- 서비스 단위로 역할이 혼합되면 Service가 Fat Service가 됨반응형
'Kotlin Spring > Kotlin Spring 강의 내용' 카테고리의 다른 글
| 6) 개발 architecture (3) 클린 아키텍처(Clean Architecture) (2) | 2025.08.31 |
|---|---|
| 6) 개발 architecture (2) 헥사고날 아키텍처(Hexagonal Architecture) (0) | 2025.08.31 |
| 5) 요구사항 분석 및 ERD 설계 (0) | 2025.08.31 |
| 4) Spring @controller, @service, @repository (0) | 2025.08.29 |
| 3) Spring 컨테이너, Bean (0) | 2025.08.26 |