2. Sever 개발의 멘탈모델

프론트엔드 개발자인 내가,,서버개발을 한다고 했을 때 뭐가 가장 다를까? 고민을 해보자면,

프론트엔드 개발은 1개의 빌드당 1개의 환경이 보장된다.
즉, 내가 작성한 코드는 사용자의 기기에서만 돌아가므로 각 환경에 대한 정책들을 고려하면 되는거고, 그걸 다 담아내면 되는 것. (폴리필이 가장 좋은 예시)

그런데 서버는 1개의 환경에서 구동된다. 그리고 여러 유저들이 딱 하나의 환경만 바라본다.
여기서부터 서버에서 신경써야할 것들이 파생된다.

1. Stateless

2. 동시성

3. 데이터중심 설계

4. Layer Architecture (계층구조)

// 전형적인 서버 구조

// 1. Controller (표현 계층)
@RestController
class ProductController(
    private val productService: ProductService
) {
    @PostMapping("/api/products")
    fun create(@RequestBody request: CreateProductRequest): ProductResponse {
        // 단순히 요청을 받아서 Service에 전달
        val product = productService.create(request)
        return ProductResponse.from(product)
    }
}

// 2. Service (비즈니스 로직 계층)
@Service
@Transactional
class ProductService(
    private val productRepository: ProductRepository,
    private val userRepository: UserRepository
) {
    fun create(request: CreateProductRequest): Product {
        // 비즈니스 규칙 검증
        val seller = userRepository.findById(request.sellerId)
            ?: throw UserNotFoundException()
        
        if (request.price < 0) {
            throw InvalidPriceException()
        }
        
        // 도메인 객체 생성 및 저장
        val product = Product(
            title = request.title,
            price = request.price,
            sellerId = seller.id
        )
        
        return productRepository.save(product)
    }
}

// 3. Repository (데이터 접근 계층)
interface ProductRepository : JpaRepository<Product, Long> {
    fun findBySellerId(sellerId: Long): List<Product>
    fun findByPriceBetween(min: Int, max: Int): List<Product>
}

// 4. Entity (도메인 계층)
@Entity
data class Product(
    @Id @GeneratedValue
    val id: Long = 0,
    val title: String,
    val price: Int,
    val sellerId: Long
)

5. 트랜잭션

@Service
@Transactional 
class TradeService(
    private val productRepository: ProductRepository,
    private val orderRepository: OrderRepository,
    private val userRepository: UserRepository,
    private val notificationService: NotificationService
) {
    fun completeTrade(productId: Long, buyerId: Long) {
        // 1. 상품 상태 변경
        val product = productRepository.findById(productId)
        product.status = ProductStatus.SOLD
        
        // 2. 주문 생성
        val order = Order(buyerId, productId)
        orderRepository.save(order)
        
        // 3. 판매자 포인트 증가
        val seller = userRepository.findById(product.sellerId)
        seller.points += product.price
        
        // 4. 알림 전송 (만약 여기서 에러 나면?)
        notificationService.sendTradeComplete(buyerId, product)
        
        // @Transactional이 있으면:
        // → 모두 성공 or 모두 롤백
        // → 중간에 실패하면 1,2,3도 취소됨
    }
}

6. 보안

// 프론트엔드 - 편의성 중심
function deleteProduct(id: number) {
    // 사용자가 삭제 버튼 보이면 권한 있다고 가정
    await api.delete(`/products/${id}`)
}

// 백엔드 - 항상 검증
@DeleteMapping("/api/products/{id}")
fun delete(@PathVariable id: Long, @AuthUser user: User) {
    val product = productRepository.findById(id)
    
    // 검증
    if (product.sellerId != user.id) {
        throw ForbiddenException("본인 상품만 삭제 가능")
    }
    
    productRepository.delete(product)
}