Posts [클린아키텍처] 26장 메인컴포넌트
Post
Cancel

[클린아키텍처] 26장 메인컴포넌트

clean-architecture-book

‘클린 아키텍처’ 기술 서적에 대해 학습했던 내용을 정리하기 위한 목적의 TIL 포스팅입니다.🙆‍♂️

  • 모든 시스템엔 컴포넌트들을 생성, 조정, 관리하는 최소 하나의 컴포넌트가 존재해야 한다.
  • 이를 메인(Main)이라 부른다.

궁극적인 세부사항

  • 메인 컴포넌트는 궁극적인 세부사항으로 가장 낮은 수준의 정책임.

image

  • 시스템 초기 진입점으로서 운영체제를 제외하면 어떤 것도 메인에 의존하지 않는다.
  • 메인은 모든 팩토리(Factory)와 전략(Strategy), 그리고 시스템 전반을 담당하는 나머지 기반 설비를 생성 후, 시스템에서 더 높은 수준을 담당하는 부분으로 제어권을 넘기는 역할을 맡는다.
  • 의존성 주입 프레임워크를 이용해 의존성을 주입하는 일은 바로 이 메인 컴포넌트에서 이뤼져야 한다.
    • 메인에 의존성이 일단 주입되고 나면, 메인은 의존성 주입 프레임워크를 사용하지 않고도 일반적인 방식으로 의존성을 분배할 수 있어야 한다.
  • 메일을 가장 지저분한 컴포넌트라 생각하자.
  • 아래 예제 코드는 최신 움퍼스 사냥 게임(Hunt the Wumpus)의 메인 컴포넌트인데 문자열을 로드하는 방법으로, 코드의 나머지 핵심 영역에서 구체적인 문자열을 알지 못하게 하였음에 주목하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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",
        "tail",
        "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",
        "spttered 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",
    };
  • main 함수에서 HtwFactory를 사용하여 게임을 생성하는 방식을 주목하자.
    • 게임을 생성할 때 htw.game.HunTheWumpusFacase라는 클래스 이름을 전달하는데, 이 클래스는 메인보다도 더 지저분하기 때문이다. 재컴파일/재배포가 되지 않게하기 위함이다.
  • 그리고 main 함수에서 주목할 점이 하나 더 있다.
    • 바로 입력 스트림 생성 부분, 게임의 메인 루프 처리, 간단한 입력 명령어 해석 등은 main 함수에서 모두 처리하지만, 명령어를 실제로 처리하는 일은 다른 고수준 컴포넌트로 위임한다는 사실이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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에서 처리한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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);
    
    // 이하 코드 생략
}
  • 요지는 메인은 클린 아키텍처에서 가장 바깥 원에 위치하는, 지저분한 저수준 모듈이다.
  • 메인은 고수준의 시스템을 위한 모든 것을 로드한 후, 제어권을 고수준의 시스템에게 넘긴다.

결론

  • 메인을 애플리케이션의 플러그인이라 생각하자. 초기 조건과 설정을 구성하고, 외부 자원을 모두 수집한 후 제어권을 애플리케이션의 고수준 정책으로 넘기는 플러그인으로 말이다..
  • 메인은 플러그인이므로 메인 컴포넌트를 애플리케이션의 설정별로 하나씩 두도록 하여 둘 이상의 메인 컴포넌트를 만들수도 있다.
    • 예를 들어, 개발용/테스트용/사용 메인 플러그인처럼 말이다..
    • 또한 배포 대상 국가별, 관할 영역별, 고객별 메인 플러그인을 만들수도 있다.
  • 메인을 플러그인 컴포넌트로 여기고, 그래서 아키텍처 경계 바깥에 위치한다고 보면 설정 관련 문제를 훨씬 쉽게 해결할 수 있다.

Reference

This post is licensed under CC BY 4.0 by the author.

[클린아키텍처] 25장 계층과 경계

[클린아키텍처] 27장 '크고 작은 모든' 서비스들