728x90
1. 강의에서 사용하는 아키텍처는?
: 도메인 별로 분리를 고려하여, 레이어드 아키텍처와 EDA, 클린아키텍처(일부)를 종합하여 설계
2. 새로운 Architecture 를 적용한 배경
1) 도메인 구분없이 개발할 경우 거대한 강결합이 발생할 수 있는 구조로 개발된다.
3. 패키지 아키텍처 구조 및 Layer 설명
presentation
├── api
│ ├── controller // REST/gRPC 등 외부 요청 진입점
│ └── dto // Controller용 DTO (Request/Response)
└── event
│ └── consumer // Kafka, RabbitMQ 등 이벤트 수신 처리
└── scheduler
└── XXXscheduler // scheduler 이벤트 발생
Application //복잡한 도메인의 흐름제어가 필요한 경우 사용
├── facade //service flow 제어(단순 CRUD에선 생략)
domain
├── service // 도메인 서비스 (비즈니스 규칙)
├── repository interface // interface
├── entity // 도메인 모델 / Entity
└── domain dto // 도메인용 DTO
infrastructure
├── repository
│ ├── jpaRepository // JPA Repository 인터페이스
│ └── repository implementation // Repository 구현체
├── api client // 외부 API 호출 Adapter
└── producer
└── event // 이벤트 발행 구현체
Layer | |
Presentation | - 외부 요청을 받음. Controller에서 DTO 변환 후 service 호출 - 이벤트 Consumer 처리 - 스케줄러 이벤트 발생 |
Domain | - 핵심 비즈니스 로직. - 도메인 서비스/Entity/도메인 DTO - Repository Interface를 통해 외부 접근 |
Infrastructure | - 외부 기술 구현체 - DB CRUD - API, 메시징을 Adapter 패턴으로 구현 - 도메인 인터페이스를 구현. |
"Domain은 Infrastructure를 몰라야 함"
"Controller/Consumer/scheduler는 Application Layer 역할 없이 Presentation → Domain 호출 가능"
4. 예제소스
1) Controller with dto
// presentation/api/dto/OrderRequest.kt
package com.example.presentation.api.dto
data class OrderRequest(
val userId: Long,
val items: List<String>,
val totalAmount: Int
)
// presentation/api/dto/OrderResponse.kt
package com.example.presentation.api.dto
data class OrderResponse(
val orderId: Long,
val userId: Long,
val totalAmount: Int
)
// presentation/api/controller/OrderController.kt
package com.example.presentation.api.controller
import com.example.domain.service.OrderService
import com.example.presentation.api.dto.OrderRequest
import com.example.presentation.api.dto.OrderResponse
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/orders")
class OrderController(private val orderService: OrderService) {
@PostMapping
fun createOrder(@RequestBody request: OrderRequest): OrderResponse {
val order = orderService.createOrder(
userId = request.userId,
items = request.items,
totalAmount = request.totalAmount
)
return OrderResponse(orderId = order.id!!, userId = order.userId, totalAmount = order.totalAmount)
}
@GetMapping("/{id}")
fun getOrder(@PathVariable id: Long): OrderResponse? {
val order = orderService.getOrder(id) ?: return null
return OrderResponse(orderId = order.id!!, userId = order.userId, totalAmount = order.totalAmount)
}
}
2) Domain Service with dto, repository interface
package com.example.domain.dto
data class OrderDto(
val orderId: Long?,
val userId: Long,
val totalAmount: Int
)
package com.example.domain.repository
import com.example.domain.entity.Order
interface OrderRepository {
fun save(order: Order): Order
fun findById(id: Long): Order?
}
// domain/service/OrderService.kt
package com.example.domain.service
import com.example.domain.entity.Order
import com.example.domain.repository.OrderRepository
import org.springframework.stereotype.Service
@Service
class OrderService(private val orderRepository: OrderRepository) {
fun createOrder(userId: Long, items: List<String>, totalAmount: Int): Order {
require(totalAmount > 0) { "총 금액은 0 이상이어야 합니다." }
val order = Order(userId = userId, items = items, totalAmount = totalAmount)
return orderRepository.save(order)
}
fun getOrder(id: Long): Order? = orderRepository.findById(id)
}
3) Domain Entity
// infrastructure/repository/entity/OrderEntity.kt
package com.example.infrastructure.repository.entity
import com.example.domain.entity.Order
import jakarta.persistence.*
@Entity
@Table(name = "orders")
data class OrderEntity(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val userId: Long,
@ElementCollection
val items: List<String>,
val totalAmount: Int
) {
fun toDomain(): Order = Order(id = id, userId = userId, items = items, totalAmount = totalAmount)
companion object {
fun fromDomain(order: Order): OrderEntity = OrderEntity(id = order.id, userId = order.userId, items = order.items, totalAmount = order.totalAmount)
}
}
4) Repository Implementation
// infrastructure/repository/jpa/SpringDataOrderJpa.kt
package com.example.infrastructure.repository.jpa
import com.example.infrastructure.repository.entity.OrderEntity
import org.springframework.data.jpa.repository.JpaRepository
interface SpringDataOrderJpa : JpaRepository<OrderEntity, Long>
// infrastructure/repository/implementation/OrderRepositoryImpl.kt
package com.example.infrastructure.repository.implementation
import com.example.domain.entity.Order
import com.example.domain.repository.OrderRepository
import com.example.infrastructure.repository.jpa.SpringDataOrderJpa
import com.example.infrastructure.repository.entity.OrderEntity
import org.springframework.stereotype.Repository
@Repository
class OrderRepositoryImpl(private val jpa: SpringDataOrderJpa) : OrderRepository {
override fun save(order: Order): Order {
val entity = OrderEntity.fromDomain(order)
return jpa.save(entity).toDomain()
}
override fun findById(id: Long): Order? =
jpa.findById(id).orElse(null)?.toDomain()
}
5) Consumer
// presentation/event/consumer/OrderEventConsumer.kt
package com.example.presentation.event.consumer
import org.springframework.stereotype.Component
@Component
class OrderEventConsumer {
fun handleOrderCreatedEvent(event: String) {
println("Received Event: $event")
// 이벤트 처리 로직
}
}
6) Producer
// infrastructure/producer/event/OrderEventProducer.kt
package com.example.infrastructure.producer.event
import com.example.domain.entity.Order
import org.springframework.stereotype.Component
@Component
class OrderEventProducer {
fun publishOrderCreated(order: Order) {
println("Publishing Order Created Event: $order")
// Kafka, RabbitMQ 등 실제 전송 로직 구현
}
}
7) Scheduler
// presentation/scheduler/OrderScheduler.kt
package com.example.presentation.scheduler
import com.example.infrastructure.producer.event.OrderEventProducer
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component
@Component
class OrderScheduler(private val orderEventProducer: OrderEventProducer) {
@Scheduled(fixedRate = 60000) // 1분마다 실행
fun generateOrderEvent() {
//비지니스 로직이 담긴 서비스 메쏘드 호출
}
}
4. 장단점 요약!
728x90
'Kotlin Spring > Kotlin Spring 강의 내용' 카테고리의 다른 글
8) Spring 캐시 (1) HomeBody 상품 리스트 구현 내 "로컬 캐싱" 추가 (0) | 2025.09.02 |
---|---|
7) 주요 기능 개발(Back-end) (0) | 2025.09.02 |
6) 개발 architecture (4) EDA(Event-Driven Architecture) 패턴 (1) | 2025.08.31 |
6) 개발 architecture (3) 클린 아키텍처(Clean Architecture) (2) | 2025.08.31 |
6) 개발 architecture (2) 헥사고날 아키텍처(Hexagonal Architecture) (0) | 2025.08.31 |