일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 트랜잭션
- http
- 인터셉터
- 디자인패턴
- exception
- RestControllerAdvice
- java
- spring boot
- 스프링 시큐리티
- 스프링
- Interceptor
- 객체지향프로그래밍
- SQL
- 스프링부트
- network
- request
- proxy pattern
- aop
- aspect
- OOP
- Spring Security
- MYSQL
- response
- mybatis
- Redis
- 관점지향프로그래밍
- Filter
- 자바
- git
- Spring
- Today
- Total
장쫄깃 기술블로그
[Architecture] 육각형 아키텍처 (Hexagonal Architecture) 본문
들어가며
소프트웨어 개발이 복잡해질수록, 변화에 유연하게 대응할 수 있는 구조가 점점 더 중요해지고 있다. 육각형 아키텍처(Hexagonal Architecture)는 이런 요구에 부합하는 설계 패턴으로, 내부 비즈니스 로직과 외부 의존성을 명확히 분리하여 코드의 유지보수성과 확장성을 높이는 데 중점을 둔다. 이번 글에서는 다양한 아키텍처와 육각형 아키텍처의 개념, 구성 요소, 장점과 단점을 살펴보고, 실무에서 이를 어떻게 효과적으로 적용할 수 있는지 알아보겠다.
계층형 아키텍처 (Layered Architecture)
계층형 아키텍처는 애플리케이션을 기능별로 층(layer)으로 나누는 가장 전통적이고 널리 사용되는 아키텍처이다. 일반적으로 다음과 같은 3~4개의 계층으로 구성된다.
- 프레젠테이션 계층 (Presentation Layer)
- UI 또는 클라이언트와의 상호작용을 처리
- 사용자의 입력을 받고, 출력 데이터를 보여줌
- 예: Controller, View
- 서비스 계층 (Service / Business Layer)
- 비즈니스 로직 처리
- 프레젠테이션 계층과 데이터 계층 간의 연결을 담당
- 데이터 액세스 계층 (Data Access Layer)
- 데이터베이스와의 상호작용을 관리
- 데이터 CRUD 작업 수행
- (선택적) 도메인 계층 (Domain Layer)
- 도메인 객체와 엔터티 정의
장점
- 이해하고 구현하기 쉽다
- 책임이 명확히 나뉘어 있다
단점
- 계층 간 의존성이 강하다
- 변화에 유연하지 않다
클린 아키텍처 (Clean Architecture)
클린 아키텍처는 의존성 규칙과 유스케이스 중심 설계에 초점을 둔 아키텍처이다. 주된 목적은 비즈니스 로직을 외부 의존성으로부터 분리하여 애플리케이션이 더 유연하고 테스트 가능하도록 만드는 것이다.
출처 : https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
- 코어 계층
- 엔터티 (Entities): 비즈니스 규칙을 정의하는 객체
- 유스케이스 (Use Cases): 애플리케이션의 동작을 정의하는 인터랙션
- 외부 계층
- 프레젠테이션 (UI, API), 데이터베이스, 프레임워크, 디바이스 등 외부 요소들
- 코어 계층에 의존성을 갖지만, 코어 계층은 외부에 의존하지 않음
장점
- 비즈니스 로직이 독립적이어서 재사용 가능
- 변경에 강하다 (UI, DB, 프레임워크 교체 용이)
단점
- 설계가 복잡할 수 있다
- 구현 비용이 상대적으로 높다
육각형형 아키텍처 (Hexagonal Architecture)
육각형 아키텍처는 시스템을 중심(핵심 비즈니스 로직)과 외부 인터페이스(사용자 인터페이스, 데이터베이스, API 등)로 나누어 설계하는 방식이다. 포트와 어댑터 아키텍처 (Ports and Adapters Architecture)라고도 불리며, 유연성과 테스트 가능성을 향상시키는 것을 목표로 한다.
구조 요약
[Inbound Adapter] → [Inbound Port] → [Core (비즈니스 로직)] → [Outbound Port] → [Outbound Adapter]
(REST Controller) (Use Case) (도메인 서비스) (Repository) (JPA Repository)
주요 개념
1. 코어 (핵심 비즈니스 로직)
애플리케이션의 핵심적인 도메인 로직이 위치하는 영역이다. 외부 환경에 의존하지 않으며, 도메인 객체와 도메인 서비스로 구성됩니다.
2. 포트(Ports)
포트는 코어와 외부 세계 간의 인터페이스로, 내부와 외부 간의 통신 규약을 정의한다. 즉, 외부 세계와 코어 간의 모든 통신은 포트를 통해 이루어진다.
포트는 두 가지로 나뉜다.
- 인바운드 포트 (Inbound Ports)
- 외부에서 코어로 들어오는 요청을 정의
- 주로 사용자 인터페이스(UI)나 외부 시스템이 코어의 기능을 호출할 수 있도록 하는 진입점 역할
- 역할: 비즈니스 로직 실행을 위한 입력을 받고 결과를 반환
- 예제: CreateOrderUseCase 인터페이스, GetUserDetailsUseCase 인터페이스
- 아웃바운드 포트 (Outbound Ports)
- 코어에서 외부 시스템으로 나가는 요청을 정의
- 데이터베이스, 메시지 브로커, 외부 API 등과의 통신 규약을 정의
- 역할: 외부 시스템과의 상호작용을 추상화하여 코어가 특정 기술에 의존하지 않도록 함
- 예제: OrderRepository 인터페이스, NotificationService 인터페이스
📄 [Inbound Port (Use Case 인터페이스)]
public interface ProductInboundPort {
void addProduct(Product product);
Product getProductById(String id);
}
📄 [Outbound Port (Repository 인터페이스)]
public interface ProductOutboundPort {
void save(Product product);
Product findById(String id);
}
3. 어댑터 (Adapters)
어댑터는 포트를 구현하여 외부 시스템과 연결하는 부분으로, 구체적인 기술적인 세부사항을 처리한다.
어댑터도 두 가지로 나뉜다.
- 인바운드 어댑터 (Inbound Adapters)
- 외부 요청을 인바운드 포트로 전달하는 역할
- UI, REST API, CLI(Command-Line Interface) 등 외부 시스템과 사용자 입력을 처리하는 계층
- 역할: 외부 입력 데이터를 인바운드 포트가 이해할 수 있는 형식으로 변환
- 예제: REST Controller(Spring MVC의 @RestController), GraphQL Resolver
- 아웃바운드 어댑터 (Outbound Adapters)
- 아웃바운드 포트의 요청을 외부 시스템으로 전달하는 역할
- 데이터베이스, 파일 시스템, 메시지 브로커, 외부 API와의 구체적인 통신을 처리
- 역할: 비즈니스 로직에서 호출된 아웃바운드 포트 인터페이스의 구현체로 동작하며, 외부 시스템과의 실제 상호작용 수행
- 예제: JpaOrderRepository(JPA 기반 구현체), SmtpNotificationService(SMTP를 통한 이메일 전송), RedisCacheAdapter(캐싱)
📄 [Inbound Adapter (REST 컨트롤러)]
@RestController
@RequestMapping("/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@PostMapping
public ResponseEntity<Void> addProduct(@RequestBody Product product) {
productService.addProduct(product);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable String id) {
Product product = productService.getProductById(id);
return ResponseEntity.ok(product);
}
}
📄 [Outbound Adapter (Redis Repository 구현)]
@Repository
public class ProductOutboundAdapter implements ProductOutboundPort {
private final RedisTemplate<String, Product> redisTemplate;
public ProductOutboundAdapter(RedisTemplate<String, Product> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void save(Product product) {
redisTemplate.opsForValue().set(product.getId(), product);
}
@Override
public Product findById(String id) {
return redisTemplate.opsForValue().get(id);
}
}
Outbount Adapter 코드는 Redis 뿐만 아니라 DB 저장, 발행, 메시지 전송 등 다양한 외부 시스템으로의 전달 역할로 구현될 수 있다.
육각형 아키텍처의 장점
- 의존성의 역전 (Dependency Inversion)
- 코어는 외부 기술에 의존하지 않고, 외부가 코어에 의존하도록 설계
- 테스트 용이성
- 외부 어댑터를 모킹(mocking)하거나 대체하여 코어 로직을 독립적으로 테스트
- 유연성 및 확장성
- 새로운 어댑터(예: REST 대신 GraphQL API 추가)를 쉽게 추가
- 유지보수성
- 비즈니스 로직과 기술적인 구현 세부사항이 분리되므로, 변경사항이 한쪽에 영향을 덜 미침