본문 바로가기

IT Book Summary/Clean Architecture

05 아키텍처 - 5

25장 계층과 경게

 

시스템을 구축하는 컴포넌트는 UI, 업무규칙, 데이터베이스 컴포넌트 외 그보다 더 많음

 

움퍼스 사냥 게임

텍스트 기반 사냥 게임.

텍스트 기반 UI는 유지. 게임규칙과 UI를 분리해 다양한 언어로 발매.

API를 생성해 게임규칙이 데이터 저장소 컴포넌트와 통신할때 사용.

 

의존성 규칙 준수하기

 

클린아키텍처?

UI에서 언어뿐 아니라 텍스트를 주고받는 매커니즘을 다양하게 할 경우.

언어를 통신 메커니즘으로부터 격리하는 API 생성

 

다이어그램

점선으로 된 테두리는 API를 정의하는 추상 컴포넌트.

추상 컴포넌트는 아래의 컴포넌트가 구현.

API는 구현하는 쪽이 아닌 사용하는 쪽에 정의되고 소속.

 

GameRules 내부 코드에서 사용하고 Language 내부 코드에서 구현하는 다형적 Boundary 인터페이스가 있음.

Languege도 동일한 구조 발견.

 

이 경우 해당 Boundary 인터페이스가 정의하는 API는 의존성 흐름의 상위에 위치한 컴포넌트에 속함.

 

 단순화된 다이어그램

화살표가 최상위 GameRules 를 향함.

흐름 횡단하기

데이터 흐름을 세개의 흐름으로 분리하고, 흐름은 모두 GameRules가 제어

 

Network 컴포넌트 우가

흐름 분리하기

또 다른 정책 집합이 존재한다.

플레이어의 생명력, 특정사건 해결하는 비용, 얻게될 소득 등.

고수준 정책에서는 플레이어의 상태를 관리.

 

고주준 정책이 플레이어를 관리

마이크로서비스 추가

MoveMagagement 와 PlayerManagement 사이 아키텍처 경계가 존재.

 

마이크로서비스 API 추가

결론

아키텍처 경계는 어디에서 존재한다.

이것이 언제 필요한지 신중하게 파악해야 한다.

You Aren't Going to Need It. 오버엔지니어링이 나쁠때가 있으므로 주의해야함.

 

경계의 구현비용이 그걸 무시해서 생기는 비용보다 적어지는 변곡점에서 경계를 구현하는 것.

 


26장 메인 Main 컴포넌트

나머지 컴포넌트를 생성, 조정, 관리하는 컴포넌트.

 

 

궁극적인 세부사항

메인은 세부사항으로 가장 낮은 수준의 정책.

시스템의 초기 진입점이며, 어떤것도 메인에 의존하지 않는다.

 

의존성을 주입하는 일은 메인 컴포넌트에서 이루어짐.

 

움퍼스 사냥게임 hunt the wumpus 메인 컴포넌트

코드의 나머지 핵심 영역에서 구체적 문자열 알지 못하게 함.

public class Main implements HtwMessageReceiver {
  private static HuntTheWumpus game;
  private static int hitPoints = 10;
  private static final List<String> caverns = new ArrayList<>(); 
  private static final String[] environments = new String[]{
  "bright",
  "humid",
  "dry",
  "creepy",
  "ugly",
  "foggy",
  "hot",
  "cold",
  "drafty",
  "dreadful"
};
  private static final String[] shapes = new String[] {
  "round",
  "square",
  "oval",
  "irregular",
  "long",
  "craggy",
  "rough",
  "tall",
  "narrow"
};
  private static final String[] cavernTypes = new String[] {
    "cavern",
    "room",
    "chamber",
    "catacomb",
    "crevasse",
    "cell",
    "tunnel",
    "passageway",
    "hall",
    "expanse"
};
private static final String[] adornments = new String[] { 
    "smelling of sulfur",
    "with engravings on the walls",
    "with a bumpy floor",
    "",
    "littered with garbage",
    "spattered with guano",
    "with piles of Wumpus droppings",
    "with bones scattered around",
    "with a corpse on the floor",
    "that seems to vibrate",
    "that feels stuffy",
    "that fills you with dread"
  };

 

HtwFactory 사용해 게임을 생성하는 방식 주목.

클래스 이름을 직접 전달. 이 클래스에 변경이 생겨도 재 컴파일/배포 하지 않기 위함.

 

public static void main(String[] args) throws IOException { 
  game = HtwFactory.makeGame("htw.game.HuntTheWumpusFacade", new Main());
  createMap(); BufferedReader br =
      new BufferedReader(new InputStreamReader(System.in)); 
  game.makeRestCommand().execute();
  
  while (true) {
    System.out.println(game.getPlayerCavern()); 
    System.out.println("Health: " + hitPoints + " arrows: " +
    game.getQuiver()); 
    HuntTheWumpus.Command c = game.makeRestCommand();
    System.out.println(">");
    String command = br.readLine(); 
    
    if (command.equalsIgnoreCase("e"))
      c = game.makeMoveCommand(EAST);
    else if (command.equalsIgnoreCase("w"))
      c = game.makeMoveCommand(WEST);
    else if (command.equalsIgnoreCase("n"))
      c = game.makeMoveCommand(NORTH); 
    else if (command.equalsIgnoreCase("s"))
      c = game.makeMoveCommand(SOUTH); 
    else if (command.equalsIgnoreCase("r"))
      c = game.makeRestCommand();
    else if (command.equalsIgnoreCase("sw"))
      c = game.makeShootCommand(WEST);
    else if (command.equalsIgnoreCase("se"))
      c = game.makeShootCommand(EAST);
    else if (command.equalsIgnoreCase("sn"))
      c = game.makeShootCommand(NORTH); 
    else if (command.equalsIgnoreCase("ss"))
      c = game.makeShootCommand(SOUTH); 
    else if (command.equalsIgnoreCase("q"))
      return;
    c.execute(); 
  }
}

대부분 메인함수에서 처리하지만 명령어를 실제로 처리하는 일은 다른 고수준 컴포넌트로 위임.

 

지도생성 main에서 처리.

private static void createMap() {
  int nCaverns = (int) (Math.random() * 30.0 + 10.0); while (nCaverns-- > 0)
  caverns.add(makeName());
  for (String cavern : caverns) {
    maybeConnectCavern(cavern, NORTH);
    maybeConnectCavern(cavern, SOUTH);
    maybeConnectCavern(cavern, EAST);
    maybeConnectCavern(cavern, WEST);
  }
  String playerCavern = anyCavern(); 
  game.setPlayerCavern(playerCavern); 
  game.setWumpusCavern(anyOther(playerCavern)); 
  game.addBatCavern(anyOther(playerCavern)); 
  game.addBatCavern(anyOther(playerCavern)); 
  game.addBatCavern(anyOther(playerCavern));
  game.addPitCavern(anyOther(playerCavern)); 
  game.addPitCavern(anyOther(playerCavern)); 
  game.addPitCavern(anyOther(playerCavern));
  game.setQuiver(5); 
  }
// much code removed... 
}

 

메인은 고수분 시스템을 위해 모든것을 로드한 후 제어권을 고수준에 넘김.

 

결론

메인을 애플리케이션의 플러그인이라 생각하자.

플러그인 컴포넌트로 여기고 아키텍처 경계 바깥에 위치. 설정관련 문제를 더 쉽게 해결 가능.


27장 '크고 작은 모든 Great and Small' 서비스들 

서비스 지향 아키텍처와 마이크로서비스 아키텍처

  • 상호결합이 철저히 분리되는것 처럼 보임
  • 개발과 배포 독립성을 지원하는것처럼 보임

서비스 아키텍처

서비스를 사용한다는 것이 본질적으로 아키텍처에 해당하는것은 아님.

단순히 애플리케이션 행위를 분리할 뿐인 서비스라면 함수호출에 불과. 아키텍처 관점에서 중요하진 않음.

 

결국 서비스는 프로세스나 플랫폼 경계를 가로지르는 함수호출임.

아키텍처적으로 중요한 서비스인지가 핵심.

 

서비스의 이점

결합 분리의 오류

시스템을 서비스들로 분리해서 얻는 이점 -> 서비스 사이 결합이 분리된다는 점.

하지만 공유하느 데이터에 의해 서비스는 강력히 결합될수 있음.

 

서비스 인터페이스가 잘 정의되어있으면 이점이 있으나, 

함수 인터페이스보다 더 정교하게 잘 정의되는 것이 아니므로 이점이 없을수도.

 

개발 및 배포 독립성의 오류

각 서비스를 전담팀이 담당하여, 작성, 유지보수 운영 책임을 가질 수 있음.

-> 개발 및 배포 독립성 확보 확장 scalable 가능한 것.

 

- 서비스 기반 시스템 외에 모노리틱, 컴포넌트 기반 시스템으로도 구축가능.

- 데이터나 행위에 결합되어 있으면 그 정도에 맞게 조정해야함.

 

야옹이 문제

택시 통합시스템을 마이크로서비스를 기반으로 확장하는 예시.

TaxiUI 서비스가 고객 담당.

TaxiFinder 서비스는 TaxiSupplier 현황을 검토해 택시 후보 선별.

TaxiFinder 서비스는 사용자에 할당된 단기 데이터 레코드에 후보 택시들 정보 저장.

TaxiSelector 서비스는 사용자 지정 조건을 기초로 적합한 택시 선택.

TaxiSelector가 해당택시를 TaxiDispatcher 서비스로 전달.

TaxiDispatcher 서비스는 택시에 배차 지시.

 

택시 통합 시스템의 서비스들

신규 서비스인 야옹이 배달 서비스를 추가하려는 경우.

 

이 서비스들은 모두 결합되어 있어서 

새로운 기능이 기능적 행위를 횡단하는 상황에 매우 취약하다.

 

객체가 구출하다

컴포넌트 기반 아키텍처에서 해결방법.

-> 다형적으로 확장할 수 있는 클래스 집합을 생성해 새로운 기능을 처리하도록 함.

 

객체지향방식으로 횡단 관심사를 처리

배차에 특화된 로직은 Rides 컴포넌트로 추출되고,

야옹이 신규기능은 Kittens 컴포넌트로 들어감.

두 컴포넌트는 기존 컴포넌트에 있는 추상 기반 클래스를 템플릿 메서드 Template Method나 Strategy 패턴을 이용해 오버라이드.

이 기능을 구현하는 클래스들은 UI의 제어에 Factories 가 생성.

TaxiUI 만 변경 필요.

따라서 야옹이 기능은 결합이 분리되고 독립적으로 개발 배포 가능.

 

컴포넌트 기반 서비스

SOLID 원칙대로 서비스를 설계가고 컴포넌트 구조를 갖출 수 있음.

서비스는 jar파일에 포함되는 추상클래스들의 집합.

새로운 jar 파일을 구성하는 클래슫르은 기존 jar 파일에 정의된 추상 클래스들을 확장해서 만듬.

새로운 기능배포는 서비스 로드 경로에 새로운 jar 파일 추가하는 것.

 

각 서비스 내부 각자 방식으로  컴포넌트 설계, 파생클래스 만들어 신규기능 추가 

새로운 기능을 추가하는 행위가 개방 폐쇄 원칙을 준수.

파생 클래스들은 각자의 컴포넌트 내부에 놓임.

 

횡단 관심사

아키텍처 경계는 서비스 사이에 있지 않고 

서비스를 관통하며, 서비스를 컴포넌트 단위로 분할.

 

횡단 관심사를 처리하려면 서비스 내부는 의존성 규칙도 준수하는 컴포넌트 아키텍처로 설계

 

서비스 내부 컴포넌트 아키테처 설계

 

결론

 

시스템 아키텍처는 시스템 내부에 그어진 경계와 경계를 넘나드는 의존성에 의해 정의.

서비스는 하나의 아키텍처 경계로 둘러싸인 단인 컴포넌트로도,

여러 아키텍처 경계로 분리된 다수의 멐포넌트로도 구성 가능.

 

 

 

 

'IT Book Summary > Clean Architecture' 카테고리의 다른 글

06 세부사항 -1  (0) 2021.04.03
05 아키텍처 - 6  (0) 2021.03.28
05 아키텍처 - 4  (0) 2021.03.20
05 아키텍처 - 3  (0) 2021.03.15
05 아키텍처 - 2  (0) 2021.03.08