
합타입은 "여러 타입 중 하나"를 의미하며, 외부에서는 내부의 복잡성을 모른 채 상위 타입으로 다룰 수 있어야 합니다.
직렬화 시 핵심은 "이 JSON이 어떤 서브타입으로 변환되어야 하는가?"를 판정하는 전략입니다.
{"fruit": "APPLE"}Fruit.values().find { it.name == json.fruit }sealed interface Shape {
data class Circle(val radius: Double) : Shape
data class Rect(val width: Int, val height: Int) : Shape
}
// {"radius": 10} -> Circle로 판정 (radius 키 존재 여부)
{"type": "A", "a": 1} {"A": {"a": 1}}JSON Schema의 oneOf만으로는 어떤 스키마를 선택할지 모호함이 발생할 수 있습니다.
이를 해결하기 위해 표준 스키마와 라이브러리들은 Discriminator를 필수로 요구하는 추세입니다.
discriminator 속성을 통해 필드명과 매핑을 명시합니다.kotlinx.serialization: @SerialName과 classDiscriminator 설정을 통해 명시적 식별자 강제.Jackson: Id.DEDUCTION 옵션을 통해 키 일치 방식 지원 가능하나, 성능과 명확성을 위해 식별자 권장.Moshi: PolymorphicJsonAdapterFactory를 통한 식별자 기반 처리.제네릭은 런타임에 타입 정보가 사라지는 Type Erasure 특성 때문에 직렬화 시 "타입 파라미터가 무엇인지" 알려주는 메커니즘이 필수적입니다.
제네릭의 가짓수가 무한하면 직렬화가 불가능합니다.
따라서 타입 상한을 설정하여 범위를 제한하고, 이를 합타입처럼 취급하여 해결합니다.
sealed interface Animal
data class Dog(val barkVolume: Int) : Animal
data class Cat(val lifeLeft: Int) : Animal
// T를 Animal로 제한하여 직렬화 가능 범위를 확정
class Box<T : Animal>(val data: T)
{
"definitions": {
"Animal": {
"oneOf": [{ "$ref": "#/definitions/Dog" }, { "$ref": "#/definitions/Cat" }],
"discriminator": { "propertyName": "type" }
},
"Fruit": {
"oneOf": [{ "$ref": "#/definitions/Apple" }, { "$ref": "#/definitions/Banana" }],
"discriminator": { "propertyName": "fruitType" }
}
},
"properties": {
"animal": { "$ref": "#/definitions/Animal" },
"fruit": { "$ref": "#/definitions/Fruit" }
}
}
모든 복잡한 객체는 결국 중복 키가 없는 Map<String, Any?>로 변환될 수 있습니다.
직렬화는 객체를 Map으로 바꾸는 과정(toMap), 역직렬화는 Map을 객체로 복구하는 과정(fromMap)입니다.
메타데이터 포함: toMap 과정에서 역직렬화 힌트인 클래스명, 타입 코드 등를 추가하여 정보 손실을 막습니다.
함수 시그니처의 정형화:
fun <T> T.toMap(serializer: Serializer<T>): Map<String, Any?>
fun <T> Map<String, Any?>.toObject(deserializer: Deserializer<T>): T
DDD에서 애그리것은 데이터 변경의 단위입니다.
애그리것 루트를 제외한 내부 구성원들을 굳이 RDB 테이블로 파편화할 필요가 없다는 것이 현대적 모델링의 핵심입니다.
애그리것 전체는 하나의 트랜잭션 내에서 정합성을 유지해야 합니다.
JSON 저장 방식을 선택했을 때 발생할 수 있는 문제와 해결책입니다.
| 문제점 | 원인 | 해결책 |
|---|---|---|
| 성능 저하 | 한 애그리것 내 아이템(예: 주문 내역)이 수천 건인 경우 | 애그리것 분리 Member와 Order를 분리 |
| 데이터 비대화 | 주문 1건에 수만 개의 상품 상세 정보 포함 | 상품 애그리것 분리 후 ID 참조만 유지 |
| 검색/필터링 | JSON 내부 필드로 검색이 필요한 경우 | CQRS 적용: 검색용 전용 테이블을 별도 생성 |
애그리것이 JSON으로 저장되어 검색이 어렵다면, 상태 변경 시 도메인 이벤트를 발행합니다. 이 이벤트를 구독하여 RDB의 플랫한 테이블을 업데이트하면, 쓰기 효율과 읽기 효율을 모두 잡을 수 있습니다.