소프트웨어 아키텍처 101

소프트웨어 아키텍처 101, 한빛미디어


이 책에서 컴포넌트의 계약이라는 표현을 처음 접하게 되었습니다.

정확하게 어떤 의미인지 알지 못했지만, 책의 내용을 이해하는데 크게 지장은 없었습니다.

하지만, 혹시 중요한 의미가 내포되어 있는 것은 아닌지 궁금하기도 했습니다. 그래서 어떤 의미인지 찾아보게 되었습니다.

컴포넌트의 계약은 계약에 의한 설계라는 개념에서 확장된 것이라고 합니다.


1. 계약에 의한 설계(Design by Contract)란 무엇인가?

계약에 의한 설계

소프트웨어 구성 요소 간의 관계를 “계약”이라는 개념으로 정의하는 설계 방식입니다.

이 개념은 Bertrand Meyer가 제안했으며, 그가 설계한 Eiffel 언어(처음 들어보는 언어입니다.)에서 언어 차원으로 지원되었다고 합니다.

핵심은 단순합니다. 소프트웨어에서 모든 기능의 호출은 계약 관계입니다.

호출자와 제공자 사이에는 각자가 지켜야 할 책임과 약속이 존재하는 것입니다.


2. 계약의 세 가지 요소

계약은 일반적으로 다음의 세 가지 요소로 구성됩니다.

1) 사전 조건 (Precondition)

기능이 호출되기 전에 반드시 참이어야 하는 조건입니다.

예를 들면 다음과 같습니다.

  • 금액은 0보다 커야 합니다.
  • clientId는 null이 아니어야 합니다.
  • 사용자는 인증된 상태여야 합니다.

이 조건을 만족시키는 것은 호출자의 책임입니다. 사전 조건이 만족되지 않으면, 호출자가 계약을 위반한 것입니다.

2) 사후 조건 (Postcondition)

기능이 정상적으로 종료되었을 때 반드시 보장되어야 하는 조건입니다.

예를 들면 다음과 같습니다.

  • 잔액은 정확히 차감되어야 합니다.
  • 반환 값은 null이 아니어야 합니다.
  • 발급된 토큰은 반드시 서명되어 있어야 합니다.

이 조건은 제공자의 책임입니다. 이 조건이 보장되지 않는다면, 구현이 잘못된 것입니다.

3) 불변 조건 (Invariant)

객체가 생명주기 전체에서 항상 유지해야 하는 조건입니다.

예를 들면 다음과 같습니다.

  • 계좌 잔액은 항상 0 이상입니다.
  • 토큰의 만료 시간은 발급 시간 이후입니다.
  • 도메인 객체는 유효한 상태만 가질 수 있습니다.

불변 조건은 객체의 정체성을 정의하는 기준입니다.


3. 계약의 본질은 책임 분리

계약에 의한 설계는 단순히 방어 코드를 추가하는 기법이 아닙니다. 핵심은 책임을 명확히 구분하는 것입니다.

구분 책임 주체
사전 조건 호출자
사후 조건 제공자
불변 조건 객체 자신

이렇게 구분하면 혹시라도 문제가 발생했을 때, 누구의 책임인지를 비교적 명확하게 판단할 수 있습니다. 대규모 시스템일수록 이러한 구분은 더욱 중요해집니다.


4. 컴포넌트의 계약

앞서 이야기했지만, 아키텍처에서 “컴포넌트의 계약”은 계약에 의한 설계의 개념을 컴포넌트 수준으로 확장한 것입니다.

그래서 컴포넌트는 내부 구현이 아니라, 외부에 보장하는 행동까지 정의되어야 합니다.

계약에 해당되는 사례들은 다음과 같습니다.

  • 어떤 입력을 받으면 어떤 출력을 보장합니다.
  • 실패 시 어떤 오류를 반환합니다.
  • 항상 유지해야 하는 규칙은 무엇입니다.

구현은 변경될 수 있습니다. 그러나 계약은 쉽게 변경되어서는 안 됩니다.

이러한 관점이 계약에 의한 설계 중심의 사고입니다.

참고로 아래의 사례들은 계약이 아닌, 구현 세부사항에 해당됩니다.

  • JPA를 사용합니다.
  • Redis 캐시를 사용합니다.
  • 내부적으로 특정 트랜잭션 전략을 사용합니다.

5. API는 계약

REST API, gRPC 정의, 메시지 스키마 등은 모두 계약에 해당되는 표현들입니다.

예를 들어 토큰 발급 API를 생각해보면 다음과 같습니다.

  • clientId와 clientSecret은 필수입니다. (사전 조건)
  • 성공 시 accessToken을 반환합니다. (사후 조건)
  • 발급된 토큰은 반드시 만료 시간을 포함합니다. (불변 조건)

이것들이 API 스펙이며, 곧 컴포넌트의 계약입니다.


6. Java에서 계약을 표현하는 방법

Java의 경우에는 언어 차원에서 직접 지원하지는 않지만, 다음과 같은 방식으로 계약을 표현할 수 있습니다.

Validation 애노테이션

@Min(1)
private int amount;

명시적인 예외 처리

if (amount <= 0) {
    throw new IllegalArgumentException("amount must be positive");
}

타입과 설계로 표현

  • null을 허용하지 않는 생성자
  • 불변 객체 설계
  • Optional 활용

계약은 코드에 드러나야 합니다. 암묵적인 전제 조건은 계약이라고 보기 어렵습니다.


7. 정리

계약에 의한 설계는 특정 기술이 아니라, 설계를 바라보는 관점에 가깝습니다.

“이 컴포넌트는 무엇을 보장하는가?”

이 질문에 명확히 답할 수 있다면, 그 컴포넌트는 계약으로 정의된 것입니다.

아키텍처에서 말하는 컴포넌트의 계약이란, 구현이 아니라 행동 보장(Behavioral Guarantee)으로 컴포넌트를 정의하자는 의미입니다.

좋은 시스템일수록 구현보다 계약이 더 중요한 기준이 됩니다.

구현은 변경될 수 있지만, 계약은 쉽게 깨져서는 안 됩니다.

계약


8. 포스팅 시리즈


#소프트웨어아키텍처101 #한빛미디어 #소프트웨어 #아키텍처 #계약 #설계