본문 바로가기

도메인 모델과 설계 경계

앞 절에서 공방 예약의 기능·관계·행동을 맞췄다. 그런데 고객 화면은 예약의 정책 버전을 보고, 운영자 화면은 현재 정책을 보고, 야간 처리 프로그램은 수업 날짜만 보고 취소를 결정한다면 어떨까? 같은 예약 R501이 들어온 경로에 따라 취소되기도 하고 거부되기도 한다. 그림에 필요한 정보가 있는 것과 그 정보를 사…

취소 그림이 맞아도 규칙의 주인이 없으면 다시 틀린다

앞 절에서 공방 예약의 기능·관계·행동을 맞췄다. 그런데 고객 화면은 예약의 정책 버전을 보고, 운영자 화면은 현재 정책을 보고, 야간 처리 프로그램은 수업 날짜만 보고 취소를 결정한다면 어떨까? 같은 예약 R501이 들어온 경로에 따라 취소되기도 하고 거부되기도 한다. 그림에 필요한 정보가 있는 것과 그 정보를 사용해 규칙을 한결같이 지키는 것은 다른 문제다.

이번에는 업무의 말을 같은 뜻으로 쓰고, 그 규칙을 책임질 대상을 정하며, 함께 지킬 범위를 그린다. 클래스 이름을 많이 외우기보다 “왜 이 둘은 같은 대상인가?”, “어디까지 함께 바꿔야 하는가?”, “다른 업무에도 같은 말이 같은 뜻인가?”를 차례로 읽는다. 설계 대안은 가상 공방을 이해하기 위한 것이며 실제 구현이나 승인 결과가 아니다.

요구공학은 필요한 약속의 근거를 찾고 분석·명세·확인하며 추적과 변경을 관리하는 활동이었다(요구 도출·분석·명세·검증·추적). 기능·정적·동적 모델은 제공할 결과, 대상의 관계, 사건에 따른 행동을 나누어 같은 요구와 맞추었다(기능·정적·동적 모델). 이제 그 약속과 표현을 바탕으로 업무의 뜻과 판단 책임을 설계에 남긴다.

쉽게 말하면: 약속을 이해할 이름과 지킬 자리를 함께 만든다

공방 담당자가 “그 예약은 취소됐지만 아직 돈이 돌아가지는 않았어요”라고 말했다고 하자. 설계도 이 말을 구별할 수 있어야 한다. 어떤 예약을 계속 가리키는지, 무엇이 취소되었는지, 돈이 돌아갔다는 사실은 어디에서 확인하는지를 따로 읽을 수 있게 만든다.

그러려면 업무의 말을 이해할 이름으로 정리하고, 그 이름이 가진 정보와 바꿀 수 있는 행동을 연결해야 한다. 마지막으로 한곳에서 책임질 일과 다른 곳의 확인이 필요한 일을 구분한다. 이번 절의 긴 이름들은 이 단순한 질문들을 정확하게 나누기 위해 붙인다.

모델의 중심을 컴퓨터가 아니라 해결할 업무에 둔다

여기서 도메인(domain)은 소프트웨어로 문제를 해결하려는 지식·활동의 영역이다. 공방에서는 수업 운영, 예약, 결제와 정산 등이 그 대상이다. 인터넷 주소의 도메인이나 데이터베이스 열에 허용할 값의 범위와는 이번 말의 뜻이 다르다.

도메인 모델(domain model)은 선택한 업무 문제를 설명하고 해결하기 위해 필요한 개념·관계·규칙을 정리한 모델이다. 예약 취소를 다루는 모델에는 예약, 적용 정책, 취소 허용 조건이 필요하다. 공방 건물의 모든 가구나 모든 직원 정보를 사실대로 복사할 필요는 없다. 어떤 문제를 해결할지에 따라 남길 개념이 달라진다.

같은 공방의 사실을 고정하고 시작한다

수업 W101은 한국 표준시 9월 20일 14시에 시작한다. 취소 정책 v1은 시작 24시간 전까지, v2는 시작 12시간 전까지 허용하며 마감 시각 자체도 포함한다. v2는 한국 표준시 9월 15일 00시 이상에 새로 확정한 예약에만 적용한다.

예약고객확정 시각적용 정책과 취소 마감
R501C019월 14일 10시v1 · 9월 19일 14시
R502C029월 16일 10시v2 · 9월 20일 02시

이후 날짜도 모두 한국 표준시다. 두 예약이 같은 W101을 가리킨다고 해서 예약의 정책까지 같아지지는 않는다. 예약 취소와 외부 결제의 환불 완료도 앞 절처럼 별개로 유지한다. 조회 실패·없는 예약·확정과 취소 이외 상태는 별도로 확인할 요구이며, 이 절의 표가 그 요구를 없애지는 않는다.

도메인 주도 설계는 패턴 이름보다 함께 이해하는 일에서 출발한다

도메인 주도 설계(DDD, Domain-Driven Design)는 중요한 업무 문제의 모델을 업무 전문가와 개발자가 함께 다듬고, 그 모델과 언어를 소프트웨어 설계에 연결하는 접근이다. 업무 전문가(domain expert)는 해당 업무의 용어·판단·예외와 그 이유를 실제로 설명할 수 있는 사람이다. 공방에서는 취소 규정을 운영하며 고객의 예외 요청을 처리하는 담당자가 그 역할을 맡을 수 있다.

개발자가 혼자 ‘예약취소관리자’라는 클래스를 만들었다고 DDD가 완성되지는 않는다. 담당자의 “취소됐다”가 예약 상태를 뜻하는지 환불 완료를 뜻하는지부터 맞춰야 한다. 반대로 업무 담당자의 기존 말이 언제나 완전한 정답인 것도 아니다. 서로 다른 예에서 모순이 드러나면 질문하고 모델과 표현을 함께 고친다.

용어집 한 장이 아니라 같은 판단을 만드는 언어다

유비쿼터스 언어(ubiquitous language)는 한 모델을 적용하기로 정한 범위 안에서 업무 담당자와 개발자가 대화·문서·모델·코드에 일관되게 사용하는 업무 언어다. ‘유비쿼터스’는 이 팀의 활동 곳곳에서 쓴다는 뜻이지, 회사 전체에 단 하나의 모델을 강제한다는 뜻이 아니다. 이 적용 범위는 뒤에서 바운디드 컨텍스트라는 이름으로 구체화한다.

표현공방 예약 업무에서의 뜻잘못 섞으면 생기는 일
수업W101처럼 정해진 시작 시각을 가진 회차W101을 예약번호로 오인
예약고객이 특정 수업에 대해 확정한 신청같은 수업의 여러 예약을 하나로 합침
적용 정책그 예약을 확정할 때 정한 정책 버전현재 정책으로 옛 예약을 다시 판정
취소 완료예약을 취소 상태로 확정하고 결과를 남김확인되지 않은 환불까지 완료 표시

명사만 맞춰서는 부족하다. “이미 취소된 본인 예약에는 기존 결과를 돌려준다”처럼 조건·행동·결과가 있는 문장도 같은 뜻으로 읽혀야 한다. 용어집을 고쳤는데 상태도와 답안의 판단은 그대로라면 언어와 모델은 여전히 어긋난다.

같은 사례를 오가며 말을 다듬는다

같은 사례로 문장·모델·설계를 다시 묻는다

화살표의 방향과 조건을 따라 연결을 읽습니다.

같은 사례로 문장·모델·설계를 다시 묻는다업무 사례에서 같은 뜻의 문장과 개념·관계·규칙을 정리하고 모순을 다시 질문한다. 개념 모델을 바탕으로 설계·코드·검증 사례를 만들고, 그 결과에서 발견한 모순도 다시 질문한다.같은 뜻 표현모델에 남김모순 확인구현·사례로 대조모순 확인사례를 다시 질문업무 사례·예외같은 뜻의 문장개념·관계·규칙설계·코드검증 사례모순을 다시 질문실선 화살표: 이해와 검토의 피드백 · 시간·호출 순서가 아님

화살표는 이해를 맞추는 피드백이다. 프로그램 호출 순서가 아니다. R501에 현재 v2를 잘못 적용했다면 적용 정책의 문장·정보·판정·경계시각 예를 함께 대조한다.

같은 사례로 문장·모델·설계를 다시 묻는다업무 사례·예외에서 같은 뜻의 문장을 만들고 개념·관계·규칙 모델에 남긴다. 모델은 설계·코드·검증 사례와 대조하고, 모델 자체와 설계 결과에서 각각 모순을 다시 질문한다. 질문은 다시 같은 업무 사례로 올라간다. 여섯 선은 이해와 검토의 피드백이며 시간이나 프로그램 호출 순서가 아니다.문장·모델·설계의검토 피드백같은 뜻표현모델에남김구현·사례로 대조사례를다시 질문모순 확인모순확인업무사례·예외같은 뜻의문장개념·관계·규칙설계·코드검증 사례모순을 다시질문실선 화살표: 이해·검토피드백시간·호출 순서가 아니다.

화살표는 실제 프로그램의 호출 순서가 아니라 이해를 맞추는 피드백을 나타낸다. R501의 취소 기한을 현재 v2로 계산한 설계가 발견되면 ‘적용 정책’의 문장, 예약이 기억할 정보, 취소 판정과 경계 시각 예를 함께 대조한다. 그림만 고치거나 이름만 번역하고 끝내지 않는 이유다.

클래스 그림·저장 표·도메인 모델은 같은 말이 아니다

앞 절의 클래스 그림으로 도메인 개념과 관계를 표현할 수 있다. 그렇지만 클래스 그림이라는 형식 자체가 업무 규칙을 보장하지는 않는다. 예약과 정책을 잇는 선 옆에 ‘현재 정책’이라고만 적으면 그림은 그려져도 과거 예약의 약속은 보존되지 않는다.

저장할 표도 모델을 구현하는 한 수단이다. ‘정책번호’라는 열이 있다는 사실만으로 어느 시점의 어떤 정책을 적용할지 결정되지는 않는다. 이 절은 업무상 같은 대상의 기준과 규칙의 책임을 다루며, 앞의 데이터 모델링에서 배운 키·관계·정규화나 앞 절의 그림 표기를 다시 정의하지 않는다.

객체와 조합은 상태와 행위를 묶고 작은 객체를 사용해 더 큰 행위를 만드는 방법이었다(모듈·캡슐화·객체·인터페이스). 방금 도메인 모델에서는 업무의 문제에 필요한 개념·관계·규칙을 남겼다. 이제 프로그램 안에 객체가 두 개 있다는 사실과 업무상 대상이 두 개라는 판단을 구별한다.

같은 대상인가, 같은 값인가

도메인 설계의 엔터티(entity)는 속성이 바뀌어도 시간이 지나면서 같은 업무 대상으로 추적해야 하는 객체다. R501이 확정에서 취소로 바뀌어도 R501은 같은 예약이다. 반대로 고객·수업·금액이 우연히 같아도 별도로 확정한 두 예약을 하나로 합치면 안 된다. 무엇으로 같은 대상을 판단할지 모델에 명시해야 한다.

값 객체(value object)는 독립적인 업무 식별성을 추적하지 않고, 의미 있는 속성들의 값과 그 값에 대한 규칙으로 구별하는 객체다. 예를 들어 ‘시간 단위 취소 기준’이 24시간과 경계 포함 여부로 완전히 정해진다면, 객체를 두 번 만들어도 두 속성이 같으면 같은 기준값으로 본다. 프로그램의 메모리 위치나 저장 표의 번호를 부여했다는 사실만으로 업무 식별성이 생기지는 않는다.

예약번호가 하는 일과 시간값이 하는 일이 다르다

비교할 둘같은가판단 이유
취소 전 R501 / 취소 후 R501같은 예약상태가 달라도 업무 식별성이 이어짐
R501 / R502다른 예약W101을 함께 가리켜도 별도 확정한 신청
(24시간, 경계 포함) / (24시간, 경계 포함)같은 취소 기준값이 예의 의미를 구성하는 두 속성이 같음
(24시간, 경계 포함) / (12시간, 경계 포함)다른 취소 기준값허용 기한을 계산하는 시간이 다름

괄호 안의 두 항목은 이 예에서 값을 비교할 속성을 읽기 쉽게 묶은 표시다. 기간만 같아도 정책 버전 v1과 v2를 자동으로 같은 정책으로 합치지는 않는다. 정책은 적용 이력과 버전의 구별이 필요하고, 그 정책에 담긴 시간 기준은 값으로 다룰 수 있다. 무엇을 식별하는지가 다르므로 ‘한 정책에 값이 있다’와 ‘정책 자체를 값으로만 본다’를 구별한다.

값을 고쳐 쓰지 않고 다른 값으로 바꾸는 이유

불변 값(immutable value)은 만들어진 뒤 그 값의 내용을 바꾸지 않는 값이다. 값 객체는 이 방식으로 다루는 것이 기본 설계 원칙이다. 24시간 기준이 필요한 예약과 12시간 기준이 필요한 예약이 함께 있어도, 기존 24시간 객체의 내부를 12로 고치는 대신 별도의 12시간 값을 만든다.

기존 객체를 여러 예약이 함께 참조하고 있었다면 내부 수정을 한 번 하는 것만으로 여러 예약의 뜻이 바뀔 수 있다. 새 값을 만드는 방식은 이런 뜻밖의 변경을 피한다. 다만 새로운 값 객체를 만들 수 있다는 것과 확정한 예약의 적용 정책을 임의로 바꿀 수 있다는 것은 다르다. 후자는 별도의 업무 변경 권한과 정책을 따라야 한다.

값 객체에도 계산과 거부 조건이 있다

이 공방의 ‘시간 단위 취소 기준’은 음수가 아닌 정수 시간과 경계 포함 여부를 가진다고 정한 설계 대안이다. 이 조건을 벗어난 -12시간은 정상 값으로 만들지 않는다. 이 제약은 모든 공방에 적용되는 보편 규정이 아니라, 여기서 사용하는 값의 의미를 명확히 하기 위한 가정이다.

W101의 시작에서 시간을 빼면 v1은 9월 20일 14시 - 24시간 = 9월 19일 14시, v2는 9월 20일 14시 - 12시간 = 9월 20일 02시다. 경계 포함 값이 참이면 요청 시각이 마감과 같아도 시간 조건은 통과한다. 여기서 구한 것은 시간 조건의 결과일 뿐이다. 다른 사람의 예약을 취소할 권한이나 이미 취소되었는지까지 이 작은 값이 판단하지는 않는다.

번호가 있으면 엔터티이고 번호가 없으면 값이라는 암기는 부족하다

화면에 같은 예약을 표시하려고 두 객체를 읽어 왔어도 업무상 식별값이 R501이면 같은 예약을 표현할 수 있다. 반대로 모든 속성이 우연히 같은 두 신청도 별도 예약으로 추적해야 한다면 식별 기준이 필요하다. 식별값은 그 판단을 표현하는 수단이지 그 판단을 대신하는 답이 아니다.

값 객체를 데이터베이스 표에 저장하면서 내부 번호를 붙이는 구현도 가능하다. 그 번호가 있어도 업무가 비교하는 것은 값일 수 있다. 또 값 객체는 반드시 숫자 하나여야 하는 것도 아니다. 시간과 경계 포함 여부처럼 함께 있어야 의미가 분명해지는 속성 묶음과 계산을 가질 수 있다.

캡슐화는 내부 상태를 직접 고치게 하지 않고 허용된 연산으로 규칙을 지키는 원칙이었으며, 불변조건은 그 연산의 정해진 관찰 지점에서 유지해야 할 조건이었다(모듈·캡슐화·객체·인터페이스). 엔터티는 업무상 같은 대상을 이어 추적하고 값 객체는 의미 있는 값으로 비교했다. 이제 이 대상과 값이 함께 지킬 규칙을 한곳에서 책임지게 한다.

어떤 규칙을 누가 책임져야 할까

도메인 책임(domain responsibility)은 모델의 어떤 요소가 알아야 할 업무 정보와 수행하거나 보장해야 할 업무 판단을 뜻한다. 공방 예약은 자기 소유 고객, 적용 정책, 현재 상태를 알아야 하고, 취소 요청에 따라 무엇을 바꿀지 일관되게 판단해야 한다. 시간 기준값에는 시간 계산을 맡기되 예약 상태까지 바꾸게 하지는 않는다.

이 절의 작은 설계에서는 “확정 예약에는 취소 결과가 없고, 취소된 예약에는 확정한 취소 결과가 하나 있다”를 지킬 조건으로 정한다. 취소 결과에는 해당 예약번호, 최초 취소 확정 시각, 판단에 사용한 정책 버전을 남긴다고 가정한다. 이는 앞 절의 반복 요청에 기존 결과를 제공하기 위한 설계 대안이며, 실제 시스템의 모든 감사 항목이나 법적 보존 의무를 대신 정한 것이 아니다.

바뀌지 않아야 하는 것은 상태값이 아니라 약속이다

예약 상태취소 결과 수이 예의 조건판단
확정0개취소 결과가 없어야 함유효한 시작 상태
취소1개확정 결과가 정확히 하나 있어야 함유효한 완료 상태
취소0개기존 결과를 돌려줄 근거가 없음부분 변경 오류
취소2개한 예약의 최초 결과를 두 번 확정함중복 변경 오류

예약의 상태값은 확정에서 취소로 바뀐다. 그래도 정상적으로 끝난 상태는 정한 조건을 계속 만족해야 한다. 그래서 앞서 배운 ‘불변 값’과 ‘불변조건’은 다르다. 앞의 것은 값 자체를 고치지 않는 방식이고, 뒤의 것은 상태가 달라져도 유지할 약속이다.

여기서 검사하는 시점은 외부에서 완료 상태로 받아들이는 시점이다. 내부 처리 중 어떤 값을 먼저 바꾸는지와, 그 중간 상태를 완료 상태로 공개하거나 저장을 확정해도 되는지는 구별한다. 다른 요청이 그 틈을 잘못 읽는 문제는 뒤의 트랜잭션 경계에서 함께 살핀다.

취소를 판단하는 길을 한곳으로 모은다

이 설계에서는 고객 화면과 운영 도구가 예약 상태나 취소 결과를 각각 직접 고치지 않는다. 예약의 취소 연산에 확인된 고객, 요청 시각과 필요한 정책 정보를 전달한다. 예약은 먼저 소유자를 확인하고, 본인의 이미 취소된 예약이면 기존 결과를 반환하며, 본인의 확정 예약이면 적용 정책의 시간 조건으로 허용·거부를 정한다.

허용이면 상태와 결과를 함께 바꾸고, 거부이면 둘 다 바꾸지 않는다. 이렇게 하면 ‘예약 상태를 바꾸는 책임’과 ‘허용 조건을 지키는 책임’이 갈라지지 않는다. 외부의 모든 요청이 이 경로를 따른다는 설계 약속도 필요하다. 내부 연산을 만들어 놓고 다른 경로에서 상태를 직접 쓰게 두면 캡슐화의 목적은 달성되지 않는다.

트랜잭션은 함께 성공하거나 실패해야 할 논리적 작업단위였고, 원자성은 일부만 확정되지 않게 하는 성질이었다(트랜잭션의 네 성질(ACID)). ACID의 일관성은 업무 조건을 만족하는 상태 사이의 이동이며, 격리성은 함께 실행되는 작업의 중간효과를 통제하는 성질이었다. 따라서 규칙을 한 객체에 적는 일과 그 변경을 장애·동시 요청에서도 지키게 하는 일을 함께 연결해야 한다.

함께 지킬 묶음과 그 묶음의 입구를 정한다

애그리게이트(aggregate)는 함께 일관성을 지켜야 할 엔터티와 값 객체를 하나의 경계로 묶어 다루는 도메인 모델의 단위다. 한 엔터티만으로 이루어질 수도 있으며, 클래스 그림에 연결된 모든 객체를 자동으로 같은 묶음에 넣는 것은 아니다. 어떤 조건을 함께 지켜야 하는지가 기준이다.

애그리게이트 루트(aggregate root)는 그 묶음을 대표하는 엔터티로서 외부 접근의 입구가 되고 묶음 전체의 규칙을 지킬 책임을 맡는다. 이번 대안의 루트는 예약 R501이며, 취소 결과와 시간 기준값은 그 내부에서 다룬다. 다른 대상은 루트를 통해 변경을 요청하고 내부 구성요소를 붙잡아 임의로 고치지 않는다. 단지 외부에서 가장 먼저 호출된 함수라는 이유만으로 루트가 되는 것은 아니다.

관계가 있다는 것과 내부 구성원이라는 것은 다르다

예약 루트의 일관성과 외부 완료의 경계

상자 안의 대상과 화살표의 조건을 함께 읽으세요. 좁은 화면에서는 같은 관계를 세로로 재배치한 도식을 보여 줍니다.

예약 루트의 일관성과 외부 완료의 경계루트가 예약 상태·취소 결과의 일관성 규칙을 지킨다. 정책 버전은 판단에 쓰는 외부 참조다. 이 모델 경계 자체가 저장 트랜잭션이나 외부 환불 완료를 보장하지는 않는다.request → root: 행동 요청root → state: 상태 규칙root → result: 결과 규칙root → value: 기한 판단root → policy: 판본 참조행동 요청상태 규칙결과 규칙기한 판단판본 참조예약 애그리게이트의 규칙 경계취소 요청: R501 · 본인 확인취소 요청R501 · 본인 확인예약 루트 R501: 규칙 판단예약 루트 R501규칙 판단예약 상태: 확정 → 취소예약 상태확정 → 취소취소 결과: 0..1개취소 결과0..1개시간·기한 값: 값으로 비교시간·기한 값값으로 비교취소 정책 버전: 적용 판본 참조취소 정책 버전적용 판본 참조
예약 루트의 일관성과 외부 완료의 경계루트가 예약 상태·취소 결과의 일관성 규칙을 지킨다. 정책 버전은 판단에 쓰는 외부 참조다. 이 모델 경계 자체가 저장 트랜잭션이나 외부 환불 완료를 보장하지는 않는다.request → root: 행동 요청root → state: 상태 규칙root → result: 결과 규칙root → value: 기한 판단root → policy: 판본 참조행동 요청상태 규칙결과 규칙기한 판단판본 참조예약 애그리게이트의규칙 경계취소 요청: R501 · 본인 확인취소 요청R501 · 본인 확인예약 루트 R501: 규칙 판단예약 루트R501규칙 판단예약 상태: 확정 → 취소예약 상태확정 → 취소취소 결과: 0..1개취소 결과0..1개시간·기한 값: 값으로 비교시간·기한 값값으로 비교취소 정책 버전: 적용 판본 참조취소 정책 버전적용 판본 참조

루트가 예약 상태·취소 결과의 일관성 규칙을 지킨다. 정책 버전은 판단에 쓰는 외부 참조다. 이 모델 경계 자체가 저장 트랜잭션이나 외부 환불 완료를 보장하지는 않는다.

루트 안과 외부 참조를 말로 확인하기

그림의 예약 루트 R501을 입구로 현재 상태·취소 결과 0개 또는 1개·시간 기준값을 함께 읽는다. 그림에는 외부 적용 정책 v1을 가리키는 판본 참조가 표시되어 있다. 본문에서 가정한 수업 W101도 식별값으로 가리키는 외부 대상이지만, 이 그림에는 별도 노드와 참조선을 그리지 않았다. 수업의 전체 예약 이력을 R501 안에 넣지 않는다.

이 그림은 설계 경계를 설명하는 자체 구성이며 UML의 공유 집약이나 합성 기호가 아니다. 앞 절의 관계 표기만으로 애그리게이트가 자동 결정되지 않는다. 예약이 수업을 가리킨다고 수업의 전체 예약 이력까지 R501 내부에 넣지는 않는다.

정책 v1의 시간 기준을 예약에 보관하는 방식과 확정 후 바뀌지 않는 v1의 값을 참조하는 방식은 모두 검토할 수 있다. 어느 쪽이든 R501에 현재 v2를 대입하지 않는다는 업무 약속은 같다. 이 그림만으로 구체적인 저장 표 수나 데이터 복사 방식을 하나로 확정하지 않는다.

애그리게이트는 모델의 경계이고 트랜잭션은 실행의 경계다

앞에서 배운 트랜잭션을 이 취소 작업에 적용할 때에는, 어떤 변경을 함께 확정하거나 취소할지 시작과 끝·포함 범위를 정한다. 여기서 말하는 트랜잭션 경계는 그 실행 범위를 가리킨다. 애그리게이트 경계가 ‘어떤 대상들의 규칙을 함께 책임질까’를 정한다면, 트랜잭션 경계는 ‘이번 실행에서 어떤 변경을 함께 확정할까’를 정한다.

DDD의 애그리게이트 원칙에서는 경계 안의 조건을 한 변경에서 맞추고, 그 경계가 트랜잭션 설계를 이끌도록 한다. R501의 상태를 취소로 바꾸는 저장과 취소 결과를 하나 남기는 저장을 따로 확정하면 중간 실패로 앞의 상태·결과 표에서 잘못된 행이 생긴다. 같은 트랜잭션에서 검사·변경·저장하고 실패하면 함께 취소해야 한다.

하지만 루트라는 이름이나 객체 메서드 호출이 데이터베이스의 원자성·격리성을 자동 제공하지는 않는다. 두 요청이 같은 ‘아직 확정’ 상태를 읽더라도 취소 결과를 중복 확정하지 않도록 충돌 통제도 필요하다. 그 구현의 격리·잠금·복구 원리는 이미 배운 트랜잭션과 DBMS 절에서 이어 읽으며, 여기서는 보호할 조건과 실행 범위를 연결한다.

정상·실패·반복을 같은 두 값으로 읽는다

아래는 실제 실행 결과가 아니라, 허용된 본인 취소에서 저장이 만족해야 할 결과를 계산한 설계 예다. 처음은 (확정, 결과 0개)이고 두 값은 예약 상태와 취소 결과 수다.

상황보호할 변화완료 후 기대 상태
정상 취소상태와 결과를 함께 확정(취소, 결과 1개)
결과 저장 전 실패상태 변경도 확정하지 않음(확정, 결과 0개)
완료 응답이 유실되어 다시 요청이미 확정한 결과 반환(취소, 결과 1개)
두 취소 요청이 겹침한 결과만 확정되게 충돌 통제(취소, 결과 1개)

두 번째 행은 아직 커밋하지 못한 실패다. 세 번째 행은 커밋했으나 요청자가 응답을 못 받은 경우이므로 원래 취소를 되돌린다고 단정하지 않는다. 같은 ‘실패’라는 말 아래 이 둘을 섞으면 재요청이 새 취소 결과를 만드는 오류를 낳는다.

중간 상태를 하나만 바꾸어 원인을 찾아 보자. 상태 저장만 별도 커밋하여 (취소, 결과 0개)가 남은 뒤 결과 저장이 실패했다면, 표의 정상 실패 행인 (확정, 결과 0개)로 돌아온 것이 아니다. 이미 일부가 확정된 잘못된 상태다. 해결할 설계는 두 저장을 함께 확정·취소하도록 실행 경계를 맞추는 것이며, 루트라는 이름을 붙이는 것만으로 고쳐지지 않는다. 반대로 (취소, 결과 1개)까지 함께 커밋한 뒤 응답만 잃었다면, 본인 재요청에 새 결과를 추가하지 않고 기존 한 개를 제공한다. 응답이 없으니 모두 처음부터 취소한다라고 답했다면 애그리게이트는 모델의 경계이고 트랜잭션은 실행의 경계다에서 설명한 확정 경계와 정상·실패·반복을 같은 두 값으로 읽는다에서 비교한 두 실패 시점을 다시 구별한다. 독립 설계에서는 먼저 실패가 커밋 전인지 후인지 표시한 뒤 보호할 상태·결과를 적는다.

정원 8명은 예약 하나만 보아서는 지킬 수 없다

앞 절의 수업 W101 정원을 8명으로 두고 현재 자리를 점유하는 확정 예약이 7개라고 하자. 새 신청 A와 B가 각각 자기 예약만 보며 ‘7은 8보다 작다’고 허용하면 합계는 7 + 1 + 1 = 9가 된다. 개별 예약이 자기 상태 규칙을 지켰어도 수업의 정원 조건은 깨질 수 있다. 취소 이력을 포함한 과거 예약 전체 수가 아니라 현재 자리를 점유하는 수를 센다는 점도 유지한다.

‘수업별 점유 수가 정원 이하’라는 조건을 매번 엄격히 지켜야 한다면 그 수와 자리 배정을 책임지는 경계를 별도로 찾아야 한다. 한 대안은 수업의 자리 배정을 하나의 작은 애그리게이트로 다루어 요청 둘이 같은 마지막 자리를 함께 얻지 못하게 하는 것이다. 이것은 R501에 공방의 모든 수업·고객·이력을 넣으라는 뜻이 아니다.

이때 예약 취소와 자리 반환도 같은 순간에 확정되어야 한다는 요구까지 있다면, 예약과 자리 배정을 분리한 그림만으로 해결되었다고 할 수 없다. 함께 보호할 변경을 다시 묶을지, 중간 상태를 허용하도록 업무 절차를 합의할지 검토해야 한다. 후자를 임의로 선택해서 좌석 요구를 낮추면 안 된다. 이 예는 경계 선택의 미해결 조건을 드러낸 것이며 실제 자리 배정·반환 설계를 완료한 것이 아니다.

무조건 크게도, 무조건 작게도 묶지 않는다

선택얻는 점비용·실패 위험다시 물을 조건
관련 대상을 아주 크게 묶음함께 검사할 범위를 한곳에 둠무관한 변경도 같은 경계에서 충돌정말 매번 함께 지켜야 하는가
불변조건별로 필요한 범위를 묶음책임과 충돌 범위를 설명하기 쉬움경계를 넘는 업무를 별도로 검토해야 함밖의 조건을 숨기지 않았는가
표나 클래스마다 무조건 분리겉보기 단위가 작아짐함께 지킬 조건이 갈라짐부분 확정에서 무엇이 깨지는가

루트는 가장 많은 데이터를 가진 객체나 가장 위에 그린 객체가 아니라, 해당 묶음의 규칙을 지킬 대표다. 외부 객체가 내부 상태를 직접 바꾸는 길, 커밋 전후의 실패, 두 요청의 충돌을 대입해 보아야 경계가 의미를 갖는다. 큰 경계가 언제나 틀린 것도 아니지만 단지 연관선이 있다는 이유만으로 크게 묶지는 않는다.

업무 판단과 실행 순서의 조정을 구분한다

도메인 서비스(domain service)는 중요한 업무 연산이 어느 엔터티나 값 객체의 자연스러운 책임에도 맞지 않을 때 모델에 명시하는 독립적인 업무 연산이다. 예를 들어 두 업무 대상 사이의 변환 규칙을 어느 한 대상에 억지로 붙이면 뜻이 흐려질 때 검토한다. 지금의 단순 시간 계산은 시간 기준값이 자연스럽게 담당할 수 있으므로 모든 판단을 ‘서비스’로 옮길 이유가 없다.

애플리케이션 서비스(application service)는 한 사용 사례를 수행하도록 필요한 대상을 구하고 업무 연산과 저장 등의 순서를 조정하는 역할이다. 공방의 취소 요청 처리에서는 신원 확인 결과를 받고 대상 예약과 정책을 구해 취소 연산을 요청하고 변경을 저장하도록 조정할 수 있다. 취소 기한 규칙을 이 조정 코드와 예약 객체에 각각 복사하면 두 곳이 다른 답을 낼 수 있다.

둘의 ‘서비스’는 각각 업무 연산과 사용 사례 조정의 역할을 뜻하며 별도 서버라는 말이 아니다. 이 구분은 역할을 나누는 대안이지 화면 호출 하나마다 두 서비스 클래스를 반드시 만들어야 한다는 규칙이 아니다. 별도로 맡길 독립 책임이 없다면 호출만 전달하는 이름을 늘리지 않는다.

저장 기술의 이름이 업무 모델을 대신하지 않게 한다

도메인 설계의 리포지터리(repository)는 필요한 애그리게이트 루트를 업무상 의미 있는 조건으로 찾고 보관하도록 제공하는 접근 추상화다. ‘예약번호로 예약을 찾는다’는 계약 뒤에 실제 저장과 조회 방법을 감추는 식이다. 이 말은 소스 코드를 보관하는 Git 저장소를 뜻하지 않는다.

예약을 구한다는 업무 표현은 유지하면서 실제로 어떤 표와 질의를 사용할지는 따로 결정할 수 있다. 모든 표마다 리포지터리를 하나씩 만들어야 하는 것은 아니며, 애그리게이트 내부 구성요소를 별도 입구로 고쳐 전체 규칙을 우회하지 않아야 한다. 조회 편의를 위해 그린 관계를 모두 변경 가능하게 노출하는 설계도 같은 주의가 필요하다.

리포지터리를 두었다고 트랜잭션이 자동으로 묶이거나 저장이 성공하는 것은 아니다. 저장 실패와 확정 결과의 처리 책임은 앞서 살핀 저장 확정과 실패의 실행 경계에 여전히 남는다. 지금 설명은 역할과 책임의 구별이며 특정 언어나 저장 제품의 구현 계약이 아니다.

도메인 모델과 업무 언어는 같은 문제의 개념·관계·규칙을 일관되게 표현했고, 애그리게이트는 함께 지킬 조건을 책임지는 묶음이었다. 그러나 예약의 취소 상태를 지킨다고 결제 쪽의 환불 완료까지 자동으로 결정되지는 않았다. 이제 한 모델과 그 말의 뜻이 어디까지 통하는지 경계를 정한다.

같은 말이 통하는 범위를 정한다

바운디드 컨텍스트(bounded context)는 특정 모델과 그 언어의 뜻을 정의하고 일관되게 적용하는 명시적인 경계다. ‘한정된 맥락’이라는 번역처럼, 어느 범위에서 이 모델을 사용하고 어디에서는 다른 모델의 뜻을 확인해야 하는지 정한다. 경계를 설명할 때 담당 업무·팀·적용 코드와 데이터 범위를 구체적으로 연결할 수 있다.

공방의 예약 운영에서는 고객이 수업을 신청하고 예약을 취소한다. 결제 정산에서는 결제된 금액과 돌려줄 금액, 실제 환불 결과가 중요하다. 둘이 ‘고객’이나 ‘취소’라는 말을 공유해도 그 말이 가리키는 정보와 완료 조건까지 자동으로 같아지는 것은 아니다. 별도 모델이 필요한 이유를 먼저 확인하고, 이름이 조금 다르다는 이유만으로 무조건 경계를 늘리지 않는다.

예약 운영의 완료와 결제 정산의 완료

질문예약 운영의 모델결제 정산의 모델
중심 대상수업, 예약, 적용 취소 정책결제, 환불 요청, 환불 결과
지금 말하는 완료해당 예약의 취소가 확정됨해당 환불의 처리 결과가 확인됨
R501이 취소되었다는 사실예약 상태와 기존 취소 결과의 근거환불 판단에 들어갈 수 있는 입력
그 사실만으로 알 수 없는 것외부 환불의 실제 성공환불 금액·가능 조건·완료 여부

이 표는 다른 모델을 구분하는 가상 대안이다. 앞 절에서 미정으로 남긴 환불 규칙을 새로 승인한 것이 아니다. 취소 사실을 받았다는 이유만으로 환불 완료를 표시하는 것은, 필요한 판단과 외부 결과 확인을 건너뛴 오류다.

이 경우 업무 언어를 일관되게 쓴다는 말은 ‘취소’를 모두 ‘환불’로 이름만 통일한다는 뜻이 아니다. 예약 취소와 환불 완료를 각각 정확히 말하고 두 개념을 어떻게 연결할지 명시하는 편이 더 일관된다.

경계를 넘으면 뜻을 옮기는 약속이 필요하다

컨텍스트 맵(context map)은 각 바운디드 컨텍스트와 그 사이의 접점·관계·변환 책임을 명시한 모델이다. 현재 누가 어떤 정보를 제공하고 받는지, 같은 식별값을 그대로 쓸 수 있는지, 뜻을 어디에서 바꾸는지를 드러낸다. 곧바로 미래의 서버 배치 그림을 그리는 것이 아니다.

예약 취소 사실을 결제 정산의 입력으로 옮긴다

화살표의 방향과 조건을 따라 연결을 읽습니다.

예약 취소 사실을 결제 정산의 입력으로 옮긴다예약 운영에서 R501의 취소를 확정할 때 적용한 정책은 v1이다. 예약번호·확정시각·정책 버전이라는 취소 사실이 접점으로 전달된다. 접점은 예약번호와 결제 대상의 연결을 확인하여 환불 판단에 필요한 입력을 결제 정산 모델로 전달한다.취소 사실: 예약번호·확정시각·정책 버전환불 판단에 필요한 입력예약 운영 모델R501 취소 확정 · 정책 v1뜻·식별값 연결 접점예약번호 ↔ 결제 대상의 연결 확인결제 정산 모델환불 규칙 판단 → 처리 결과 확인

선은 넘길 정보의 뜻과 변환 책임을 보여준다. 실시간 전달·횟수·오류 복구를 확정하지 않는다. 예약 취소 확정 사실은 환불 판단의 입력이며 환불 성공 자체가 아니다.

예약 취소 사실을 결제 정산의 입력으로 옮긴다예약 운영 모델의 R501 취소 확정·정책 v1에서 예약번호·확정시각·정책 버전이라는 취소 사실이 접점으로 전달된다. 뜻·식별값 접점은 예약번호와 결제 대상의 연결을 확인해 결제 정산 모델의 환불 판단에 필요한 입력으로 옮긴다. 환불 규칙 판단과 처리 결과 확인은 별개이며 예약 취소 확정이 환불 완료를 뜻하지 않는다. 선은 전달 시간·횟수·복구나 서버 배치를 정하지 않는다.예약 사실에서정산 판단 입력으로취소 사실:예약번호·확정시각·정책 버전환불판단에필요한입력예약 운영 모델R501 취소 확정정책 v1뜻·식별값연결 접점예약번호 ↔ 결제대상의연결 확인결제 정산 모델환불 규칙 판단 →처리 결과 확인취소 확정 ≠ 환불 완료실시간·횟수·복구방식이나서버 배치를 확정하는 선아님.

화살표는 모델 간에 어떤 뜻의 정보가 넘어가는지를 나타내며 실시간 전달, 전달 횟수, 오류 복구 방식을 확정한 기호가 아니다. 예약번호와 결제번호가 우연히 같다고 같은 식별 체계로 취급하지 않는다. 연결이 없거나 여러 결제가 연결되는 경우의 결과, 필요한 금액·규정, 재처리 책임은 접점 계약에서 확인할 사항이다.

세 가지 경계는 크기만 다른 같은 상자가 아니다

구분정하는 질문공방에서의 예같다고 단정하면 안 되는 것
애그리게이트어떤 대상의 조건을 함께 책임지나예약 상태와 취소 결과클래스·저장 표 하나와 항상 동일
트랜잭션 경계이번 변경을 어디까지 함께 확정하나상태 변경과 결과 저장의 전부 또는 전무루트를 호출하면 자동으로 확보
바운디드 컨텍스트어떤 모델과 말의 뜻이 어디까지 통하나예약 운영과 결제 정산의 의미 구별하나의 서버·데이터베이스와 항상 동일

한 컨텍스트에 여러 애그리게이트가 있을 수 있다. 또한 모델의 뜻을 분리했다고 반드시 별도 서버를 만들어야 하는 것은 아니다. 모델 경계와 물리적 배포는 질문이 다르며, 나중의 아키텍처에서는 규모·장애·운영 요구까지 고려해 배치를 결정한다. 여기서 배우는 경계가 그 선택의 근거가 될 수는 있지만 곧바로 배치의 정답은 아니다.

작은 모델로 반례를 설명할 수 있는지 본다

처음부터 회사 전체의 완성 모델을 그리는 대신 필요한 업무의 실제 말과 예외를 모은다. 같은 대상을 추적해야 하는지, 값만 비교하면 되는지 정하고, 정상 종료 때 유지할 조건을 문장으로 쓴다. 그런 다음 그 조건을 책임질 묶음과 변경 경계, 다른 의미로 쓰는 모델과의 접점을 고른다.

규칙이 단순하고 한 요청의 처리 순서로 충분히 이해되는 부분이라면 절차 중심의 구성도 비교할 수 있다. 도메인 모델을 정교하게 다듬는 비용은 반복되는 업무 규칙과 복잡한 변경을 이해하는 이익으로 설명해야 한다. 이름만 늘린 루트·서비스·리포지터리가 조건을 더 잘 지키지 못한다면 책임을 다시 단순하게 정리한다.

단순하게 만든다는 이유로 취소 결과의 중복이나 정원 초과 같은 조건을 삭제하지는 않는다. 필요한 규칙은 유지하면서, 그 규칙을 설명하고 지킬 수 있는 가장 작은 책임과 경계를 찾는 것이 이 절의 선택 기준이다.

처음의 두 예약을 끝까지 같은 뜻으로 읽는다

아래 각 행은 독립된 시작 상태에서 생각한 설계 판정이다. 앞 행의 취소를 다음 행에 누적하지 않는다. 취소 결과 수의 표현은 앞서 정한 취소 결과를 하나 남기는 설계 대안을 따른다.

시작 상태·요청판단할 근거예약의 기대 결과경계 밖에서 모르는 것
확정 R501, C01, 9월 19일 14시v1 마감과 같음취소·결과 1개실제 환불 완료
확정 R501, C01, 같은 날 14시 00분 01초v1 마감 1초 경과확정·결과 0개운영자 예외 승인 여부
확정 R502, C02, 9월 19일 20시v2 마감의 6시간 전취소·결과 1개실제 환불 완료
이미 취소된 R501, C01, 9월 20일 03시본인 예약의 기존 결과취소·결과 1개 유지환불 재처리 필요 여부
확정 R501, C02, 9월 19일 13시다른 사람의 예약거부·상태와 결과 보존타인 예약의 처리 내역

마감 시각 계산은 시간값의 책임이고, 예약 소유자·상태·정책을 합친 취소 판단은 예약의 책임이다. 취소 결과를 하나만 확정하는 것은 애그리게이트와 실행 경계가 함께 지킬 조건이며, 그 결과를 환불 완료와 구별하는 것은 모델의 의미 경계다. 같은 작은 사례가 네 판단을 연결한다.

좋은 이름만으로 가려지지 않는 다섯 실패

‘취소’를 예약과 환불에서 같은 완료로 쓰면 모델의 뜻이 섞인다. 각각의 완료 조건과 연결 계약을 명시한다. 모든 객체를 번호 중심으로 다루면 의미가 같은 값까지 불필요하게 추적하고, 반대로 예약을 속성만으로 비교하면 별도 신청이 합쳐질 수 있다. 업무상 동일성 기준으로 구분한다.

루트 밖에서 내부 값을 직접 고치면 규칙을 우회한다. 허용한 변경 입구로 책임을 모으고 저장의 확정·충돌 조건까지 연결한다. 모든 관계를 크게 묶으면 무관한 변경의 충돌이 늘 수 있고, 무조건 잘게 쪼개면 함께 지킬 조건이 사라진다. 정원 8명 반례처럼 경계를 넘는 조건을 드러내어 다시 판단한다.

마지막으로 컨텍스트를 나눴다는 사실만으로 통합을 끝내지 않는다. 예약번호와 결제 대상의 관계, 취소 사실과 환불 결과의 차이, 알 수 없는 결과를 누가 확인할지가 남는다. 미정인 요구를 성공으로 가정하면 경계 그림이 간단해 보여도 설계는 완결되지 않는다.

독립 연습 — 응답이 없다는 사실을 어느 경계에서 해석하는가

다음은 본문 사례의 조건을 적용하거나 바꾼 자체 연습이다. 먼저 판단과 근거를 적고 풀이를 펼친다.

본문의 예약 루트 설계에서 R501의 상태와 취소 결과 한 개를 같은 트랜잭션으로 확정했다. 고객 응답만 유실되었고 외부 환불 결과는 아직 조회하지 않았다. 본인이 같은 취소를 다시 요청했을 때의 결정과, 화면에 “환불 완료”를 표시할 수 있는지 판단하라.

  • 예약 상태·결과 수·반환할 결과를 쓰라.
  • 루트의 규칙, 저장 확정, 다른 모델의 완료 의미 중 각 결론의 근거를 골라 설명하라.
풀이와 오답 교정 보기

예약은 취소이고 결과는 한 개다. 재요청에는 기존 결과를 반환하며 결과를 추가하지 않는다. 응답 유실은 이미 함께 확정한 두 값을 실패 상태로 되돌리지 않는다. 그러나 예약의 취소 확정은 결제 정산 모델의 환불 완료가 아니므로, 미조회 상태에서 환불 완료를 표시할 수 없다. 루트는 규칙의 책임, 트랜잭션은 실행의 확정 범위, 컨텍스트는 완료라는 말의 적용 범위를 각각 설명한다.

결과를 두 개로 만들었다면 확정 전후의 실패, 환불까지 완료로 썼다면 두 모델의 완료, 루트만으로 원자성이 생긴다고 썼다면 모델과 실행 경계에서 고친다.

10점으로 연결하기: 같은 대상과 같은 값을 구별한다

도메인 설계에서 엔터티와 값 객체를 비교하고, 공방 예약의 취소 기준과 애그리게이트에 적용하여 설명하시오.

이 문제는 이론을 답안으로 옮기기 위한 자체 작성 문제이며 실제 기출 인용이 아니다. ‘비교’에는 구별 기준과 반례가 필요하고, ‘적용’에는 R501과 시간 기준값, 함께 지킬 조건이 필요하다. 번호가 있으면 엔터티라는 한 줄이나 객체 목록만으로 끝내지 않는다.

10점 풀이와 답안 구성 보기

10점 답안 목차

  1. 식별성의 지속으로 구별하는 엔터티와 의미 있는 값으로 구별하는 값 객체를 정의한다.
  2. R501의 상태 변화, 시간 기준값의 동등성·불변 처리·기한 계산으로 비교한다.
  3. 예약을 루트로 상태와 취소 결과의 조건을 함께 지키고, 실행의 원자성·충돌 통제를 별도로 연결한다.

답안 비교표: 동일성 판단에서 변경 책임까지

비교와 책임읽을 연결
엔터티의 지속 식별성R501 확정 → R501 취소: 같은 예약의 상태 변화
값의 동등성(24시간, 포함) = (24시간, 포함): 같은 의미의 값
변경 책임과 실행 경계예약 루트가 상태와 취소 결과를 함께 보호하고, 실행에서는 함께 확정할 경계를 맞춤

표는 식별성이 이어지는 대상과 값의 동등성을 구분하고, 그 대상이 지킬 상태 조건을 함께 바꾸는 책임으로 연결한다.

10점 모범답안

엔터티는 속성이 변해도 같은 업무 대상으로 추적하는 객체이며, 값 객체는 독립적인 식별성 대신 의미 있는 값과 규칙으로 구별하는 객체다. R501은 확정에서 취소로 바뀌어도 같은 예약이지만 R502와는 별도 예약이다. 같은 수업을 가리킨다는 이유로 합치지 않는다.

시간 기준값 (24시간, 경계 포함)은 같은 두 속성이면 동등하며, 12시간 기준과는 다르다. 값 객체는 불변으로 다루고 새 값이 필요하면 기존 내부를 고치지 않고 다른 값을 만든다. W101의 9월 20일 14시에서 24시간을 빼면 v1 마감은 9월 19일 14시다. 이는 시간 조건의 계산이지 예약 소유권이나 환불 완료의 판단은 아니다.

예약을 애그리게이트 루트로 두는 대안에서는 상태와 취소 결과를 함께 책임진다. 정상 취소는 (확정, 결과 0개)에서 (취소, 결과 1개)로 바뀌고 반복 요청은 기존 결과를 반환한다. 외부에서 내부 상태를 직접 변경하지 못하게 막고 함께 저장을 확정해야 한다. 루트라는 모델 이름만으로 원자성·격리성이 생기지 않으므로 중간 실패와 동시 요청에서도 결과가 하나만 남도록 트랜잭션과 충돌 통제를 연결한다.

25점으로 연결하기: 규칙을 지킬 경계를 논증한다

공방 예약 취소를 도메인 모델로 설계하려 한다. 업무 언어, 엔터티와 값 객체, 책임과 애그리게이트·트랜잭션 경계, 바운디드 컨텍스트를 연결하여 설계 방안을 제시하고, 정원과 환불을 포함한 경계 밖 조건 및 검증 방안을 설명하시오.

이것도 자체 작성 문제다. 핵심은 패턴 이름의 나열이 아니라 같은 규칙을 어디에서 어떤 범위로 지키고, 무엇이 아직 확인되지 않았는지 설명하는 데 있다. 정원·환불의 전체 구현을 임의로 발명하지 말고 본문에서 드러낸 요구와 경계의 관계로 답한다.

25점 풀이와 답안 구성 보기

25점 답안 목차

  1. 업무 전문가와 적용 범위의 언어를 맞추고, 예약·수업·정책·취소의 개념과 규칙을 모델에 남긴다.
  2. 예약의 지속 식별성과 시간 기준값의 동등성·불변 처리를 구분하여 적용 정책과 마감 계산을 보존한다.
  3. 예약 루트의 업무 판단, 시간값의 계산, 사용 사례 조정과 저장 접근의 책임을 구분한다.
  4. 상태·결과의 불변조건을 애그리게이트와 트랜잭션 경계에 연결하고, 정원 8명의 경계 밖 반례와 크기 비용을 검토한다.
  5. 예약 취소와 환불 완료의 의미를 컨텍스트별로 나누고 식별값·입력·변환 책임을 접점 계약에 명시한다.
  6. 마감·권한·반복·중간 실패·동시 요청을 대조하고, 미정인 자리 반환·환불 결과를 완료로 가장하지 않는다.

실제 도식을 연결한 답안 논증 순서

  1. 사례·문장·모델의 피드백에서 예약·적용 정책·취소의 업무 언어를 맞춘다.
  2. 예약 엔터티와 시간 기준값을 구별하고 예약 루트의 규칙 경계에서 상태·결과를 함께 보호한다. 정원 조건은 별도로 점검한다.
  3. 저장 확정·충돌 통제로 예약 취소 확정 사실을 보호한다.
  4. 두 컨텍스트의 접점에서 식별값·뜻의 변환 계약을 확인하고 결제 정산의 환불 판단·결과 확인을 구별한다.

이 순서는 실제 네트워크 호출 순서가 아니라 설계 논증의 순서다. 예약의 변경을 지킨다는 주장과 결제 정산까지 완료되었다는 주장은 구분해서 적는다.

25점 모범답안

1. 업무 언어와 모델의 연결

도메인 모델은 선택한 업무 문제를 설명하고 해결할 개념·관계·규칙을 정리한 모델이다. 업무 전문가와 개발자는 같은 적용 범위에서 유비쿼터스 언어를 사용하고 대화·문서·그림·코드의 뜻을 맞춘다. 공방은 수업 W101과 예약 R501·R502, 예약별 적용 정책을 구분한다. 취소는 예약의 상태 변경이며 환불 완료와 같지 않다. 용어를 바꾸면 관련 규칙·설계·검증 사례도 함께 대조한다.

2. 식별성과 값의 구별

예약은 상태가 달라져도 같은 신청으로 추적하는 엔터티다. 시간 기준은 기간과 경계 포함 여부로 비교하는 값 객체로 두고 불변으로 취급한다. R501의 v1 마감은 9월 19일 14시이며 R502의 v2 마감은 9월 20일 02시다. 같은 W101의 예약이라고 정책을 현재 버전으로 덮어쓰지 않는다. 시간값의 계산만으로 다른 고객의 취소 권한까지 판단하지 않는다.

3. 책임과 변경 입구

예약 루트는 소유자·상태·적용 정책으로 취소를 판단하고 시간값은 시간 계산을 맡는다. 애플리케이션 서비스는 사용 사례를 조정하고 리포지터리는 저장 접근을 감추며, 업무 판단을 중복 구현하지 않는다. 엔터티나 값 객체가 자연스럽게 맡기 어려운 중요한 업무 연산일 때만 도메인 서비스를 검토한다. 외부에서 내부 상태를 직접 변경하면 루트 규칙을 우회하게 된다.

4. 함께 지킬 조건과 실행 경계

이 대안의 조건은 확정이면 취소 결과 0개, 취소이면 결과 1개다. 상태와 결과를 한 애그리게이트가 책임지고 저장도 같은 트랜잭션에서 함께 확정한다. 루트 호출만으로 원자성·격리성이 확보되는 것은 아니므로 실패 시 함께 취소하고 동시 요청의 중복 결과를 통제한다. 정원 8명인 수업에서 7명이 자리를 점유한 상태에서 두 예약이 각각 허용되면 9명이 되어 개별 예약만으로 정원을 지킬 수 없다. 자리 배정을 책임질 범위를 찾고, 취소와 자리 반환의 즉시 일치를 요구한다면 경계를 다시 검토한다. 모든 이력을 크게 묶는 충돌 비용과 무조건 쪼개는 조건 누락을 함께 비교한다.

5. 모델의 의미 경계와 접점

바운디드 컨텍스트는 특정 모델과 언어가 일관되게 적용되는 범위다. 예약 운영의 취소 확정 사실은 결제 정산의 환불 판단에 필요한 입력일 수 있지만 환불 성공 자체가 아니다. 컨텍스트 맵에서 예약번호와 결제 대상의 연결, 넘길 사실의 뜻, 변환·확인 책임을 명시한다. 한 컨텍스트가 반드시 한 서버이고 한 애그리게이트가 반드시 한 표라는 가정은 하지 않는다.

6. 검증과 남은 조건

R501은 마감과 같으면 허용하고 1초 뒤면 거부하며, 본인의 이미 취소된 예약은 늦게 다시 요청해도 기존 결과를 반환한다. 타인 예약은 먼저 거부한다. 저장 전 실패와 커밋 후 응답 유실을 구별하고 겹친 요청에서도 결과가 하나만 남는지 검토한다. 자리 반환의 일치 시점, 환불 규칙과 실제 처리 결과는 미정 사항을 명시해 후속 요구·설계·시험에 연결하며, 이 설계 표를 실제 실행이나 승인 증거로 표시하지 않는다.

다음 회독에서도 같은 네 질문을 따라간다

업무의 말이 같은 뜻인가, 대상의 식별성과 값이 구별되는가, 함께 지킬 조건을 누가 책임지는가, 그 모델이 어디까지 통하는가를 차례로 읽는다. 도메인 모델은 표와 클래스의 이름만이 아니라 규칙을 포함한다. 애그리게이트는 일관성을 책임질 묶음, 트랜잭션은 변경을 확정할 실행 범위, 바운디드 컨텍스트는 모델의 의미가 통하는 범위다.

공방에서 배운 핵심은 경계를 그려 놓고 밖의 조건을 잊지 않는 것이다. 정원과 환불처럼 다른 대상·모델에 걸친 약속은 따로 드러내야 하며, 작은 그림이나 적은 클래스 수를 얻으려고 필요한 이론과 요구를 삭제하지 않는다.

근거와 적용 범위

이 절은 2026년 9월 5일 확인한 Eric Evans의 DDD Reference: Definitions and Pattern Summaries, 2015년 판의 정의(인쇄 vi), Bounded Context(2), Ubiquitous Language(3~4), Model-Driven Design(6), Layered Architecture(10), Entities(11), Value Objects(12), Services(14), Aggregates(16), Repositories(17), Assertions(22), Context Map(29)를 주된 이론 근거로 사용한다. 인쇄 1쪽은 PDF 8쪽이므로 두 쪽수를 구별했다. 이 문서는 표준 적합성 인증서가 아니라 원저자의 패턴·정의 요약이다.

해당 Reference는 Eric Evans 저작이며 CC BY 4.0으로 공개되어 있다. 저자 배포 페이지와 PDF 표지에서 권리표시를 확인했다. 이 절의 한국어 설명은 학습용으로 새로 풀어 쓴 것이며, 공방 표·도식·반례·문제·답안은 원문 예시나 그림을 복제하지 않은 자체 구성이다.

보조 비교에는 Martin Fowler의 Domain Model, Transaction Script, Bounded Context 원저자 공개 설명을 사용한다. 요청별 절차 구성도 선택지라는 설명과 의미 경계의 구별에 한정하며 원문 도식은 가져오지 않았다. 실행 조정 역할은 같은 저서의 공동 저자 Randy Stafford가 쓴 Service Layer의 응답·트랜잭션 조정 설명을 함께 대조했다. 이 역할 구별을 특정 서비스 클래스나 배포의 의무로 확대하지 않는다.

일반 트랜잭션·캡슐화·불변조건과 앞 절의 모델 표기 정의는 각각의 앞선 담당 절을 참조한다. 불변조건의 관찰 지점은 Cornell CS 2110 Spring 2026의 Classes and Encapsulation에서 Class Invariant와 Maintaining Class Invariants를 확인하며, 그 교재의 코드나 그림은 복제하지 않았다. 특정 제품의 최신 기능, 분산 처리의 완전한 구현, 실제 공방의 승인 정책을 검증한 자료로 확대하지 않는다.

직접 연습: 값을 나누고, 확정의 순서를 따지고, 접점에서 모르는 것을 남긴다.

아래는 본문의 구별을 직접 적용하기 위한 자체 조건이다. 예약과 결제의 실제 구현을 완성했다고 가정하지 말고, 주어진 정보로 정할 수 있는 값과 추가 확인이 필요한 판단을 나누어 적는다.

  1. DM-V. W101은 9월 20일 14시에 시작한다. A 예약은 24시간 기준, B 예약은 12시간 기준이어야 한다. 잘못된 구현에서 두 예약이 같은 변경 가능한 시간값 24를 참조하다가 B의 기준을 정하려고 그 값을 12로 고쳤다. A의 마감은 얼마나 이동하는가? A와 B가 각각 불변의 24시간·12시간 값을 가진 올바른 대안에서 9월 19일 20시 요청의 시간 조건은 각각 통과하는가? 이 비교로 예약번호 A와 B까지 같은 예약이 되는가?
  2. DM-T. (확정, 결과 0개)를 두 취소 요청이 모두 읽고, 각각 K1·K2라는 새 결과를 추가하여 둘 다 따로 확정했다고 가정한다. 마지막 상태·결과 수는 어떤 조건을 어기는가? 비교 대안에서는 첫 요청의 검사·상태·결과 저장을 함께 확정하고, 두 번째 요청은 그 확정된 상태를 읽는다고 정한다. 두 번째 요청은 무엇을 반환하며 결과 수는 얼마인가? 첫 요청의 결과 저장이 커밋 전에 실패한 경우와, 함께 커밋한 뒤 응답만 유실된 경우도 구별하라.
  3. DM-C. 예약 취소 사실 세 건을 받았다. 예약 R601은 연결된 결제 대상이 없고, R602는 P10·P11 두 대상으로 연결되며, R603은 P12 하나로 연결된다. R603에 대해서는 별도 연습 조건으로 P12의 결제 확인액이 60,000원, 이미 반환을 확인한 금액이 20,000원이고 남은 금액 전체를 환불 요청할 수 있다고 정한다. 외부에서 새 환불의 완료 결과는 아직 받지 않았다. R601·R602에서 무엇을 더 확인해야 하는가? R603의 새 요청액과 지금 표시할 수 없는 완료 상태는 무엇인가? 예약번호가 결제번호와 같은 글자라는 사실만으로 접점의 확인을 생략해도 되는가?
공유 값 변경, 두 취소의 확정, 결제 접점의 조건 풀이

1. DM-V — 같은 값의 공유와 서로 다른 기준의 적용을 구별한다. 잘못된 공유 값 변경은 A까지 12시간 기준으로 만들어 마감을 9월 19일 14시에서 9월 20일 02시로 12시간 늦춘다. 9월 19일 20시는 올바른 A의 마감보다 6시간 뒤이므로 시간 조건을 통과하지 못한다. B의 마감보다 6시간 전이므로 B의 시간 조건은 통과한다. 두 불변 값을 따로 적용하면 A의 기존 약속을 고치지 않고 B의 다른 기준을 표현할 수 있다. 이는 시간 조건만의 비교이며 소유권·예약 상태는 별도 판단한다. 값이 같거나 같은 수업을 가리켜도 A와 B라는 별도 예약의 식별성을 합치지 않는다.

2. DM-T — 판단을 한곳에 둔 것과 겹친 변경을 통제한 것은 다르다. 잘못된 대안의 마지막은 (취소, 결과 2개)로, 취소에는 최초 확정 결과가 정확히 하나 있어야 한다는 조건을 어긴다. 비교 대안에서는 첫 요청이 (취소, 결과 1개)를 함께 확정한다. 두 번째는 이미 취소된 본인 예약의 K1을 반환하고 K2를 추가하지 않으므로 결과는 1개다. 이는 두 번째가 첫 확정 상태를 읽도록 정한 조건의 결과다. 실제 겹친 실행에서도 그렇게 읽고 확정하게 할 충돌 통제는 별도로 구현해야 한다.

커밋 전 실패에는 두 저장을 함께 취소하여 (확정, 결과 0개)가 남아야 한다. 커밋 뒤 응답 유실에는 (취소, 결과 1개)가 남고 본인 재요청은 기존 결과를 받는다. 응답이 없다는 관측만으로 두 경우를 같은 시작 상태로 취급하면 안 된다. 이런 상태 추적은 지킬 실행 계약을 설명할 뿐, 실제 데이터베이스의 원자성·격리성을 검사한 결과는 아니다.

3. DM-C — 취소 사실을 입력으로 받되 결제 쪽의 식별성과 완료를 다시 확인한다. R601에는 어떤 결제가 대응하는지부터 확인해야 한다. R602에는 두 결제 각각의 관계와 금액·처리 범위를 확인해야 하며 P10 하나를 임의로 골라 전체 환불로 처리하지 않는다. R603은 문제에서 연결과 별도 환불 조건을 주었으므로 새 요청액을 60,000−20,000=40,000원으로 계산할 수 있다. 그렇더라도 새 환불의 외부 완료 결과가 없으므로 지금 ‘환불 완료’라고 표시할 근거는 없다.

이 환불 계산 조건은 연습을 위해 따로 준 것이며 본문 공방의 미정 환불 규칙을 승인한 것이 아니다. 서로 다른 식별 체계에서 글자가 우연히 같아도 대응 관계가 확인된 것은 아니다. 접점은 식별값의 연결, 입력의 뜻, 이미 확인한 결과와 새 요청의 차이를 명시해야 한다. 예약 취소 확정과 환불 요청액을 안다는 사실이 외부 환불 성공을 대신하지 않는다.

이 페이지 목차

이 페이지 목차

취소 그림이 맞아도 규칙의 주인이 없으면 다시 틀린다쉽게 말하면: 약속을 이해할 이름과 지킬 자리를 함께 만든다모델의 중심을 컴퓨터가 아니라 해결할 업무에 둔다같은 공방의 사실을 고정하고 시작한다도메인 주도 설계는 패턴 이름보다 함께 이해하는 일에서 출발한다용어집 한 장이 아니라 같은 판단을 만드는 언어다같은 사례를 오가며 말을 다듬는다클래스 그림·저장 표·도메인 모델은 같은 말이 아니다같은 대상인가, 같은 값인가예약번호가 하는 일과 시간값이 하는 일이 다르다값을 고쳐 쓰지 않고 다른 값으로 바꾸는 이유값 객체에도 계산과 거부 조건이 있다번호가 있으면 엔터티이고 번호가 없으면 값이라는 암기는 부족하다어떤 규칙을 누가 책임져야 할까바뀌지 않아야 하는 것은 상태값이 아니라 약속이다취소를 판단하는 길을 한곳으로 모은다함께 지킬 묶음과 그 묶음의 입구를 정한다관계가 있다는 것과 내부 구성원이라는 것은 다르다애그리게이트는 모델의 경계이고 트랜잭션은 실행의 경계다정상·실패·반복을 같은 두 값으로 읽는다정원 8명은 예약 하나만 보아서는 지킬 수 없다무조건 크게도, 무조건 작게도 묶지 않는다업무 판단과 실행 순서의 조정을 구분한다저장 기술의 이름이 업무 모델을 대신하지 않게 한다같은 말이 통하는 범위를 정한다예약 운영의 완료와 결제 정산의 완료경계를 넘으면 뜻을 옮기는 약속이 필요하다세 가지 경계는 크기만 다른 같은 상자가 아니다작은 모델로 반례를 설명할 수 있는지 본다처음의 두 예약을 끝까지 같은 뜻으로 읽는다좋은 이름만으로 가려지지 않는 다섯 실패독립 연습 — 응답이 없다는 사실을 어느 경계에서 해석하는가10점으로 연결하기: 같은 대상과 같은 값을 구별한다10점 답안 목차답안 비교표: 동일성 판단에서 변경 책임까지10점 모범답안25점으로 연결하기: 규칙을 지킬 경계를 논증한다25점 답안 목차실제 도식을 연결한 답안 논증 순서25점 모범답안다음 회독에서도 같은 네 질문을 따라간다근거와 적용 범위