헥사고날 아키텍처

뾰족머리삼돌이·2025년 2월 6일

서버

목록 보기
10/10

본인은 Spring으로 웹개발을 배웠고, 지금까지 3회의 프로젝트 경험을 가지고 있다. 그 경험속에서 모든 아키텍처는 3-Tier 아키텍처였고, 프로젝트 규모도 작았기때문에 별다른 문제점을 느끼지는 못했었다. MSA라는 용어가 주변에서 자주 들려왔지만, 소규모 프로젝트에서는 고려할만한 대상이 아니라는 말도 함께 들려왔기에 크게 의식하지 않았었다.

취업준비를 이어가면서 공고들과 채용설명회를 참가하다보니 우연히 헥사고날 아키텍처라는 용어가 눈에 띄었다. 조직내에서 헥사고날 아키텍처를 사용한다고 했고, 그 조직에 들어가고 싶었기 때문에 자연스럽게 아키텍처에 대해 공부하게 되어 글로 작성해본다.

개념적인 이해를 돕기위한 글이라 자세한 내용은 부족하다

소프트웨어 아키텍처

하나의 서비스를 구성하는 요소는 다양하다.

  • 일반 사용자가 엑세스 할 수 있는 UI 등을 제공하는 표현 계층
  • 비즈니스 로직이 처리되는 비즈니스 로직 계층
  • 서비스에 필요한 데이터들을 저장하고, 관리하는 데이터 계층

서비스는 이러한 계층들이 유기적으로 얽혀있는 구조를 띄고있다.
소프트웨어 아키텍처는 이 계층간의 구조와 관계를 나타내는 지침이나 원칙을 의미한다.

3-Tier 아키텍처

3-Tier 아키텍처는 표현 계층, 비즈니스 로직 계층, 데이터 계층이 선형적으로 연결되어 있는 구조를 의미한다.

계층과 계층이 직접적으로 연결되는 모놀리식 아키텍처의 대표적인 한 종류다.

서비스 이용자는 표현 계층을 통해 서비스에 접근하게되고, 어떠한 작업을 실행하면 비즈니스 로직 계층으로 요청이 전달된다. 이후, 데이터 요청이 필요한 작업이라면 데이터 계층를 통해 데이터를 얻어온 뒤 다시 표현계층에 처리된 결과를 출력하는 형식이다.

이 구조에서 비즈니스 로직 계층이 사라지면 2-Tier 아키텍처, 데이터 계층까지 사라지면 1-Tier 아키텍처가 된다.

이 구조에서 아쉬운 점은 각 계층간 종속성을 가지기 쉽다는 것이다.

예를들어, Spring 프로젝트를 만들었다고 생각해보자.
JPA를 사용한다고 가정했을 때, ServiceRepository에 대한 종속성을 가진다. Entity는 DB 테이블 규격과 JPA 문법에 맞춰지기 때문에 불필요한 기본생성자를 작성해야한다. 또한, 데이터 계층의 세부사항들이 노출되는 문제도 있다.

헥사고날 아키텍처

헥사고날 아키텍처는 애플리케이션의 코어 비즈니스 로직을 외부의 요청과 독립적으로 유지관리할 수 있는 아키텍처다.

헥사고날 아키텍처는 크게 3가지의 구성요소로 이루어져 있다.

  • 프라이머리 어댑터(driving adapter) : 외부 요청을 받아들이고 코어 컴포넌트를 호출
  • 코어 컴포넌트(application core) : 비즈니스 로직을 처리하며, 세컨더리 어댑터를 통해 외부 시스템과 연동
  • 세컨더리 어댑터(driven adapter) : 외부 시스템과의 상호작용

클린 아키텍처를 준수하기 때문에 저수준의 컴포넌트가 고수준 컴포넌트에 영향이 없도록 설계되어 있다. 이를 위해 포트와 어댑터를 이용한다

헥사고날 아키텍처의 다른 이름은 포트와 어댑터 아키텍처

클린 아키텍처

코드 레벨의 클린 코드나 지금 작성하는 클린 아키텍처 처럼 소프트웨어 개발에 클린이라는 용어가 들어갔을 때, 핵심은 추상화에 있다.

OOP를 예로 든다면 각 코드들의 결합도는 줄이고 응집도를 높혀 독립적인 객체들로 구성하는 것이 클린하게 만드는 거라고 볼 수 있다.

클린 아키텍처 또한, 아키텍처를 구성하는 각 계층들간의 결합도를 낮추고 응집도는 높히는 방식의 아키텍처 구성을 의미한다.

포트와 어댑터

Spring 기준으로, 일반적인 형태의 Service 클래스를 생각해보자

@Service 
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {

	private final UserRepository userRepository;

	@Override
	public UserResponseDto getUserInfo(Long userId) {
		// ...
		UserEntity user = userRepository.findById(userId);
		// ...

		return UserResponseDto.fromEntity(user, userInfo);
	}
}

대략적인 형태만 생각해봤을 때, Service에서 Repository를 이용하여 Entity를 얻어내는 형태를 띈다. 이는 현재 코드가 작성된 비즈니스 로직 계층이 외부 시스템인 DB에 종속되기 때문에, DB테이블 구조가 변경되는 등의 변경에 직접적인 영향을 받게된다.


헥사고날 아키텍처는 이러한 종속성을 제거하기 위해 어댑터 패턴을 이용한다.
외부 시스템을 port로 구분하고, 각 시스템에 해당하는 어댑터클래스를 생성하여 비즈니스 로직에서는 일관된 처리방식을 제공한다.

@Service 
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {

	private final UserJpaPort userJpaPort;

	@Override
	public UserResponseDto getUserInfo(Long userId) {
		// ...
		User user = userJpaPort.findById(userId);
		// ...

		return UserResponseDto.from(user);
	}
}
@Repository
@RequiredArgsConstructor
public class UserJpaAdapter implements UserJpaPort {

	private final UserRepository userRepository;

	@Override
	public User getUserInfo(Long userId) {
		// ...
		UserEntity userEntity = userRepository.findById(userId);
		// ...

		return User.fromJpaEntity(userEntity);
	}
}

이렇게 어댑터 패턴을 이용하여 외부 시스템을 호출하게 구성하면, 비즈니스 로직을 처리하는 곳에서는 항상 일관된 사용방식을 유지할 수 있다.

외부 시스템에 구조적인 변화가 있더라도 어댑터클래스만 수정해주면 되며,
다른 외부 시스템을 호출하도록 변경하는 것도 포트만 변경해주면 되므로 수월하다.

여기에 전략패턴까지 추가한다면 시스템 분류에따른 전략클래스를 생성하고, 전략 클래스내에서 관련있는 포트들을 관리하는 방식으로 작성할 수도 있을 것이다.

MSA

마지막으로 MSA(Microservice Architecture) 에 대해서도 간단하게 살펴보자.
MSA는 서비스 단위로 개발단위를 분류하여, 각 서비스들간의 유기적인 연결을 통해 아키텍처를 구성하는 방식이다. MSA또한 그 목적이 서비스간의 결합도를 낮추고 응집도를 높이는데 있다.

모놀리식 아키텍처에서의 한 시스템은 메일, 검색 등의 여러 세부 서비스로 구분이 가능한데, MSA는 이러한 세부 서비스 단위로 각각 시스템을 구성하는 것이다. 각 시스템이 제공하는 인터페이스를 통해 시스템간 통신으로 작업들을 처리한다.

이러한 시스템간 통신에는 네트워크 통신이 필요하며, 그 과정에서 AMQP와 같은 여러 프로토콜들을 고려해야한다.

헥사고날 아키텍처와 MSA의 관계를 생각해보자면
MSA 구조내에서 각 서비스 시스템들의 내부구조에 헥사고날 아키텍처가 적용될 수 있다. MSA자체는 하나의 시스템을 여러 세부 시스템으로 나눔으로써 결합도를 낮춘다. 여기에 헥사고날 아키텍처를 적용하면 나눠진 각 시스템내의 결합도 또한 낮출 수 있게된다.

참고

0개의 댓글