데이터 지향 프로그래밍 4주차 - 데이터는 어디에서 유효해져야 할까

앞선 모임에서는 데이터를 범용 자료구조로 표현하고, 불변 상태를 안전하게 갱신하며, 데이터 자체를 입출력으로 삼아 테스트하는 방법을 살펴봤습니다. 네 번째 모임의 질문은 그다음 단계로 이어졌습니다. 시스템 밖에서 들어온 데이터를 우리는 언제부터 믿어도 될까요?

이번 주에는 7장의 기본 데이터 검증과 부록 A의 네 가지 원칙을 함께 읽었습니다. 한 발표는 네 원칙을 다시 훑으며 DOP가 잘 맞는 문제와 선택의 비용을 살펴봤고, 다른 발표는 JSON Schema를 이용해 시스템 경계에서 데이터를 검증하는 흐름을 구체적으로 정리했습니다. 발표 사이사이에는 Avro, EAV, PostgreSQL의 JSON 타입처럼 익숙한 기술을 DOP의 관점에서 다시 보는 이야기도 오갔습니다.

유효한 JSON이 곧 유효한 데이터는 아닙니다

문법에 맞는 JSON이라도 애플리케이션이 기대한 데이터라는 보장은 없습니다. 책 검색 요청에 query가 빠질 수 있고, 출판 연도 자리에 문자열이 들어오거나, 허용하지 않은 값이 전달될 수도 있습니다. JSON 파서는 문법 오류는 찾지만 필드의 존재 여부와 타입, 값의 범위처럼 애플리케이션이 정한 계약까지 알지는 못합니다.

이 차이는 범용 자료구조를 사용할 때 더 잘 드러납니다. 클래스의 생성자나 정적 타입에 필드 구조를 고정하지 않은 대신, 들어온 맵이 실제로 필요한 모양을 갖췄는지 별도로 확인해야 합니다. 데이터 표현을 유연하게 만들었다면 유효성 검증이 사라지는 것이 아니라, 검증을 담당할 위치와 장치를 명시적으로 정해야 합니다.

데이터 지향 프로그래밍 4주차 - 데이터는 어디에서 유효해져야 할까
문법을 확인하는 파싱과 데이터 계약을 확인하는 스키마 검증은 서로 다른 단계입니다.

검증은 파싱 뒤, 도메인 바인딩 앞에 둡니다

HTTP 요청을 예로 들면 입력은 먼저 바이트로 도착합니다. 서버는 이를 JSON 노드나 범용 맵으로 파싱합니다. 그다음 해당 요청에 맞는 스키마를 선택해 필드와 타입, 제약 조건을 검사합니다. 검증을 통과한 데이터만 도메인에서 사용하는 요청 값으로 변환하고 비즈니스 로직에 전달합니다.

이 순서를 지키면 잘못된 데이터가 도메인 로직 안쪽까지 흘러간 뒤 엉뚱한 예외를 만드는 일을 줄일 수 있습니다. 실패 이유도 요청이 잘못되었습니다로 뭉뚱그리기보다 어느 경로의 어떤 값이 어떤 조건을 어겼는지 돌려줄 수 있습니다. 검증기는 첫 오류에서 멈추도록 설정할 수도 있고, 사용자가 한 번에 고칠 수 있도록 여러 오류를 모아 반환할 수도 있습니다. 어느 쪽이 적절한지는 요청의 성격과 오류 응답 정책에 따라 달라집니다.

다만 이 흐름이 모든 내부 함수 호출에 같은 검증을 반복하라는 뜻은 아닙니다. 7장은 우선 HTTP 요청, 외부 API 응답, 메시지 큐, 데이터베이스처럼 시스템 안팎이 만나는 경계에 집중합니다. 시스템 내부를 흐르는 데이터의 계약은 뒤 장에서 별도로 다룹니다.

스키마는 데이터의 모양과 조건을 데이터로 표현합니다

JSON Schema는 데이터와 떨어진 문서로 필드의 모양과 제약을 표현합니다. properties에는 각 필드의 스키마를 적고, required에는 반드시 있어야 할 필드명을 나열합니다. 배열의 원소는 items로 설명할 수 있습니다. 이렇게 만든 스키마는 특정 클래스 정의에 갇히지 않으므로 서로 다른 언어와 서비스가 같은 계약을 읽고 검증하는 데 사용할 수 있습니다.

단일 키워드로 표현하기 어려운 조건은 스키마를 조합해 나타냅니다. 예를 들어 ISBN-10과 ISBN-13 가운데 적어도 하나가 있어야 한다면 두 필드를 모두 required에 넣을 수는 없습니다. anyOf는 여러 하위 스키마 가운데 하나 이상을 만족하도록 하고, oneOf는 정확히 하나만 만족하도록 합니다. 두 키워드는 비슷해 보이지만 둘 다 존재해도 되는지에 따라 의미가 달라집니다.

데이터 지향 프로그래밍 4주차 - 데이터는 어디에서 유효해져야 할까 - 이미지 2
required는 개별 필드의 필수 여부를 정하고, anyOfoneOf는 여러 조건의 관계를 표현합니다.

여기서 중요한 책임이 하나 남습니다. 데이터가 자신에게 적용할 스키마를 스스로 아는 것은 아닙니다. 요청의 종류와 버전에 맞는 스키마를 선택하고 검증기에 함께 전달하는 일은 애플리케이션의 몫입니다. 다른 스키마를 연결하거나 검증 호출을 빠뜨리면 스키마 파일이 존재해도 계약은 지켜지지 않습니다. 데이터와 스키마를 분리한다는 원칙은 둘의 관계를 없애는 것이 아니라, 그 관계를 코드에서 분명히 관리하라는 요구에 가깝습니다.

실행 시점의 스키마는 정적 타입을 대체하지 않습니다

클래스로 표현한 데이터는 컴파일러와 IDE가 필드명과 타입 오류를 일찍 알려 줄 수 있습니다. 반면 문자열 키를 사용하는 범용 맵은 필드 추가와 변환이 자유로운 대신, 오타나 잘못된 타입이 실행할 때까지 드러나지 않을 수 있습니다. JSON Schema는 이때 잃은 검사의 일부를 실행 시점에 보완합니다.

데이터 지향 프로그래밍 4주차 - 데이터는 어디에서 유효해져야 할까 - 이미지 3
범용 자료구조와 스키마의 조합은 클래스와 같은 검사를 다른 시점과 방식으로 수행합니다.

그렇다고 실행 시점 스키마가 정적 타입의 완전한 대체물은 아닙니다. 정적 타입은 코드를 작성하고 컴파일하는 동안 피드백을 주지만, 스키마 검증은 실제 데이터와 선택된 스키마를 가지고 실행해야 결과를 알 수 있습니다. 반대로 정적 타입만으로는 네트워크를 통해 들어온 바이트가 계약을 지켰는지 보장할 수 없습니다. 두 장치는 경쟁 관계라기보다 서로 다른 구간의 오류를 맡습니다.

이 구분은 TypeScript 같은 점진적 타입 시스템을 어떻게 볼지에도 여지를 남깁니다. 타입 선언은 개발 중 자동완성과 정적 진단을 제공하지만, 외부에서 들어온 JSON이 그 타입 선언을 실제로 만족하는지는 별도의 실행 시점 검증이 필요합니다. 타입 표기가 있다고 데이터가 자동으로 검증되는 것은 아닙니다.

네 가지 원칙에는 각각 얻는 것과 감당할 비용이 있습니다

부록 A를 따라 DOP의 네 가지 원칙도 다시 살펴봤습니다. 코드를 데이터에서 분리하면 같은 데이터에 새로운 연산을 더하기 쉬워지고, 범용 자료구조는 데이터를 특정 클래스 계층에 고정하지 않습니다. 불변 데이터는 과거 버전을 보존해 상태 변화를 예측하기 쉽게 만들고, 데이터 표현과 스키마의 분리는 같은 데이터를 여러 계약과 도구에서 다룰 여지를 만듭니다.

각 원칙은 편리함만 주지 않습니다. 코드와 데이터를 나누면 연관된 동작을 찾는 방식이 달라지고, 범용 맵은 필드 발견성과 컴파일 시점 검사를 약하게 만듭니다. 불변 갱신에는 새 버전을 만들고 공유하는 자료구조가 필요하며, 분리된 스키마는 올바른 버전을 선택하고 함께 관리해야 합니다. 이번 7장의 검증은 네 번째 원칙의 장점뿐 아니라 두 번째 원칙이 내준 안전성을 어디에서 보완하는지도 보여 줬습니다.

Apache Avro는 이 관계를 다른 형태로 보여 주는 사례였습니다. Avro는 스키마를 바탕으로 데이터를 직렬화하고, 코드 생성을 하지 않아도 GenericRecord 같은 범용 표현을 사용할 수 있습니다. JSON Schema가 JSON 문서의 유효성을 검사하는 언어라면, Avro는 스키마와 함께 효율적인 이진 직렬화와 스키마 변화의 해석을 다루는 시스템입니다. 둘 다 데이터와 계약을 클래스 정의 하나에만 묶지 않지만 목적과 사용 위치는 다릅니다.

DOP가 잘 맞는 문제도 데이터의 성격에 따라 달라집니다

토론에서는 DOP를 모든 도메인에 같은 강도로 적용할 수 있는지 이야기를 나눴습니다. 이벤트 수집, 로그 처리, ETL, 분석 파이프라인처럼 데이터의 종류는 많지만 비슷한 변환과 집계가 반복되는 문제에서는 범용 연산과 스키마 기반 계약이 잘 어울립니다. 데이터가 여러 시스템을 오가고 스키마가 진화하는 환경에서도 장점이 분명합니다.

반대로 주문이나 결제처럼 상태 전이와 도메인 규칙이 중심인 문제에서는 행위와 불변 조건을 가까이 두는 객체 모델이 더 이해하기 쉬울 수 있습니다. 이는 DOP와 OOP 가운데 하나만 골라야 한다는 결론이 아닙니다. 경계의 데이터 교환에는 스키마와 범용 구조를 사용하고, 규칙이 밀집된 내부 모델에는 명시적인 타입과 행위를 두는 식으로 조합할 수 있습니다.

데이터 지향 프로그래밍 4주차 - 데이터는 어디에서 유효해져야 할까 - 이미지 4
문제의 중심이 데이터 변환인지, 규칙과 상태 전이인지에 따라 적절한 표현 방식이 달라집니다.

EAV(Entity-Attribute-Value)와 관계형 데이터베이스의 JSON 타입에 관한 이야기도 같은 선택 문제로 이어졌습니다. 속성이 매우 드물고 자주 달라지는 데이터는 EAV나 문서형 필드가 유연할 수 있지만, 제약 조건과 조인, 검색 성능을 관리하기 어려워질 수 있습니다. 관계가 중요한 정보는 관계형 스키마로 두고 변동이 큰 일부 속성만 JSON으로 저장하는 혼합 방식도 가능합니다. 중요한 것은 유연한 저장 형식 자체가 DOP를 보장한다고 단정하지 않는 일입니다. 조회 방식과 무결성, 변경 빈도를 함께 보고 선택해야 합니다.

결국 스키마는 자유를 지키기 위한 별도의 규율이었습니다

이번 모임에서는 범용 자료구조가 유효성 검증을 포기하는 선택이 아니라는 점을 확인했습니다. 데이터의 표현을 클래스에서 떼어 낸 뒤, 시스템 경계에서 어떤 모양을 받아들일지 스키마로 명시합니다. 데이터는 유연하게 흐르지만 아무 데이터나 통과하는 것은 아닙니다.

동시에 스키마가 모든 안전성을 한 번에 해결하지 않는다는 점도 선명했습니다. 어떤 스키마를 적용할지 결정해야 하고, 도메인 규칙은 스키마만으로 충분하지 않을 수 있으며, 스키마가 바뀔 때 생산자와 소비자의 호환성도 관리해야 합니다. 다음 학습으로 이어질 질문은 다음 세 가지였습니다.

네 번째 모임은 DOP의 네 원칙을 한 번 더 외우는 시간이기보다, 각 원칙을 실제 시스템에 적용할 때 필요한 보완 장치를 확인하는 시간이었습니다. 범용 데이터가 주는 유연함과 스키마가 주는 규율을 함께 보니, 데이터가 시스템 안으로 들어오는 첫 관문이 조금 더 구체적으로 보였습니다.

발표를 준비하고 각자의 경험을 보태며 네 번째 모임을 함께 만들어 주신 모든 분께 감사드립니다.



작성 참고

데이터 지향 프로그래밍 4주차 - 데이터는 어디에서 유효해져야 할까 - 이미지 5