둘셋 개발!

[Java] 디자인 패턴 (4) - 헥사곤 패턴 본문

카테고리 없음

[Java] 디자인 패턴 (4) - 헥사곤 패턴

23 2024. 3. 12. 21:25

헥사곤 패턴이란?

(출처: https://haandol.github.io/2022/02/13/demystifying-hexgagonal-architecture.html)

사진에서 보다시피 아키텍처 모양이 육각형이어서 헥사곤 패턴이다.

port와 Adapter을 사용하여 이어 간의 원치않은 종속성을 피할 수 있고,

중심부에 비즈니스 핵심 코어 코드를 캡슐화하고 외부 시스템(Adapter)과는 port를 통해 연결되기 때문에 비즈니스 로직 변경으로 인한 사용자 인터페이스 코드 변경, 반대로 사용자 인터페이스 코드 변경으로 인한 비즈니스 로직 변경을 막을 수 있다. (유연한 설계)

 

 

단순히 스프링 프로젝트를 하면 Controller -> Service -> Repositroy 이렇게 참조하는 형태가 가장 흔하다.

여기서 헥사곤 패턴을 적용시키면 Controller가 사용자 요청을 받고 비즈니스 로직을 처리하려면 바로 Service를 의존하는 것이 아니라 Port을 통해 접근한다. Port는 인터페이스로 되어 있고, 실제 비즈니스 로직을 처리하는 Service는 Port를 구현한 구현체이다. 

그리고 Service에서 처리하여 나온 결괏값을 Repository에 저장하고자 한다면, 바로 Repository를 호출하는 것이 아니라 Port를 통해 호출을 한다.

즉, 비즈니스 로직을 중심에 두고 외부와의 통신이 필요할 때는 Port를 통한다고 생각하면 된다.

 

그리고 Port를 통해 비즈니스로직과 통신하는 Adapter는 Controller, Repository 등등이 될 수 있겠다.

여기서 Adapter 종류는 2가지로 나눌 수 있다.

Primary adapter, Secondary adapter이다.

Primary adapter는 사용자 요청을 받아들이고 Secondary adapter는 도메인 모델의 처리를 담당한다.

그렇다면! Controller는 primary adapter, Respository는 secondary adapter라고 할 수 있겠다.

(위의 그림에서 Client라고 써져있는써져 있는 구름이 가리키는 Adapter가 Primary이고 External System이라고 써져 있는 구름이 가리키는 Adapter가 Secondary이다.)


 

예시

(해당 예시는 프리온본딩에서 배운 예시 그대로를 사용했습니다.)

우선 디렉토리 구조는 다음과 같다

헥사곤 디렉터리 구조

1. Adapter

in 폴더는 Primary Adapter가 있는 곳이다.

out 폴더는 Secondary Adapter가 있는 곳이다.

 

2. Application

port폴더의 in폴더는 service(비즈니스 로직)에 접근하기 위한 인터페이스가 있는 곳이다.

port폴더의 out폴더는 service(비즈니스 로직)이 외부 시스템에 접근하기 위한 인터페이스가 있는 곳이다.

 

3. domain

Domain Model에서 사용되는 DTO(Data Transfer Object)가 있는 곳이다.

 

 

- /adapter/in/PerformanceController

@RestController
@RequestMapping("/performance")
@RequiredArgsConstructor
public class PerformanceController {
	
	private final GetPerformanceInfoUseCase getPerformanceInfoUseCase;

	@GetMapping
	public List<PerformanceInfo> getAllPerformanceInfo() {
		return getPerformanceInfoUseCase.getPerformanceInfoAllList();
	}

}

 

- /application/port/in/GetPerformanceInfoUseCase

public interface GetPerformanceInfoUseCase {
	List<PerformanceInfo> getPerformanceInfoAllList();
}

 

Controller는 구현체인 서비스 코드를 바로 의존하는 것이 아니라 인터페이스를 의존하는 것을 볼 수 있다.

이 인터페이스는 Port역할을 한다.

 

 

- /application/service/GetPerformanceInfoService

@Component
@RequiredArgsConstructor
public class GetPerformanceInfoService implements GetPerformanceInfoUseCase {
	private final LoadPerformancePort loadPerformancePort;

	@Override
	public List<PerformanceInfo> getPerformanceInfoAllList() {
		return loadPerformancePort.getAllPerformanceInfo();
	}
}

우리가 아는 그 서비스 클래스이다!

코드를 보면 Port인 GetPerformanceInfoUseCase를 구현하는 것을 볼 수 있다.

그리고 레포지토리를 의존하지 않고 Port를 의존하고 있다.

 

- /application/port/out/LoadPerformancePort

public interface LoadPerformancePort {
	List<PerformanceInfo> getAllPerformanceInfo();
}

 

- /adapter/out/repository/SpringDataPerformanceRespository

public interface SpringDataPerformanceRepository extends JpaRepository<PerformanceEntity, UUID> {
	List<PerformanceEntity> findByIsReserve(String isReserve);

	PerformanceEntity findByName(String name);
}

이것도 우리가 아는 레포지토리 코드이다.

하지만 GetPerformanceInfoService는 이 레포지토리를 바로 의존하지 않고 Port를 거친다.

 

- /port/out/LoadPerformancePort/PerformancePersistenceAdapter

@Component
@RequiredArgsConstructor
public class PerformancePersistenceAdapter implements LoadPerformancePort {
	private final SpringDataPerformanceRepository springDataPerformanceRepository;
	@Override
	public List<PerformanceInfo> getAllPerformanceInfo() {
		return springDataPerformanceRepository.findByIsReserve("");
	}
}

 

이 어댑터가 LoadPerformancePort를 구현하고 있고 여기서 SpringDataPerformanceRepository를 의존하고 있다.

 

전체 구조를 다음과 같다

 

(/web은 무시하고 봐주세요!)

 

 

헥사곤 패턴을 사용해서 간단한 구조의 프로젝트를 한번 만들어 봐야겠다!