Programming/기타

핵사고날 아키텍처의 설계와 구현 - 어댑터의 이해

armyost 2023. 10. 25. 13:44
728x90

어댑터의 이해 

 

핵사고날 아미텍처에서 어댑터의 역할을 이해하기 위한 실용적인 비유는 컴퓨터의 원격 연결에 대한 비유다. 컴퓨터에 대한 원격 연결을 위한 텔넷, SSH, RDP 같은 다양한 프로토콜이 있다. 이러한 것들이 어댑터라고 할 수 있다. 핵사고날 시스템이 제공하는 기능에 엑세스 수단으로 지원되는 기술을 정의하는 포로토콜처럼 동작한다. 앞의 비유를 사용하면 운영체제를 자바나 다른 프로그래밍 언어를 사용해 개발된 애플리케이션으로 대체할 수 있다.

 

우리의 자바 어플리케이션이 핵사고날 애플리케이션이라고 가정하면 해당 애플리케이션에서 제공되는 기능은 애플리케이션 핵사곤과 도메인 핵사곤에서 제공되는 유스케이스 → 포트→ 그리고 비즈니스 규칙으로 구성된다. REST와 RPC 클라이언트에서 이러한 기능을 사용하고 싶다면 REST 어댑터와 RPC어댑터를 만들어야 한다. 애플리케이션 기능을 노출하는데 사용되는 어댑터를 입력 어댑터라고 한다. 

 

 

드라이빙 오퍼레이션 허용을 위한 입력 어댑터 사용

입력 포트는 유스케이스의 목표를 달성하기 위해 입력포트가 수행하는 오퍼렝션의 수행 방법을 지정해 유스케이스를 구현하는 수단이다. 입력포트 객체는 입력 어댑터가 보낸 자극을 통해 오퍼레이션을 수행하는데 필요한 모든 데이터를 수신한다. 그러나 입력데이터를 도메인 핵사곤과 호환되는 형식으로 변환하기 위해 이 단계에서 최종적인 변환이 발생할 수 있다. 

 

REST 입력 어댑터
이제 기반 RouterNetworkAdapter 추상 클래스를 정의했다. 계속해서 REST 어댑터를 생성해 보자.
RouterNetworkRestAdapter 생성자를 정의하는 것으로 구현을 시작한다.

public RouterNetworkRestAdapter(RouterNetworkUseCase outerNetworkUseCase){
	this.routerNetworkUseCase = routerNetworkUseCase;
}


CLI 입력 어댑터
두 번째 입력 어댑터를 만들기 위해 다시 기반 어댑터 클래스를 확장한다.

public class RouterNetworkCLIAdapter extends RouterNetworkAdapter { 
	public RouterNetworkCLIAdapter( RouterNetworkUseCase routerNetworkUseCase){
		this.routerNetworkUseCase = routerNetworkUseCase;
   	}
}

 

다양한 데이터 소스와 통신하기 위한 출력 어댑터 사용

 RDBMS의 대안으로, 데이터 조직화 수단으로 테이블과 칼럼, 스키마에 의존하지 않고 데이터를 저장하는 방법을 제안하는 NoSQL 데이터베이스가 나왔으며, NoSQL 방식은 도큐먼트(document), 키-값 스토어(key-value store), 와이드 칼럼 스토어(Wide column store), 그래프를 기반으로 다양한 데이터 저장 기법을 제공한다. RDBMS 방식에 국한되지 않고, 더 나은 방법으로 비즈니스 요구사항을 만족시키고 대안이 없어서 RDBMS에 의존하는 번거로운 솔루션을 회피하기 위해 소프트웨어 개발자들은 NoSQL 기술을 사용하기 시작했다.
데이터베이스 외에도 데이터 처리에 대한 소프트웨어 요구사항을 만족시키는 다른 데이터 소스가 사용 되었다. 몇 가지 예를 들면, 파일 시스템과 메시지 브로커, 디렉터리 기반 스토리지(LDAP), 메인프레임 스토리지는 소프트웨어가 데이터를 처리할 수 있는 몇 가지 방법이다. 클라우드 컴퓨팅 세계에서는 데이 터를 주고받기 위해 시스템을 다양한 기술과 통합하는 것이 더 자연스러워지고 있다. 이제 시스템은 이기종 기술이라는 맥락에서 스스로를 이해할 수 있어야 하기 때문에 이러한 통합은 소프트웨어 개발에서몇 가지 문제를 나타낸다. 이러한 상황은 이질성을 촉진하는 마이크로서비스 같은 아키텍처에서는 더 악화된다. 이러한 문제를 해결하려면 기술적으로 이기종 환경의 변화를 극복하는 기술이 필요하다.

public class RouterNetworkH2Adapter implements RouterNetworkOutputPort {
	private static RouterNetworkH2Adapter instance;
		@PersistenceContext
		private EntityManager em;
		private RouterNetworkH2Adapter(){
		setUpH2Database();
		}

		@Override
		public Router fetchRouterById(RouterId routerId) { 
        	var routerData = em.getReference(RouterData.class, routerId.getUUID());
			return RouterH2Mapper.toDomain(routerData);
		}

		@Override 
        public boolean persistRouter(Router router) {
			var routerData = RouterH2Mapper.toH2(router);
			em.persist(routerData);
			return true;
		}

		private void setUpH2Database() {
			var entityManagerFactory = Persistence.createEntityManagerFactory("inventory");
			var em = entityManagerFactory.createEntityManager();
			this.em = em;
		}
        
		/** 코드 생략 **/
}

재정의하는 첫 번째 메서드는 엔티티 관리자 참조를 사용해 H2 데이터베이스에서 라우터를 가져오기 위해 routerId 를 수신하는 fetchRouterById 다. Router 도메인 엔티티를 데이터베이스에 바로 매핑할 수 없다. 또한 데이터베이스 엔티티를 도메인 엔티티로 사용할 수도 없다. 이것이 fetchRouterById 에서 H2 데이터베이스에서 도메인으로 데이터를 매핑하기 위해 toDomain 메서드를 사용하는 이유다.

 도메인 모델이 우선이므로 시스템과 데이터베이스 기술의 결합을 원치 않는다는 것을 기억하라. 이것이 데이터베이스 타입에 직접 매핑하기 위해 RouterData ORM 클래스를 만들어야 하는 이유다. 여기서는 EclipseLink를 사용하지만 모든 JPA 호환 구현을 사용할 수 있다.

헥사고날 애플리케이션이 카프카로 이벤트를 내보내고 소비할 수 있도록 적절한 포트와 어댑터를 추가해야 한다. NotifyEventOutputPort 포트로 해당 작업을 수행해 보자.

public interface NotifyEventOutputPort {
	void sendEvent(String Event);
	String getEvent();
	}
}


다음으로 NotifyEventKafkaAdapter 출력 어댑터로 출력 포트를 구현한다. 먼저 카프카 연결 속성을 정의하여 NotifyEventKafkaAdapter 어댑터의 구현을 시작한다.

public class NotifyEventKafkaAdapter implements NotifyEventOutputPort { 
	private static String KAFKA_BROKERS = "localhost:9092";
	private static String GROUP_ID_CONFIG = "consumerGroup1";
	private static String CLIENT_ID = "hexagonalclient";
	private static String TOPIC_NAME = "topology-inventory-events";
	private static String OFFSET_RESET_EARLIER = "earliest";
	private static Integer MAX_NO_MESSAGE_FOUND_COUNT = 100;
	/** 코드 생략 **/
}


localhost:9092으로 설정된 KAFKA_BROKERS 변숫값이 카프카 토픽을 부트스트랩하는 데 사용되는 호스트와 포트에 해당한다는 점에 유의한다. topology-inventory-events로 설정된 TOPIC_NAME 변숫값은 메시지를 생성하고 소비하는 토픽을 나타낸다