책 소개
소스 코드 파일은 여기에서 내려 받으실 수 있습니다.
https://github.com/AcornPublishing/data-oriented-programming
요약
데이터 지향 프로그래밍은 데이터를 데이터로 다룬다는 간단한 발상에 기반해 단순하면서도 최신의 해결책을 알려주는 책이다. 이해하기 쉽도록 이야기 식으로 전개되는 이 책은 우리가 그동안 지나치게 복잡한 기술을 사용해서 데이터 중심 애플리케이션을 만들고 있었고, 그 때문에 불필요한 복잡성에 빠져 있었다고 일깨워준다. 그리고 네 가지 데이터 지향 프로그래밍 원리로 단순하게 문제를 푸는 방법을 설명한다. 책 서두에 제시된 원리를 대규모 시스템이나 팀 개발에 적용하는 현실적인 도전에 대한 의외의 답을 접하면서 사고의 틀이 확장됨을 경험할 수 있을 것이다.
추천의 글
프로그래밍 원리, 설계 방법, 아키텍처 방식, 심지어 언어의 기능조차 모두 (변화에) 적응하면서도 복잡성을 조직화하는 일과 관련돼 있다. 나는 2009년에 불변 데이터(immutable data)와 프로그램의 일부를 프로그램 자체 내부 데이터로 전환하는 두 가지 특성 때문에 클로저(Clojure)에 이끌렸고, 같은 이유로 최근에는 예호나탄 샤르빗이 쓴 이 책에 이끌렸다.
2005년에는 좋아하는 사람들과 함께 좋아하는 프로젝트 중 하나를 수행했다. 그 프로젝트는 자바 프로젝트였는데, 당시 자바 세계에서 일반적이지 않은 두 가지를 시도했다. 먼저, 핵심 데이터값을 불변으로 만들었다. 쉽지 않았지만, 효과가 아주 좋았다. 많은 클래스의 clone과 deepClone 메서드를 직접 작성했는데, 그로 인한 이득은 대단했다. 한 가지 예로, 사용자마다 개별화할 템플릿 문서가 필요하다고 가정하자. 전체 객체 트리의 복사본을 만들 수 있다면 객체 스스로는 자신이 템플릿 데이터인지 개별화된 데이터인지 알 필요가 없다. 결정은 그 참조를 가진 객체에 달렸다. 또 다른 큰 이점은 비교할 때다. 값이 불변이므로, 식별자가 동일하다는 것은 곧 값이 동등하다는 것을 의미한다. 따라서 동등성 확인을 매우 빠르게 할 수 있다.
두 번째 기법은 (예호나탄이 이 책에서 보인 정도까지는 아니지만) 범용 데이터(generic data)를 활용하는 것이었다. 어떤 계층에 클래스 계층 구조가 있다고 할 때, 인접한 계층은 이 클래스를 좀 더 일반적인 클래스의 인스턴스로 표현한다. 한 계층의 멤버 변수는 다른 계층에서 맵에 담긴 필드로 서술될 수 있다. 이런 방식은 분명히 팀에서 몇몇 사람이 잡담처럼 가볍게 제안한 의견에 영향을 받은 것으로 보인다. 이 또한 바로 이득이 됐다. 다양한 구성으로 객체를 조합하고 재조합할 수 있었기 때문이다.
이와 같이 데이터 지향 프로그래밍(DOP, Data-Oriented Programming)은 우발적 복잡도(accidental complexity)를 줄이고 작업하는 추상화의 수준을 높일 수 있다. 클래스에 범용 함수(generic function)를 만들어 넣은 결과, 프로그램의 반복되는 동작이 자연스럽지 않아 보일 것이다. 범용 함수가 있는 클래스는 프로그램 내부 값의 특정 부분 집합을 전담하는 작은 이름 공간(namespace) 같은 역할을 한다. 프로그램의 거의 모든 값은 맵과 리스트로 접어 넣을 수 있다. 객체 멤버의 이름(리플렉션 API를 사용해야 겨우 얻을 수 있는 데이터)은 맵의 키로 바꿀 수 있다. 이렇게 하면 코드가 조금씩 사라지는데, 이것이 깨달음의 첫 단계다.
이쯤 되면, 컴파일러가 컴파일하면서 멤버 이름을 사용해 코드에 오류가 없는지 확인하지 않느냐며 반대하는 사람이 있을 수 있다. 실제로 그렇다. 하지만 예호나탄이 컴파일 시점의 확인은 값에 오류가 없는지 확인하는 방법의 작은 하위 집합일 뿐이라는, 깨달음의 다음 단계로 우리를 안내할 것이니 믿어보자. 오류 확인 자체도 데이터로 만들 수 있다. 스키마를 프로그램 내부 값으로 만들 수 있을 뿐 아니라, 타입 시스템(type system)의 최전선에 있는 연구자들이 여전히 알아내려고 노력 중인 규칙도 적용할 수 있다. 이것이 두 번째 단계의 깨달음이다.
데이터 지향 프로그래밍은 특히 웹 API로 일할 때 빛난다. 통신에는 타입 시스템이 없으므로 요청에 포함된 데이터를 도메인 클래스에 그대로 매핑하려고 하면 구현이 복잡해질 뿐 아니라 쉽게 깨진다. 데이터를 데이터로 남겨두면 코드가 더 단순해지고 수백 메가바이트 크기의 프레임워크 라이브러리를 사용할 필요가 대폭 줄어든다.
그렇다면 캡슐화, 상속, 다형성이라는 객체지향 프로그래밍(OOP, Object-Oriented Programming)의 미덕은 이제 쓸모없는 것일까? 이 세 가지를 나눠 마치 ‘일품요리’처럼 각각 따로 맛볼 수 있다는 사실이 밝혀졌다(내 생각에 구현 상속은 이 중에서 가장 중요하지 않다. 종종 가장 먼저 배운 것임에도 그렇다. 나는 이제 프로토콜과 공유된 함수 서명을 통한 인터페이스 상속을 선호한다). 데이터 지향 프로그래밍은 전통적인 유형의 다형성을 제공한다. 첫 번째 인수의 자료형에 따라 여러 함수 중 하나로 디스패치(dispatch)하는 것이다(객체지향 언어에서 this는 암묵적인 메서드의 첫 번째 인자다. 생략된 첫 인자가 점(.) 앞에 올 뿐이다). 하지만 데이터 지향 프로그래밍은 스키마 확인처럼 더 많은 역동성을 허용한다. 처음 두 인자의 자료형에 따라 디스패치한다고 생각해보자. 또는 인자에 오늘 날짜가 들어가 있는 ‘생일’ 필드가 있는지에 따라 디스패치된다고 상상해보자. 이것이 깨달음의 세 번째 단계다.
캡슐화는 여전히 프로그램을 조직하는 논리에 적용해야 한다. 우리는 값이 아닌 하위 시스템을 캡슐화한다. 이 캡슐화는 데이비드 파나스(David Parnas)의 설계 결정 은닉(design decision hiding)을 구현한다. 하위 시스템 내부에서는 클래스가 강요하는, 단절된 이름 공간으로 데이터를 나누는 것을 그만둘 수 있다. 앨런 펄리스(Alan Perlis)는 “자료구조 열 개에 함수 열 개가 동작하는 것보다 자료구조 하나에 함수 100개가 동작하는 것이 더 낫다”고 말했다.
엔트로피와의 끝없는 전투 중에는 데이터 지향 프로그래밍으로 코드양을 줄여 싸움을 계속할 수 있고 추상화 수준을 높여 프로그램의 논리와 의미를 정확하고 명료하게 만들 수 있다. 데이터 지향 프로그래밍으로의 여정을 즐기고, 새로운 고원에 도착할 때면 잠시 멈춰 전망을 즐기면서 스스로에게 “데이터일 뿐이야!”라고 말하자.
이 책을 만난 시점은 정말 적절했다. 나는 거의 20년간 객체지향 프레임워크로 웹 앱을 구축했다. 스스로를 프로그래밍 전문가라고 생각하지는 않았지만, 전형적인 비즈니스 문제를 파악하고 데이터 모델을 구상하고 MVC 방식의 앱을 구축할 정도로는 내가 쓰는 도구에 대해 충분히 알고 있었다.
프로젝트의 시작은 늘 흥분됐다. 나는 구성 요소를 서로 연결하고 앱이 생명을 얻는 것을 보는 느낌을 정말 좋아한다. 하지만 일단 앱이 돌아가기 시작하면 문제에 빠지고 말았는데, 앱의 일부만 변경하고 싶어도 모델을 전부 검토해야만 했다. 테스트를 작성해야 한다는 것은 알았지만, 테스트하려면 너무 많은 상태를 설정해야만 했으므로 테스트가 그만한 가치가 있다고 느끼지 못했다. 변경하기 어려운 코드를 더 작성하고 싶지는 않았다. 콘솔에서 간단한 코드를 실행해보려고 해도 번거로웠다. 메서드를 호출하려면 데이터베이스 상태를 만들어야 했기 때문이다. 내가 잘못하고 있다고 생각했지만, 정밀한 테스트 프레임워크와 같이 익숙한 기존 해법은 일을 처리하기 쉽게 만들기보다는 오히려 복잡도를 더하는 것 같았다.
그러던 어느 날, 유튜브에서 클로저의 창시자인 리치 히키(Rich Hickey)의 발표를 봤다. 그는 함수형 프로그래밍(FP, Functional Programming)을 설명하면서 이를 객체지향과 비교했는데, 객체지향을 ‘장소지향 프로그래밍(place-oriented programming)’이라고 부르면서 비꼬았다. 그의 주장이 맞는지는 확신할 수 없었지만, “당신이 아니라 당신의 언어가 문제입니다”라고 말하는 듯한 이면의 메시지에 흥미를 느꼈다. 결국 해당 주제와 관련해 찾을 수 있는 비디오는 모두 찾아봤고, 클로저가 답이 될 수도 있겠다는 생각을 하기 시작했다.
수년이 흘렀다. 나는 계속 클로저 영상을 보면서 함수형 원리를 가능한 한 적용하려고 시도해왔지만, 새로운 프로젝트를 시작할 때마다 손에 익은 프레임워크로 되돌아갔다. 완전히 다른 라이브러리 생태계를 가진 또 다른 언어로 바꾼다는 것은 너무나 큰 도약이었다.
그러다가 새로운 제품 개발을 막 시작하려던 시점에 이 책을 알게 됐다. 무엇보다 책 제목의 ‘데이터 지향(Data-Oriented)’이라는 단어가 마음을 울렸다. 예전에 봤던 클로저 비디오에서 프로그래머들이 이 말을 하는 것을 듣기는 했지만, 무슨 뜻으로 하는 말인지 명확히 이해하지는 못했다. 일일이 전용 객체를 만들기보다 맵이나 배열 같은 데이터 리터럴(data literal)을 사용해 시스템을 구축하는 것이 더 쉽다는 내용이었다. 내가 아는 언어는 데이터 리터럴을 훌륭하게 지원했으므로, 언젠가 클로저로 전환하는 대망의 날까지 나를 지탱해줄 만한 뭔가를 배워야 한다고 생각했다.
첫 깨달음의 순간은 도입 부분을 읽자마자 바로 다가왔다. 책의 서두에서 예호나탄은 자신이 클로저로 10년간 코드를 작성했음에도 이 책은 언어 중립적이고 예제는 자바스크립트로 제공될 것이라고 설명했다. 언어를 바꾸지 않고도 내가 프로그램을 작성하는 방식을 획기적으로 개선할 수 있다고 하니 믿을 수 없었다.
이 가능성에 너무 들떠 그 자리에서 책을 단숨에 다 읽어버렸는데, 그동안 보이지 않던 것이 보이기 시작했다. 내 코드는 테스트하기 어려웠다. 내가 ORM을 사용했고, 모든 기능이 데이터베이스 상태를 가정한 객체로 작성됐기 때문이다. 책에서 예제와 함께 설명된 내용을 도저히 무시할 수 없었다. 내게 필요한 것은 새로운 언어가 아니었다. 다른 프로그래밍 방식이 필요할 뿐이었다.
내가 대단하다고 생각하는 설계자들은 하나같이 좋은 설계란 분리해내는 것이라고 강조한다. 코드를 동작하게 만드는 일이 아니다. 엉겨 붙은 덩어리를 서로 다른 부분으로 떼어내고 전체에 영향을 주지 않고도 일부만 변경할 수 있게 해야 된다는 이야기다.
이 책에서는 코드와 데이터를 분리하며 그 결과는 놀랍고 흥미롭다. 더 나아가 특정 언어와 프로그래밍 방식을 분리하는 것으로 보인다. 나는 이제 클로저로 전환하지 않을 것 같고 더는 그래야 할 것처럼 느끼지도 않는다. 데이터 지향 프로그래밍은 내가 사용하는 언어의 새로운 가능성과 매일 등장하는 수많은 새 프레임워크를 보게 해줬다.
한국어판 추천의 글
소프트웨어는 물질적인 실체가 없습니다. 때문에 코드만 바꾸면 무궁무진하게 다양한 일을 해낼 수 있으므로 ‘손쉽게’ 변경 가능한 무른 무엇이라고 생각하기 쉽습니다. 하지만 실제 소프트웨어는 엄청나게 복잡하고 그로 인해 경화되곤 합니다. 클래스 계층 구조와 함수 흐름이나 데이터 간의 의존 관계, 내재된 시간적 선후 관계 등이 복잡하게 뒤얽히면서 고객의 수정 요청을 코드에 적용하기 어려운 경우가 아주 많습니다. 따라서 개발 초기부터 이런 본질적인 소프트웨어의 복잡도를 낮춰서 최대한 말랑말랑하게 유지하기 위한 다양한 방법이 존재합니다.
이 책은 이런 문제를 해결하는 방법으로 데이터 지향 프로그래밍(DOP)을 소개합니다. DOP를 소개할 뿐 아니라 DOP라는 개념을 처음 접하면 질문을 던질 법한 내용들인 상태 관리, 동시성 제어, 테스트, 데이터 유효성 관리, 영속화, 디버깅 등에 대해서도 실제 예제를 통해 차근차근 설명합니다. 이를 통해 DOP의 핵심을 쉽게 이해할 수 있고 우리 프로젝트에도 차근차근 적용할 수 있습니다. 데이터와 코드의 분리, 순수하고 불변적인 데이터 구조, 상태가 없는 함수, 동적 디스패치 등과 같은 DOP의 개념은 하나의 시스템에서 돌아가는 앱 뿐만 아니라 비동기적으로 여러 서버가 협력해 작동하는 분산 환경에서도 유용한 방법론이라고 생각합니다.
무엇보다도 부드럽고 자연스러운 번역 덕분에 이 책을 더 쉽게 이해할 수 있습니다. 여러 개발자에게 도움이 될 만한 좋은 책이 번역된 것이 무척 기쁩니다. 많은 분이 이 책을 읽고 좀 더 나은 개발자로 발전해 나갔으면 좋겠습니다.
‘자바 백엔드 개발은 웹 브라우저와 DB를 연결하는 인터페이스를 만드는 것에 불과하다’라는 자조적인 선언이 개발자 사이에 돌았던 기억이 있습니다. 비즈니스 로직은 DB가 담당하는 SQL과 저장 프로시저에 담겨 있고, 백엔드 코드는 웹 요청을 SQL로 만들어 DB에 전달하거나 DB에서 돌려받은 정보를 HTML, JSON, XML 등으로 변환하는 것이 전부라는 것이었죠. 결국 중요한 것은 데이터이고 애플리케이션은 데이터보다 수명이 짧으니 애플리케이션은 그저 DB 인터페이스로 만들면 충분하다는 이야기였습니다.
『데이터 지향 프로그래밍』이라는 제목을 봤을 때, 혹시 그때 들었던 이야기가 다시 반복되는 건 아닌가 하는 의심이 먼저 들었습니다. 하지만 책에서 설명하는 데이터를 지향하는 프로그래밍 방식은 전혀 달랐습니다.
저자는 행위라고 설명하는 애플리케이션 코드를 이용해 데이터를 다루는 놀랍도록 다양한 전략을 설명합니다. 백엔드 코드는 그저 DB 데이터의 포맷을 바꿔서 전달하는 인터페이스가 아니라, 범용적인 데이터로 표현되는 애플리케이션의 핵심 정보를 효과적으로 다루며 도메인과 애플리케이션 로직을 담을 수 있는 유용한 도구가 될 수 있다는 것을 보여줍니다.
가장 감탄한 것은 데이터를 처리하는 코드가 얼마나 효과적으로 동시성을 다룰 수 있으며 단위 테스트를 비롯한 다양한 테스트를 손쉽게 만들 수 있는지 보여주는 내용입니다. 이렇게 작성된 명료한 코드는 이해하기 쉽고 변경이 유연하며, 객체지향이 추구하고자 했던 것을 크게 포기하지 않으면서도 데이터를 잘 다루는 코드가 가능하겠다는 기대감을 갖게 합니다. 따라서 너무 많은, 비슷한 구조의 DTO 작성과 반복적인 단순 데이터 변환 코드에 지친다는 백엔드 개발자들에게 새로운 개발 전략을 찾기 위한 많은 힌트를 제공해줄 것입니다.
물론 대부분의 예제에서 사용한 자바스크립트나 그와 유사한 유연한 언어가 아니라면 분명 한계가 존재할 것입니다. 자바나 코틀린 개발자라면, 또 JPA와 같은 ORM에 익숙한 데이터 처리 기술을 포기할 수 없다면, 맵으로 표현한 데이터의 사용이 쉽지는 않으리라 생각합니다. 하지만 데이터를 다루는 언어 표현력의 발전에 도움을 받으면 새로운 길이 보이지 않을까 싶기도 합니다.
책에 나오는 대화의 많은 부분이 아마도 책을 읽는 기존 백엔드 개발 방식에 익숙한 개발자의 머리에 떠오를 것입니다. 이에 대해 저자가 어떻게 계속 설득하는지 따라가보면 꽤 많은 인사이트를 얻게 될 것입니다.
이 책에서 다루는 내용
◆ 데이터와 코드의 분리
◆ 일반화 자료형을 사용한 데이터의 표현
◆ 데이터 변경 없는 상태 관리
◆ 대규모 시스템의 동시성 제어
◆ 데이터 지향 단위 테스트 작성
◆ 데이터 규격 지정
이 책의 대상 독자
자바, C#, C++, 루비, 파이썬 같은 고급 프로그래밍 언어를 사용한 경험이 있는 프론트엔드, 백엔드, 풀 스택 개발자를 위한 책이다. 객체지향 프로그래밍 개발자라면 이 책에서 제시하는 몇 가지 발상이 다소 불편할 수 있고 이미 익숙한 프로그래밍 패러다임을 버려야 할 수도 있다. 함수형 프로그래밍 개발자는 이 책을 좀 더 쉽게 소화할 수 있다. 하지만 그들에게도 놀랄 만한 요소는 있을 것이다.
목차
목차
- 1부 유연성
- 1장 객체지향 프로그래밍의 복잡성
- 1.1 OOP 설계: 정통인가? 고전인가?
- 1.1.1 설계 단계
- 1.1.2 UML 기초
- 1.1.3 클래스도 상세 설명
- 1.1.4 구현 단계
- 1.2 복잡성의 근원
- 1.2.1 다량의 클래스 간 관계
- 1.2.2 예상치 못한 코드 동작
- 1.2.3 쉽지 않은 데이터 직렬화
- 1.2.4 복잡한 클래스 계층 구조
- 요약
- 2장 코드와 데이터 분리
- 2.1 DOP 시스템의 두 부분
- 2.2 데이터 개체
- 2.3 코드 모듈
- 2.4 이해하기 쉬운 DOP 시스템
- 2.5 유연한 DOP 시스템
- 요약
- 3장 기본 데이터 조작
- 3.1 데이터 모델 설계
- 3.2 맵으로 관리되는 레코드
- 3.3 범용 함수를 사용한 데이터 조작
- 3.4 검색 결과 연산
- 3.5 이종 자료형의 레코드 처리
- 요약
- 4장 상태 관리
- 4.1 다중 버전 시스템 데이터
- 4.2 구조적 공유
- 4.3 구조적 공유 구현
- 4.4 데이터 안전성
- 4.5 변경의 반영 단계
- 4.6 시스템 상태 무결성 보장
- 4.7 이전 상태 복원
- 요약
- 5장 기본 동시성 제어
- 5.1 낙관적 동시성 제어
- 5.2 동시 변경 조정
- 5.3 컬렉션 축소
- 5.4 구조적 비교
- 5.5 조정 알고리듬 구현
- 요약
- 6장 단위 테스트
- 6.1 간결한 데이터 지향 테스트 케이스
- 6.2 데이터 조작 코드의 단위 테스트
- 6.2.1 함수 호출 트리
- 6.2.2 끝 단 함수의 단위 테스트
- 6.2.3 트리 중간 노드의 단위 테스트
- 6.3 조회 함수의 단위 테스트
- 6.4 변경의 단위 테스트
- 다음 단계
- 요약
- 2부 확장성
- 7장 기본 데이터 유효성 확인
- 7.1 DOP의 데이터 유효성 확인
- 7.2 JSON 스키마 소개
- 7.3 유연하고도 엄격한 스키마
- 7.4 스키마 합성
- 7.5 데이터 유효성 오류 상세 정보
- 요약
- 8장 고급 동시성 제어
- 8.1 복잡한 잠금
- 8.2 스레드 안전한 원자 계수기
- 8.3 스레드 안전한 원자 캐시
- 8.4 원자 기반 상태 관리
- 요약
- 9장 영속 자료구조
- 9.1 영속 자료구조의 필요성
- 9.2 영속 자료구조의 효율성
- 9.3 영속 자료구조 라이브러리
- 9.3.1 자바 영속 자료구조
- 9.3.2 자바스크립트 영속 자료구조
- 9.4 실무 영속 자료구조
- 9.4.1 영속 자료구조를 사용한 조회 코드
- 9.4.2 영속 자료구조를 사용한 변경 코드
- 9.4.3 직렬화와 역직렬화
- 9.4.4 구조적 비교
- 요약
- 10장 데이터베이스 작업
- 10.1 데이터베이스에서 데이터를 가져오는 작업
- 10.2 데이터베이스에 데이터 저장하기
- 10.3 단순한 데이터 조작
- 10.4 복잡한 데이터 조작
- 요약
- 11장 웹 서비스
- 11.1 또 다른 기능 요청
- 11.2 외부와 동일한 내부 구축
- 11.3 맵으로 표현되는 클라이언트 요청
- 11.4 맵으로 표현되는 서버 응답
- 11.5 정보 전달
- 11.6 실제 검색 결과 보강
- 일정 준수
- 요약
- 3부 유지보수성
- 12장 고급 데이터 유효성 확인
- 12.1 함수 인자 유효성 확인
- 12.2 반환값 유효성 확인
- 12.3 고급 데이터 유효성 확인
- 12.4 데이터 모델 도식 자동 생성
- 12.5 스키마 기반 단위 테스트 자동 생성
- 12.6 새로운 선물
- 요약
- 13장 다형성
- 13.1 다형성의 핵심
- 13.2 단일 디스패치 다중 메서드
- 13.3 다중 디스패치 다중 메서드
- 13.4 동적 디스패치 다중 메서드
- 13.5 실운영 시스템에 다중 메서드 통합
- 요약
- 14장 고급 데이터 조작
- 14.1 풍부한 표현의 맵 값 갱신
- 14.2 중첩된 데이터 조작
- 14.3 최적의 도구 사용
- 14.4 배열 필드 해체
- 요약
- 15장 디버그
- 15.1 프로그래밍 결정론
- 15.2 숫자와 문자열을 사용한 재현
- 15.3 모든 데이터 유형에서 재현
- 15.4 단위 테스트
- 15.5 외부 데이터 테스트
- 작별
- 요약
- 부록 A 데이터 지향 프로그래밍 원리
- 부록 B 정적 타입 언어의 범용 데이터 접근
- 부록 C 패러다임의 발전과 데이터 지향 프로그래밍
- 부록 D 로대시 요약