로버트 C. 마틴의 클린 아키텍처: 소프트웨어 구조와 설계의 원칙을 읽으며 기록하고 싶은 부분만 발췌해 각색하여 작성한 글입니다. 하여 실제 글쓴이의 의도와 다르게 작성될 수 있음을 알립니다.
자바 프로그램은 가변 변수 mutable variable를 사용하는데, 가변 변수는 프로그램 실행 중에 상태가 변할 수 있다. 앞의 예제에서 반복문을 제어하는 변수인 i가 가변 변수다. 클로저 프로그램에서는 이러한 가변 변수가 전혀 없다. 클로저에서는 x와 같은 변수가 한 번 초기화되면 절대로 변하지 않는다. 이는 우리에게 놀라운 사실을 알려준다. 함수형 언어에서 변수는 변경되지 않는다.
# 불변성과 아키텍처
아키테거를 고려할 때 이러한 내용이 왜 중요한가? 아키텍트는 왜 변수의 가변성을 염려하는가? 경합 race 조건, 교착상태 deadlock 조건, 동시 업데이트 concurrent update 문제가 모두 가변 변수로 인해 발생하기 때문이다. 만약 어떠한 변수도 갱신되지 않는다면 경합 조건이나 동시 업데이트 문제가 일어나지 않는다. lock이 가변적이지 않다면 교착상태도 일어나지 않는다. 다시 말해 우리가 동시성 애플리케이션에서 마주치는 모든 문제, 즉 다수의 스레드와 프로세스를 사용하는 애플리케이션에서 마주치는 모든 문제는 가변 변수가 없다면 절대로 생기지 않는다.
아키텍트라면 동시성 concurrency 문제에 지대한 관심을 가져야만 한다. 우리는 스레드와 프로세스가 여러 개인 상황에서도 설계한 시스템이 여전히 강건하기를 바란다. 그렇다면 이제 불변성이 정말로 실현 가능할까? 가능하다. 단, 저장 공간이 무한하고 프로세서의 속도가 무한히 빠르다고 전제한다면 말이다. 그래, 불변성은 실현 간으하겠지만 일종의 타협을 해야한다. 어떤 타협이 필요한지 살펴보자.
# 가변성의 분리
불변성과 관련하여 가장 주요한 타협 중 하나는 애플리케이션, 또는 그 내부의 서비스를 가변 컴포넌트와 불변 컴포넌트로 분리하는 일이다. 불변 컴포넌트에서는 순수하게 함수형 방식으로만 작업이 처리되며, 어떤 가변 변수도 사용되지 않는다. 불변 컴포넌트는 변수의 상태를 변경할 수 있는, 즉 순수 함수형 컴포넌트가 아닌 하나 이상의 다른 컴포넌트와 서로 통신한다.
상태 변경은 컴포넌트를 갖가지 동시성에 노출하는 꼴이므로, 흔히 트랜잭션 메모리 transactional memory와 같은 실천법을 사용하여 동시 업데이트와 경합 조건 문제로부터 가변 변수를 보호한다.
애플리케이션을 제대로 구조화하려면 변수를 변경하는 컴포넌트와 변경하지 않는 컴포넌트를 분리해야 한다는 것이다. 그리고 이렇게 분리하려면 가변 변수들을 보호하는 적절한 수단을 동원해 뒷받침해야 한다. 현명한 아키텍트라면 가능한 한 많은 처리를 불변 컴포넌트로 옮겨야 하고, 가변 컴포넌트에서는 가능한 한 많은 코드를 빼내야 한다.
# 이벤트 소싱
이벤트 소싱은 상태가 아닌 트랜잭션을 저장하자는 전략이다. 상태가 필요해지면 단순히 상태의 시작점부터 모든 트랜잭션을 처리한다. 시간이 지날수록 트랜잭션 수는 끝없이 증가하고, 필요한 컴퓨팅 자원은 걷잡을 수 없이 커진다. 아마 저장 공간이 많이 필요할 것이다. 실제로 오프라인 데이터 저장소는 급격하게 증가하여 이제는 수 테라바이트도 작다고 여기는 시대다. 따라서 우리는 저장 공간을 충분히 확보할 수 있다.
더 중요한 점은 데이터 저장소에서 삭제되거나 변경되는 것이 하나도 없다는 사실이다. 결과적으로 애플리케이션은 CRUD가 아니라 그저 CR만 수행한다. 또한 데이터 저장소에서 변경과 삭제가 전혀 발생하지 않으므로 동시 업데이트 문제 또한 일어나지 않는다.
저장 공간과 처리 능력이 충분하면 애플리케이션이 완전한 불변성을 갖도록 만들 수 있고, 따라서 완전한 함수형으로 만들 수 있다. 이 이야기가 여전히 터무니없게 들린다면, 소스 코드 버전 관리 시스템이 정확히 이 방식으로 동작한다는 사실을 떠올려 보면 도움이 될 것이다.