4 분 소요

References


📌 무엇이 문제인가?

  • 어떤 상황에 주어졌을 때 누구의 문제인지, 어디가 문제인지, 어떤 것이 문제인지, 문제의 원인이 무엇인지 파악해야 한다
  • 무엇이 문제인지, 왜 문제인지, 누구의 문제인지
  • 문제를 다양한 시각에서 바라봐야 한다

📌 도메인(domain)

프로젝트를 진행할 때 문제(Problem) 영역과 솔루션(Solution) 영역 크게 두 가지가 존재한다. 문제 영역은 일반적으로 도메인, 핵심 도메인과 같은 용어로 표현하는데 이들은 소프트웨어가 실제로 해결할 문제, 즉 그 목적을 나타낸다. 솔루션 영역은 비즈니스 로직, 도메인 지식, 비즈니스 규칙과 같은 용어로 표현하는데, 이들은 주어진 문제인 도메인에 대한 솔루션을 나타낸다.

위 두 영역을 나누는 기준은 서브도메인(Subdomain)과 바운디드 컨텍스트(Bounded Context)의 차이와 일치한다. 서브도메인은 문제(도메인) 그 자체를 의미하며 다른 서브도메인과 독립적으로 쉽게 격리될 수 있는 부분을 뜻한다. 반면 바운디드 컨텍스트는 그 문제(도메인)에 대한 솔루션을 뜻한다. 즉, 어떤 코드를 통해 그 문제를 해결하는 것이다.

정리하자면 다음과 같다.

  • 도메인은 소프트웨어로 해결하고자 하는

    문제 영역(problem space)

    • 즉, 도메인은 소프트웨어가 존재하는 이유이자 목적이다
  • 바운디드 컨텍스트는 문제 해결에 필요한 결정들을 조율하는 솔루션 영역(solution space)

  • 소프트웨어를 사용하는 사용자의 활동, 관심사와 관련된 내용들

소프트웨어는 사람의 욕망과 욕구를 해결하려고 만든 창조물이다. 사람들의 욕망과 욕구가 개발자에게 전달됐을 때 그것을 도메인이라고 부른다. - 조영호 -

도메인 모델(model)

  • 모델은 특정 목적을 위해 현실 세계에 존재하는 것을 가공하고 편집해 정보를 제공
  • 특정 다이어그램이 아니라 다이어그램으로 전달하려는 아이디어이자 목적을 가진 의사소통 수단
  • 행위와 데이터를 통합하는 도메인의 객체 모델
  • 도메인 모델이란 특정 문제와 관련된 모든 주제의 개념 모델이다. 도메인 모델은 다양한 엔티티, 엔티티의 속성, 역할, 관계, 제약을 기술한다. 문제에 대한 솔루션을 기술하지 않는다.

바운디드 컨텍스트

  • 해결 영역(solution space)
  • 관심사를 분리하고 격리해 문제 해결에 집중할 범위를 정한다
  • 비즈니스적으로 중요한 결정은 도메인에게 위임하고, 필요한 기능을 제공하기 위해 필요한 나머지 결정들을 조율한다


📌 도메인 로직

하지만 도메인 로직만으로는 프로젝트를 완성할 수 없다. 애플리케이션은 다양한 측면에서 작업되며, 그 중 일부는 도메인 모델을 구축하는 데 전혀 관련이 없을 수 있다. 예를 들어 도메인 모델을 DB에 저장하기 위해 연결하고, 외부 서비스와 작업하며 클라이언트와 상호작용하는 데 상당한 시간이 들어갈 수 있다.

또한 종종 트랜잭션 스크립트 패턴을 사용하여 코드를 구성하면 한 프로시저에 코드가 분리되지 않은 상태로 들어가 어떤 유형의 로직인지 구분하기 어려운 상황도 발생한다.

애플리케이션이 성장할 수록 도메인 로직과 다른 유형의 로직을 명확하게 구분하는게 매우 중요하다.

도메인 모델을 추출하고 코드에서 명시적으로 구분하는 것은 관심사를 명확히 분리해 도메인 이해를 다른 세부 사항에 신경 쓰지 않고 집중할 수 있게 만든다

그렇다면 어떤 로직이 도메인 모델에 속해야 하는지, 바운디드 컨텍스트(애플리케이션 레이어의 일부)인지 어떻게 결정할까?

이를 위해서는 코드가 비즈니스적 의미가 있는 결정을 내리는지 살펴봐야 한다. 이것이 도메인 로직을 구분하는 중요한 기준이다. 도메인 모델은 비즈니스적으로 매우 중요한 결정을 생성하는 것으로 다른 작업(UI, DB 연결, 통신 등)과 연관된 바운디드 컨텍스트와 철저히 분리해야 한다.

도메인 로직: 관심사의 분리

아래 코드로 도메인 로직과 바운디드 컨텍스트를 한번 구분해 보자

private void TakeMoney(decimal amount) {

    string error = atm.canTakeMoney(amount);

    if (error != string.Empty) {
        NotifyClient(error);
        return ;
    }
 
    decimal amountWithCommission = atm.caluculateAmountWithCommission(amount);
    paymentGateway.chargePayment(amountWithCommission);
    atm.takeMoney(amount);
    repository.save(_atm);
 
    NotifyClient("You have taken " + amount.ToString("C2"));
}
  • canTakeMoney(), caluculateAmountWithCommission()
    • ATM 도메인 클래스가 위 두개의 메소드를 제공
    • 위 두개는 도메인 로직이다
  • takeMoeny() 메소드는 애플리케이션이 제공할 기능에 필요한 결정들을 조율한다
    • 여기 안에 canTakeMoney(), caluculateAmountWithCommission() 를 제외한 나머지 코드들은 기능 제공을 위한 흐름을 담당하는 로직들이다. 즉, 도메인 로직이 아니다.

즉, 비즈니스적으로 중요한 결정은 ATM 도메인이 내리고 제공할 기능을 위한 나머지 결정들은 바운디드 컨텍스트에서 내린다.

이 코드는 스스로 중요한 비즈니스 결정을 내리지 않고 도메인 모델에게 결정을 위임한다. 이것이 관심사를 적절하게 분리하는 방법이다. 애플리케이션 레이어에는 상당한 양의 코드가 포함될 수 있지만 그 중 어느 것도 비즈니스 크리티컬한 결정을 내리는 것이 아니어야 한다. 비즈니스적으로 중요한 결정은 오직 도메인 모델에서만 이루어져야 한다.


📌 관심사의 분리: 설계의 근본 원칙

수많은 소프트웨어 대가들은 설계의 근본은 관심사의 분리(Separation of Concerns)라고 말한다.

하나의 뚱뚱한 코드가 아니라 작게 쪼개어 각자에 맞는 역할을 부여하라는 뜻이다. 그렇다면 관심사의 분리가 주목받는 이유는 뭘까? 시간이 지날수록 소프트웨어는 복잡해지기 때문이다. 우리가 많이 사용하는 카카오톡 서비스도 약 10년 전 메시지만 보낼 수 있던 서비스에서 현재는 엄청나게 많은 기능들을 제공하고 있다. 이런 복잡한 서비스를 만드는 주체는 인간이다.

객체지향의 사실과 오해라는 책에서도 인간은 본능적으로 복잡한 것을 최대한 단순하게 바라보려하는 경향이 있다고 말한다. 그래서 대가들은 복잡한 소프트웨어를 최대한 단순하게 바라보기 위해 관심사의 분리를 선택했다.

관심사를 잘 나눈다는 것은?

그렇다면 어떻게 해야 관심사를 잘 분리할 수 있을까?

소프트웨어 공학에서는 ‘응집도’와 ‘결합도’ 두 가지 요소로 평가한다

  • 응집도(Cohesion): 얼마나 비슷한 역할을 하는 코드끼리 모여있나
  • 결합도(Coupling): 얼마나 다른 역할을 가진 코드에게 영향을 덜 받나

응집도는 끼리끼리의 법칙이다. 비슷한 역할을 가진 것들을 최대한 긁어 모을 수록 응집도는 올라가고 관심사가 잘 분리되었다고 말할 수 있다. 반대로 결합도는 독립성을 뜻한다. 내가 이 일을 해결할 때 외부에서 최대한 도움을 받지 않는 것을 뜻한다.

응집도는 높이기 비교적 쉽지만, 결합도를 낮추는 방법은 어렵다. 그래서 등장한 것이 GOF의 디자인 패턴이다. 결합도를 낮추려는 고민과 노하우가 GOF 디자인 패턴에 모두 녹아 있으니 참고하면 좋다.

관심사의 분리를 나누기 위한 규칙들

관심사 분리를 위한 방법들은 여러 책, 기법들에 잘 녹아져 있다. 대표적인 예시 몇 가지를 살펴본다.

클린 코드: 함수를 최대한 작게 나눈다

  • 코드가 길어진다면 매우 많은 일을하고 있는 거다
  • 20줄, 15줄, 10줄 자신만의 기준을 세워 함수를 분리한다
  • 함수의 분리는 관심사의 분리로 가는 첫 번째 걸음이다

캡슐화: 인터페이스와 구현체를 구분한다

  • 캡슐화는 공과 사를 구분하는 작업이다. 공은 인터페이스, 사는 구현체를 뜻한다
  • 캡슐화를 통해 공적인 부분은 public으로 공개해 해결해 주고, 어떻게 해결하는지 구체적인 방법은 private으로 숨겨야 한다. 즉, 기능만 제공해줘라 어떻게 해결하는지는 알려줄 필요도 없고 알려줘도 안된다(영업 비밀).

단일 책임 원칙(SRP): 변경 이유를 기준으로 나눈다

  • 잘 동작하고 있어도 나중에 뭔가 변경될 수 있을거 같으면 분리해라
  • 예를 들어 자동차 게임에서 4 이상의 랜덤값이 들어오면 1칸 전진을 하는데, 미래에 8 이상의 큰 랜덤값이 들어와 2칸 전진하라는 요구사항이 생길 수도 있을거 같으면 분리하는 거다
  • 이 변경의 이유를 기준으로 객체를 분리한다

MVC 패턴: UI와 비즈니스 로직은 철저히 분리한다

  • 이제 객체의 관점을 넘어 더 큰 설계의 관점에서 바라본다
  • 소프트웨어의 관심사를 ‘UI’와 ‘비즈니스 로직’으로 나누는 것은 너무 효과적이어서 거의 소프트웨어 개발에서는 국룰
  • 정말 자주 바뀌는 UI와 비즈니스 로직을 분리해두는 것은 매우 중요하다

이외에도 정말 다양한 방법들이 있지만 아직은 위 규칙들만 알아도 된다. 위 규칙들을 정확히 지켜낼 수 있을 때 다음글을 참고하면 좋을것 같다.

댓글남기기