[JpaTest] Layered Architecture vs Hexagonal Architecture

y001·2025년 1월 24일
post-thumbnail

1. 소프트웨어 아키텍처 비교: Layered vs Hexagonal

소프트웨어 아키텍처는 애플리케이션의 확장성과 유지보수성을 결정짓는 핵심 요소로, 설계 선택에 따라 개발 과정과 결과물에 큰 영향을 미칩니다. Layered Architecture(계층형 아키텍처)와 Hexagonal Architecture(헥사고날 아키텍처)는 서로 다른 설계 철학을 기반으로 한 대표적인 아키텍처 방식입니다. 이 글에서는 두 아키텍처의 이론적 기반과 실무적 차이를 비교하고, 주차 시스템을 활용한 현실적인 예제를 통해 그 특성을 심도 있게 분석합니다.


2. Layered Architecture (계층형 아키텍처)

Layered Architecture는 소프트웨어를 여러 계층으로 나누어 설계하는 전통적이고 직관적인 방식입니다. 각 계층은 서로 독립적이면서도 특정한 역할을 수행하며, 일반적으로 다음과 같은 구성으로 이루어집니다:

  1. Presentation Layer: 사용자 인터페이스를 담당하며, 요청을 처리하고 결과를 반환합니다.
  2. Application Layer: 비즈니스 로직과 흐름을 제어하며, 도메인 계층과 상호작용합니다.
  3. Domain Layer (Business Logic Layer): 핵심 비즈니스 로직을 캡슐화합니다.
  4. Infrastructure Layer: 데이터베이스, 외부 API, 메시지 브로커와 같은 외부 시스템과의 통신을 처리합니다.

장점

  • 명확한 계층 분리: 초보 개발자도 쉽게 이해할 수 있는 구조.
  • 재사용성 증가: 특정 계층의 코드가 다른 프로젝트에서도 재사용 가능.
  • 넓은 적용 범위: 다양한 프로젝트에서 검증된 표준적인 방식.

단점

  • 테스트 어려움: 계층 간 강한 결합으로 인해 단위 테스트가 복잡해질 수 있음.
  • 유연성 부족: 특정 계층의 변경이 다른 계층에 큰 영향을 줄 수 있음.
  • 비즈니스 로직과 인프라 결합: 도메인 로직이 데이터 접근 방식에 의존하는 경향.

예제: 주차 시스템 (Java + Spring Boot)

@RestController
@RequestMapping("/parking")
public class ParkingController {
    private final ParkingService parkingService;

    public ParkingController(ParkingService parkingService) {
        this.parkingService = parkingService;
    }

    @PostMapping("/tickets")
    public ResponseEntity<ParkingTicket> issueTicket(@RequestBody VehicleDto vehicleDto) {
        ParkingTicket ticket = parkingService.issueTicket(vehicleDto);
        return ResponseEntity.ok(ticket);
    }
}

@Service
public class ParkingService {
    private final ParkingRepository parkingRepository;

    public ParkingService(ParkingRepository parkingRepository) {
        this.parkingRepository = parkingRepository;
    }

    public ParkingTicket issueTicket(VehicleDto vehicleDto) {
        Vehicle vehicle = new Vehicle(vehicleDto.getLicensePlate());
        ParkingTicket ticket = new ParkingTicket(vehicle);
        parkingRepository.save(ticket);
        return ticket;
    }
}

@Repository
public interface ParkingRepository extends JpaRepository<ParkingTicket, Long> {}

구조도

Presentation Layer  ->  Application Layer  ->  Domain Layer  ->  Infrastructure Layer
(ParkingController)    (ParkingService)        (ParkingTicket)    (ParkingRepository)

3. Hexagonal Architecture (헥사고날 아키텍처)

Hexagonal Architecture는 애플리케이션의 비즈니스 로직(Core)을 외부 의존성으로부터 완전히 분리하는 것을 목표로 합니다. 이를 위해 포트와 어댑터 개념을 사용합니다:

  • 포트: 코어와 외부 시스템 간의 인터페이스로, 코어가 외부와 상호작용하는 방법을 정의합니다.
  • 어댑터: 포트를 구현하여 외부 시스템(예: 데이터베이스, 메시지 브로커, 사용자 인터페이스 등)과 실제로 연결되는 역할을 수행합니다.

장점

  • 테스트 용이성: 외부 시스템과의 의존성이 분리되어 단위 테스트가 간단해짐.
  • 확장성과 유연성: 새로운 어댑터를 추가하거나 기존 의존성을 교체하기 쉽습니다.
  • 비즈니스 로직의 독립성: 핵심 로직이 외부 기술에 의존하지 않음.

단점

  • 복잡성 증가: 초기 설계와 구현이 Layered Architecture보다 어려움.
  • 작은 프로젝트에 부적합: 단순한 요구사항에는 과도한 설계로 보일 수 있음.

예제: 주차 시스템 (Kotlin + Spring Boot)

// Core
interface ParkingService {
    fun issueTicket(vehicleDto: VehicleDto): ParkingTicket
}

class ParkingServiceImpl(private val parkingRepository: ParkingRepository) : ParkingService {
    override fun issueTicket(vehicleDto: VehicleDto): ParkingTicket {
        val vehicle = Vehicle(vehicleDto.licensePlate)
        val ticket = ParkingTicket(vehicle)
        parkingRepository.save(ticket)
        return ticket
    }
}

// Ports
interface ParkingRepository {
    fun save(ticket: ParkingTicket)
}

// Adapters
@Repository
class JpaParkingRepository(private val jpaRepository: SpringDataJpaParkingRepository) : ParkingRepository {
    override fun save(ticket: ParkingTicket) {
        jpaRepository.save(ticket)
    }
}

interface SpringDataJpaParkingRepository : JpaRepository<ParkingTicket, Long>

구조도

      Adapters (REST API, Repository)
                  |          ^
                  v          |
      Ports (ParkingService, ParkingRepository)
                  |
                  v
               Core (Business Logic)

4. Layered vs Hexagonal Architecture 비교

항목Layered ArchitectureHexagonal Architecture
구조수직적 계층 구조포트와 어댑터를 통한 수평적 구조
비즈니스 로직특정 계층(주로 도메인 계층)에 포함애플리케이션의 중심(core)에 독립적으로 위치
테스트 용이성외부 의존성 때문에 테스트가 어려울 수 있음외부 의존성과 분리되어 테스트가 용이
유연성특정 계층 간 강한 결합 가능성외부 의존성 변경에 대한 높은 유연성
학습 곡선비교적 쉬움비교적 어려움

📌 이 글은 TDD 강의를 학습한 내용을 바탕으로 재구성하였습니다. 문제가 되는 부분이 있다면 수정하겠습니다.

0개의 댓글