wampy 2023. 5. 1. 15:00

헥사고날 아키텍처 이해

  • 애플리케이션이 UI나 데이터베이스 없이 동작하도록 만드다?
  • 비즈니스 코드를 기술코드로부터 분리
    • 비즈니스 코드가 어디에 존재해야 하는지
    • 기술 문제로부터 격리되고 보호돼야 하는 위치가 어디인지
    • 도메인 헥사곤 생성
  • 도메인 헥사곤?
    • 소프트웨어가 해결하기를 원하는 핵시미 문제를 설명하는 요소를 결합한다.
    • 엔티티, 값 객체
      • 엔티티 (Entity) : 식별자(identiy)를 할당할 수 있는 것
      • 값 객체(Value Object) : 엔티티들을 합성하기 위해 사용하는 불변 컴포넌트
  • 애플리케이션 헥사곤?
    • 도메인 헥사곤에서 나오는 비즈니스 규칙을 사용, 처리하고 조정
    • 비즈니스 측면과 기술 측면 사이에 있으며
    • 양쪽과 상호작용하는 중개자 역할을 함
    • 포트와 유스케이스를 이용
  • 프레임워크 헥사곤
    • 외부 인터페이스를 제공
    • 애플리케이션 기능의 노출 방법을 결정할 수 있는 곳
    • REST 엔드포인트 정의...... 데이터베이스... 메시지브로커.... 메커니즘 정의....
    • 어댑터를 통해 기술 결정을 구체화함

도메인 헥사곤

  • 실 세계 문제를 이해하고 모델링하는 활동을 나타냄
  • e.g 네트워크 및 토폴로지 인벤토리를 생성하는 프로젝트...
    • 목적은 네트워크를 구성하는 모든 리소스에 대한 포괄적인 뷰 제공..
    • 도메인 헥사곤을 사용해 이러한 네트워크 및 토폴로지 요소를 코드로 식별 및 분류하고
    • 연관시키는 데 필요한 지식을 모델링

엔티티

  • 객체를 특징짓는 것 : 연속성, 정체성
    • 객체의 수명주기 및 변경 가능한 특성과 관련 있다
  • 연속성과 정체성을 통한 라우터의 특징 분석
    • 상태가 활성 상태인지 비활성 상태인지 정의
    • 다른 라우터나 네트워크 장비와 갖는 관계를 설명하는 일부 속성 할당 -> 수명주기를 가짐
    • 모든 라우터는 인벤토리에서 고유해야함 -> 식별자를 가져야 함
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;
    }

    @Override
    public String toString(){
        return "Router{" +
                "routerType=" + routerType +
                ", routerId=" + routerId +
                '}';
    }
}

값 객체

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

애플리케이션 헥사곤

  • 애플리케이션 특화 작업을 추상적으로 처리하는 곳
  • 도메인 비즈니스 규칙에 기반한 소프트웨어 사용자의 의도와 기능을 표현
  • ex. 같은 타입의 라우터들을 조회하는 방법?
    • 데이터 처리가 필요
    • 라우터 타입을 조회하기 위해 사용자 입력을 받아야 함 -> 입력 검증 필요 -> 비즈니스 규칙 사용
    • 이 모튼 작업은 유스케이스에서 그룹화 할 수 있다

유스케이스, 입력포트, 출력포트는 애플리케이션에 포함되어 있음

유스케이스

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

입력 포트

  • 소프트웨어의 의도를 구현한다 (유스케이스 인터페이스의 구현체)
public class RouterViewInputPort implements RouterViewUseCase {

    private RouterViewOutputPort routerListOutputPort;

    public RouterViewInputPort(RouterViewOutputPort routerViewOutputPort) {
        this.routerListOutputPort = routerViewOutputPort;
    }

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

출력 포트

  • 유스케이스가 목표를 달성하기 위해 외부 리소스에서 데이터를 가져온다
  • 유스케이스나 입력 포트가 오퍼레이션을 수행하기 위해 어떤 종류의 데이터를 외부에서 가져와야 하는지를
  • 기술에 구애받지 않고 설명하는 인터페이스로 표현
public interface RouterViewOutputPort {

    List<Router> fetchRouters();
}

프레임워크 헥사곤

  • 소프트웨어와 통신하는 기술을 구현하는 영역
  • 드라이버 관점 - 입력 어댑터
  • 드리븐 관점 - 출력 어댑터

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

  • 드라이빙 오퍼레이션
    • 소프트웨어 동작을 요청
    • 외부 엔티티가 시스템과 상호작용하고 외부 엔티티의 요청을 내 도메인 애플리케이션으로 변환하는 방법으로 정의
  • 외부 엔티티들이 시스템의 동작을 유도
public class RouterViewCLIAdapter {

    RouterViewUseCase routerViewUseCase;

    public RouterViewCLIAdapter(){
        setAdapters();
    }

    public List<Router> obtainRelatedRouters(String type) {
        return routerViewUseCase.getRouters(
                Router.filterRouterByType(RouterType.valueOf(type)));
    }

    private void setAdapters(){
        this.routerViewUseCase = new RouterViewInputPort(RouterViewFileAdapter.getInstance());
    }
}

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

  • 외부에서 소프트웨어 요구사항을 충족시키는 데 필요한 데이터를 가져옴
public class RouterViewFileAdapter implements RouterViewOutputPort {

    private static RouterViewFileAdapter instance;

    @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;
    }


    private RouterViewFileAdapter() {
    }

    public static RouterViewFileAdapter getInstance() {
        if (instance == null) {
            instance = new RouterViewFileAdapter();
        }
        return instance;
    }
}