본문 바로가기

Book

정책과 수준 그리고 업무 규칙

다음은 클린 아키텍처 19~20 장 정리한 내용이다.

19장 정책과 수준

소프트웨어 시스템이란 정책을 기술한 것이다. 실제로 컴퓨터 프로그램의 핵심부는 이게 전부다. 컴퓨터 프로그램은 각 입력을 출력으로 변환하는 정책을 상세하게 기술한 설명서임.


다시 한번 정리하면, 아키텍처 개발은 재편성된 컴포넌트들을 비순환 방향 그래프로 구성하는 기술을 포함함. 그래프에서 정점은 동일한 수준의 정책을 포함하는 컴포넌트에 해당함. 방향이 있는 간선은 컴포넌트 사이의 의존성을 나타냄. 간선은 다른 수준에 위치한 컴포넌트를 서로 연결함
이러한 의존성은 실제 소스 코드, 컴파일타임의 의존성임. 자바의 경우 import 문에 해당함. 좋은 아키텍처라면 각 컴포넌트를 연결할 때 의존성의 방향이 컴포넌트 수준을 기반으로 연결되도록 만들어야 함. 즉 저수준에서 고수준으로 의존하도록 설계

수준

수준은 엄밀하게 정의하자면 '입력과 출력까지의 거리' 임. 시스템과 출력 모두로부터 멀리 위치할수록 정책의 수준은 높아짐. 입력과 출력을 다루는 정책이라면 시스템에서 최하위 수준에 위치함. 아래는 간단한 암호화 프로그램 예시.

입력 장치에서 문자를 읽어서 테이블을 참조하여 문자를 번역한 후, 번역된 문자를 출력 장치로 기록. 제대로 설계했다면 소스코드 의존성은 점선처럼 표시되어야 함.
번역 컴포넌트는 이 시스템에서 최고 수준의 컴포넌트인데, 입력과 출력으로부터 가장 멀리 떨어져 있기 때문.
주목할 점은 데이터 흐름과 소스 코드 의존성이 항상 같은 방향을 가리키지는 않는다는 사실임. 이게 바로 소프트웨어 아키텍처의 예술임. 소스 코드 의존성은 그 수준에 따라 결합되어야 하며, 데이터 흐름을 기준으로 결합되어서는 안 됨.

//밑에는 잘못된 코드
function encrypt(){
    while(true){
        writeChar(translate(readChar()));
    }
}

아래는 인터페이스를 추가해 오로지 고수준으로만 의존성 방향이 향하게 만듦. 점선으로 표시된 원의 경계 안쪽으로 의존성이 향함. 이렇게 묶인 게 고수준임. 여기서 고수준 암호화 정책과 저수준 입력/출력 정책으로부터 분리시킨 방식에 주목해야 함.

밑에는 예시 코드 (책 내용기반, 다를 수 있음)

// CharReader와 CharWriter의 추상화된 인터페이스 정의
interface CharReader {
    char readChar();
}

interface CharWriter {
    void writeChar(char ch);
}

class ConsoleReader implements CharReader {
    @Override
    public char readChar() {
        try {
            return (char) System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
            return '\0';
        }
    }
}

class ConsoleWriter implements CharWriter {
    @Override
    public void writeChar(char ch) {
        System.out.println("Output character: " + ch);
    }
}
class Encrypt {
    private CharReader reader;
    private CharWriter writer;

    public Encrypt(CharReader reader, CharWriter writer) {
        this.reader = reader;
        this.writer = writer;
    }

    public char encryptChar() {
        char ch = reader.readChar();
        return Character.toUpperCase(ch);
    }

    public void decryptChar(char ch) {
        writer.writeChar(Character.toLowerCase(ch));
    }
}

// 예시 사용
public class Main {
    public static void main(String[] args) {
        CharReader reader = new ConsoleReader();
        CharWriter writer = new ConsoleWriter();
        Encrypt encrypt = new Encrypt(reader, writer);

        char encryptedChar = encrypt.encryptChar();

        encrypt.decryptChar(encryptedChar);
    }
}

여기서는 고수준 정책, 즉 입력과 출력에서부터 멀리 떨어진 정책은 저수준 정책에 비해 덜 빈번하게 변경되고, 보다 중요한 이유로 변경되는 경향이 있다고 함. 그리고 입출력에 가까이한 저수준 정책들은 보다 긴급성을 요하며, 덜 중요한 이유로 변경되는 경향이 있음. 예시로 입출력 장치가 변경될 가능성이 암호화 알고리즘이 변경될 가능성 보다 높아 보인다고 한다(근데 이건 좀 헷갈리는 게, 이런 예시 말고 보통 서비스를 개발하면 사실 서비스 정책이 되게 자주 바뀜, 오히려 고수준이 더 자주 바뀌는 경우도 많다고 생각됨. 아무튼 더 고민해야 할 일)
결론적으로는 계속 반복하지만 고수준 컴포넌트에 저수준 컴포넌트가 플러그인 되어야 함. 고수준은 전혀 몰라야 함.

20장 업무 규칙

업무 규칙이란 컴퓨터상으로 구현했는지와 상관없이, 사업적으로 수익을 얻거나 비용을 줄일 수 있어야 함. 심지어 사람이 수동으로 직접 수행하더라도 마찬가지임.
예를 들어 대출에 N%의 이자를 부과한다는 사실은 은행이 돈을 버는 업무 규칙임.
이러한 규칙을 핵심 업무 규칙이라 할 거임. 그리고 이러한 핵심 업무 규칙은 보통 데이터를 요구하는데, 이러한 데이터를 핵심 업무 데이터라고 함.

엔티티

엔티티는 컴퓨터 시스템 내부의 객체로서, 핵심 업무 데이터를 기반으로 동작하는 일련의 핵심 업무를 구체화함. 엔티티 객체는 핵심 업무 데이터를 직접 포함하거나 핵심 업무 데이터에 매우 쉽게 접근할 수 있음. 엔티티의 인터페이스는 핵심 업무 데이터를 기반으로 동작하는 핵심 업무 규칙을 구현한 함수들로 구성됨.
이러한 클래스를 만들 때 업무의 대표자로서 독립적으로 존재하게 구현하고 나머지 데이터베이스, 사용자 인터페이스, 서드파티 프레임워크등에 대한 고려사항들로 오염되어서는 안 됨. 어떤 시스템에서도 업무 수행을 해야 하고, 시스템의 표현방식이나 데이터 저장 방식 그 어떤 것과도 무관해야 함. 순수 업무 그 자체임.
엔티티는 꼭 클래스일 필요도 없고 아무튼 핵심 업무 데이터와 핵심 업무 규칙을 하나로 묶어서 별도 소프트웨어 모듈로 만들어야 함.

유스케이스

모든 업무 규칙이 엔티티처럼 순수한 것은 아님, 자동화된 시스템이 동작하는 방법을 정의하고 제약함으로써 수익을 얻거나 비용을 줄이는 업무 규칙도 존재함. 이러한 규칙은 자동화된 시스템의 요소로 존재해야만 의미가 있으므로 수동 환경에서는 사용될 수 없음.
유스케이스는

자동화된 시스템이 사용되는 방법을 설명함.

유스케이스는 사용자가 제공해야 하는 입력, 사용자에게 보여줄 출력, 그리고 해당 출력을 생성하기 위한 처리 단계를 기술함.

엔티티 내의 핵심 업무 규칙과는 반대로, 유스케이스는 애플리케이션에 특화된 업무 규칙을 설명함.


유스케이스는 엔티티 내부의 핵심 업무 규칙을

어떻게, 그리고 언제 호출할지를 명시하는 규칙을 담는다. 엔티티가 어떻게 춤을 출지를 유스케이스가 제어하는 것임.


주목할 사실은 인터페이스로 들어오는 데이터와 인터페이스에서 되돌려주는 데이터를 형식 없이 명시한다는 점만 빼면,

유스케이스는 사용자 인터페이스를 기술하지 않는다는 점임. 유스케이스만 봐서는 이 애플리케이션이 웹을 통해 전달되는지, 리치 클라이언트인지, 콘솔 기반인지 구분하기란 불가능함.
매우 중요한 점임, 유스케이스는 시스템이 사용자에게 어떻게 보이는지를 설명하지 않음. 단지 애플리케이션에 특화된 규칙을 설명하며, 사용자와 엔티티 사이의 상호작용을 규정함.


엔티티는 자신을 제어하는 유스케이스에 대해 아무것도 모름. 엔티티가 고수준이고 유스케이스가 저수준인 이유는 유스케이스는 단일 애플리케이션에 특화되어 있으며, 시스템 입력과 출력에 보다 가까움. 엔티티는 수많은 애플리케이션에서 업무에 대해서 사용할 수 있는 일반화된 개념임. 그래서 시스템 입력에 보다 멀리 있음.

요청 및 응답 모델

유스케이스는 들어오는 데이터가 어떤 컴포넌트에서 왔고 방식에 대해서 알아선 안 됨. 단지 입력을 받고 응답을 해야 함. 웹에 대해서도 알 필요 없음, 그니까 어떤 인터페이스에 종속되면 안 됨. 엔티티와 응답/요청 모델은 반드시 분리되어야 함. 두 객체를 함께 묶게 되면 SRP, CCP 원칙을 위배하게 됨. 그렇게 되면 수많은 떠돌이 데이터가 만들어지고 조건문이 추가될 것임.

결론

업무 규칙은 소프트웨어 시스템이 존재하는 이유임. UI, DB와 같은 저수준 관심사에 오염되어서는 안됨

 

참고 및 출처: <클린 아키텍처> (로버트 C.마틴지음, 송준이옮김, 인사이트 , 2019)

 

'Book' 카테고리의 다른 글

계약에 의한 설계  (0) 2024.02.04
부분적 경계  (1) 2024.02.02
컴포넌트 응집도, 결합  (1) 2023.12.31
프로그래밍 패러다임  (0) 2023.12.22
계층형 설계 1,2  (2) 2023.12.04