계층형 설계 (Layered Design) 이해하기

계층형 설계는 소프트웨어를 여러 추상화 계층으로 나누어 구성하는 아키텍처 패턴입니다. 이를 통해 코드의 재사용성을 높이고 복잡도를 관리할 수 있습니다.

1. 직접 구현 (Direct Implementation)

계층형 설계에서 직접 구현은 중요한 원칙 중 하나입니다. 이 원칙은 함수의 시그니처가 나타내는 문제를 함수 본문에서 적절하게 구체화해야 한다는 것을 의미

1.1 단일 책임 원칙(SRP)과의 차이점

구분 단일 책임 원칙(SRP) 직접 구현 원칙
범위 클래스/모듈의 책임 개수 계층 간 의존성 방향
목적 변경의 이유를 하나로 제한 의존성을 한 단계로 제한
적용 대상 클래스/모듈 레벨 아키텍처 레벨
중점사항 무엇을 하는가 어떻게 하는가

1.2 직접 구현의 주요 문제점과 해결책

문제점

하위 계층의 코드가 잘못 설계되면 상위 계층까지 연쇄적으로 수정이 필요한 상황이 발생할 수 있습니다.

해결방안

  1. 인터페이스 분리 원칙 (Interface Segregation Principle)
  2. 의존성 역전 원칙 (Dependency Inversion Principle)

예제: 주문 처리 시스템

// Kotlin
// 1. 인터페이스 분리
interface OrderCreator {
    fun createOrder(request: OrderRequest): Order
}

interface OrderFinder {
    fun findById(id: String): Order?
}

// 2. 의존성 역전
interface OrderRepository {
    fun save(order: Order)
    fun findById(id: String): Order?
}

class OrderService(
    private val orderRepository: OrderRepository  // 구체적 구현체가 아닌 인터페이스에 의존
) : OrderCreator, OrderFinder {
    override fun createOrder(request: OrderRequest): Order {
        val order = Order.from(request)
        return orderRepository.save(order)
    }

    override fun findById(id: String): Order? {
        return orderRepository.findById(id)
    }
}

// JavaScript
// 1. 인터페이스 분리
class OrderCreator {
  createOrder(request) {
    throw new Error("Not implemented");
  }
}

class OrderFinder {
  findById(id) {
    throw new Error("Not implemented");
  }
}

// 2. 의존성 역전
class OrderService extends OrderCreator {
  constructor(orderRepository) {  // 인터페이스 주입
    super();
    this.orderRepository = orderRepository;
  }

  async createOrder(request) {
    const order = Order.from(request);
    return await this.orderRepository.save(order);
  }

  async findById(id) {
    return await this.orderRepository.findById(id);
  }
}

-- Haskell
-- 1. 인터페이스 분리
class OrderCreator m where
  createOrder :: OrderRequest -> m Order

class OrderFinder m where
  findById :: String -> m (Maybe Order)

-- 2. 의존성 역전
class OrderRepository m where
  save :: Order -> m Order
  findById :: String -> m (Maybe Order)

data OrderService m = OrderService {
  orderRepo :: OrderRepository m
}

instance (Monad m, OrderRepository m) => OrderCreator (OrderService m) where
  createOrder request = do
    let order = orderFromRequest request
    save (orderRepo order)