01. 왜 헥사고날 아키텍처인가?

김민규·2023년 6월 8일
0
post-thumbnail

도메인 헥사곤

  • 도메인 헥사곤은 실 세계 문제를 이해하고 모델링하는 활동을 나타낸다.
  • 도메인 헥사곤 안에는 중요한 비즈니스 데이터와 규칙에 관련된 엔티티들이 있다.
  • 엔티티(Entity)와 값 객체(Value Object)를 기반으로 도메인 헥사곤을 나타낸다.

엔티티

  • 엔티티는 좀 더 표현력 있는 코드를 작성하는 데 도움을 준다.
  • 연속성과 정체성은 엔티티를 결정하는 요소다.
public class Router {
	private final RouterType routerType;
    private final RouterId routerId;
    
    public Router(RouterType routerType, RouterId routerId) {
    	this.routerType = routerType;
        this.routerId = routerId;
    }
    
    public static Predicate<Router> filterRouterByType(RouterType routerType) {
    	return routerType.equals(RouterType.CORE)
        	? isCore() : isEdge();
    }
    
    private static Predicate<Router> isCore() {
    	return p -> p.getRouterType() == RouterType.CORE;
    }
    
    private static Predicate<Router> isEdge() {
    	return p -> p.getRouterType() == RouterType.EDGE;
    }
    
    public static List<Router> retrieveRouter(List<Router> routers, Predicate<Router> predicate) {
    	return routers.stream()
        	.filter(predicate)
            .collect(Collectors.<Router>toList());
    }
    
    public RouterType getRouterType() {
    	return routerType;
    }
}

값 객체

  • 값 객체는 무언가 고유하게 식별할 필요가 없는 경우
  • 객체의 정체성보다 속성에 관심을 갖는 경우에 도움이 된다.
public enum RouterType {
	EDGE,
    CORE;
}

애플리케이션 헥사곤

  • 애플리케이션 특화 작업을 추상적으로 처리하는 곳이다.
  • 유스케이스(Use Case), 입력포트(Input Port), 출력포트(Output Port)를 기반으로 한다.

유스케이스

  • 도메인 제약사항을 지원하기 위해 시스템의 동작을 소프트웨어 영역 내에 존재하는 애플리케이션 특화 오퍼레이션을 통해 나타낸다.
  • 유스케이스는 엔티티 및 다른 유스케이스와 직접 상호작용하고 그것들을 유연한 컴포넌트로 만들 수 있다.
public interface RouterViewUseCase {
	List<Router> getRouters(Predicate<Router> filter);
}

입력 포트

  • 입력 포트의 역할: 유스케이스가 소프트웨어가 하는 일을 설명하는 인터페이스라면 유스케이스 인터페이스를 구현해야 한다.
public class RouterViewInputPort implements RouterViewUseCase {

	private RouterViewOutputPort routerListOutputPort;
    
    public RouterViewInputPort(RouterViewOutputPort routerViewOutputPort) {
    	this.routerViewOutputPort = routerViewOutputPort;
    }
    
    @Override
    List<Router> getRouters(Predicate<Router> filter) {
    	var routers = routerListOutputPort.fetchRouters();
        return Router.retrieveRouter(routers, filter);
    }
}

출력 포트

  • 출력 포트의 역할: 유스케이스가 외부 리소스에서 데이터를 가져와야하는 상황이 있다.
public interface RouterViewOutputPort {
	List<Router> fetchRouters();
}

프레임워크 헥사곤

  • 소프트웨어와 통신할 수 있는 기술을 결정한다.
    • 드라이빙(driving): 입력 어댑터(Input Adapter)
    • 드리븐(driven): 출력 어댑터(Output Adapter)

드라이빙 오퍼레이션과 입력 어댑터

  • 드라이빙 오퍼레이션(Driving operation)은 소프트웨어에 동작을 요청하는 것이다.
  • SOAP, GRPC, STDIN, REST
public class RouterViewCLIAdapter {
	RouterViewUseCase routerViewUseCase;
    
    public RouterViewCLIAdapter() {
    	setAdapters();
    }
    
    public List<Router> obtainRelatedRouters(String type) {
    	return routerViewUseCase.getRouters(outer.filterRouterByType(RouterType.valueOf(type)));
    }
    
    private void setAdapters() {
    	this.routerViewUseCase = new RouterViewInputPort(RouterViewFileAdapter.getInstance());
    }

드리븐 오퍼레이션과 출력 어댑터

  • 이 오퍼레이션은 애플리케이션에서 트리거되고, 외부에서 데이터를 가져온다.
  • DBMS, FILE, SMTP, OAUTH2
public class RouterViewFileAdapter implements RouterViewOutputPort {
	
    @Override
    public List<Router> fetchRouters() {
    	return readFileAsString();
    }
    
    private static List<Router> readFileAsString() {
    	List<Router> routers = new ArrayList<>();
        try (Stream<String> stream = new BufferedReader(
        	new InputStreamReader(RouterViewFileAdapter.class.getClassLoader().
            	getResourceAsStream("routers.txt"))).lines()) {
                stream.forEach(line -> {
                	String[] routerEntry = line.split(";");
                    var id = routerEntry[0];
                    var type = routerEntry[1];
                    Router router = new Router(RouterType.valueOf(type), RouterId.of(id));
                    routers.add(router);
                });
        } catch (Exception e) {
        	e.printStackTrace();
        }
        return routers;
    }
}
profile
Backend Engineer, Vim User

0개의 댓글