본문으로 건너뛰기

Entity 스펙 스키마

이 페이지는 Fascia의 Entity(비즈니스 객체) 스펙에 대한 전체 JSON Schema를 정의합니다. Entity는 Fascia의 핵심 비즈니스 객체 추상화로, 필드, 관계, 상태 머신(Status Machine), 불변식(Invariant)을 캡슐화합니다. 데이터베이스 저장 모델(테이블, 컬럼, 인덱스)은 Entity 스펙에서 자동으로 도출됩니다.

JSON Schema

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"required": ["name", "version", "description", "fields", "statusMachine"],
"properties": {
"name": {
"type": "string",
"pattern": "^[A-Z][a-zA-Z0-9]*$",
"description": "PascalCase entity name"
},
"version": {
"type": "integer",
"minimum": 1
},
"description": {
"type": "string"
},
"fields": {
"type": "object",
"minProperties": 1,
"additionalProperties": { "$ref": "#/$defs/Field" }
},
"relationships": {
"type": "object",
"additionalProperties": { "$ref": "#/$defs/Relationship" }
},
"statusMachine": { "$ref": "#/$defs/StatusMachine" },
"invariants": {
"type": "array",
"items": { "$ref": "#/$defs/Invariant" }
},
"rowLevelAccess": {
"type": "boolean",
"default": false
},
"ownerField": {
"type": "string"
}
}
}

최상위 속성

속성타입필수설명
namestringPascalCase Entity 이름 (예: Reservation, PaymentRecord). ^[A-Z][a-zA-Z0-9]*$ 패턴을 따라야 합니다.
versioninteger불변 버전 번호. 1부터 시작하며, 변경 시마다 증가합니다.
descriptionstringEntity의 목적에 대한 사람이 읽을 수 있는 설명입니다.
fieldsobjectEntity에 정의된 사용자 필드입니다. 최소 하나의 필드가 필요합니다. Field 참조.
relationshipsobject아니오다른 Entity와의 관계를 정의합니다. Relationship 참조.
statusMachineobject허용된 상태와 전이를 정의하는 상태 머신입니다. StatusMachine 참조.
invariantsarray아니오항상 참이어야 하는 비즈니스 규칙입니다. Invariant 참조.
rowLevelAccessboolean아니오true로 설정 시, 관리자가 아닌 사용자는 자신이 소유한 행에만 접근할 수 있습니다. 기본값은 false입니다.
ownerFieldstring아니오소유자 User Entity를 참조하는 필드 이름입니다. rowLevelAccesstrue일 때 필수입니다.

정의

Field

fields 객체의 각 항목은 Entity의 타입이 지정된 데이터 속성을 정의합니다.

{
"type": "object",
"required": ["type"],
"properties": {
"type": {
"enum": ["string", "number", "boolean", "date", "datetime", "enum", "uuid", "json", "reference"]
},
"required": { "type": "boolean", "default": false },
"unique": { "type": "boolean", "default": false },
"indexed": { "type": "boolean", "default": false },
"default": {},
"enumValues": {
"type": "array",
"items": { "type": "string" },
"description": "Required when type is 'enum'"
},
"referenceTo": {
"type": "string",
"description": "Target entity name. Required when type is 'reference'."
},
"description": { "type": "string" }
}
}
속성타입필수설명
typeenumstring, number, boolean, date, datetime, enum, uuid, json, reference 중 하나입니다.
requiredboolean아니오해당 필드에 값이 반드시 있어야 하는지 여부입니다. 기본값은 false입니다.
uniqueboolean아니오유니크 제약 조건 적용 여부입니다. 기본값은 false입니다.
indexedboolean아니오쿼리 성능을 위한 인덱스 적용 여부입니다. 기본값은 false입니다.
defaultany아니오생성 시 필드 값이 제공되지 않을 때 적용되는 기본값입니다.
enumValuesstring[]조건부허용되는 값 목록입니다. type"enum"일 때 필수입니다.
referenceTostring조건부대상 Entity의 PascalCase 이름입니다. type"reference"일 때 필수입니다.
descriptionstring아니오필드에 대한 사람이 읽을 수 있는 설명입니다.

허용되는 필드 타입

타입설명데이터베이스 매핑
string텍스트 데이터TEXT 또는 VARCHAR
number숫자 데이터 (정수 또는 소수)NUMERIC
boolean참/거짓BOOLEAN
date시간 없는 날짜DATE
datetime시간대가 포함된 날짜와 시간TIMESTAMPTZ
enum고정된 문자열 값 집합 중 하나CHECK 제약 조건이 있는 TEXT
uuidUUID 식별자UUID
json임의의 JSON 데이터JSONB
reference다른 Entity에 대한 외래 키UUID (FK 제약 조건)

Relationship

Entity 간의 관계를 정의합니다.

{
"type": "object",
"required": ["type", "target"],
"properties": {
"type": { "enum": ["hasOne", "hasMany", "belongsTo", "manyToMany"] },
"target": { "type": "string", "description": "Target entity name" },
"foreignKey": { "type": "string" },
"through": { "type": "string", "description": "Join table name for manyToMany" }
}
}
속성타입필수설명
typeenumhasOne, hasMany, belongsTo, manyToMany 중 하나입니다.
targetstring관련 Entity의 PascalCase 이름입니다.
foreignKeystring아니오외래 키 컬럼 이름입니다. 생략 시 자동으로 도출됩니다.
throughstring아니오조인 테이블 이름입니다. manyToMany 관계에서 필수입니다.

관계 유형

유형카디널리티외래 키 위치
hasOne1:1대상 Entity에 위치
hasMany1:N대상 Entity에 위치
belongsToN:1현재 Entity에 위치
manyToManyN:M별도의 조인 테이블 (through로 지정)

StatusMachine

모든 Entity는 상태 머신을 정의해야 합니다. 단순한 경우(예: active에서 deleted로)에도 마찬가지입니다. 상태 머신은 허용되는 상태 전이를 제어합니다.

{
"type": "object",
"required": ["states", "initialState", "transitions"],
"properties": {
"states": {
"type": "array",
"items": { "type": "string" },
"minItems": 2
},
"initialState": { "type": "string" },
"transitions": {
"type": "array",
"items": { "$ref": "#/$defs/Transition" },
"minItems": 1
}
}
}
속성타입필수설명
statesstring[]가능한 모든 상태입니다. 최소 2개의 상태가 필요합니다. lowercase_snake 네이밍을 사용합니다 (예: pending, in_progress).
initialStatestring새로 생성된 레코드에 할당되는 상태입니다. states 배열에 포함되어야 합니다.
transitionsTransition[]허용되는 상태 전이 목록입니다. 최소 하나의 전이가 필요합니다.

규칙:

  • 모든 Entity는 상태 머신을 가져야 합니다.
  • 전이는 명시적이어야 합니다 -- 와일드카드 전이는 허용되지 않습니다.
  • initialState는 정의된 states 중 하나여야 합니다.

Transition

상태 머신 내에서 허용되는 상태 변경을 정의합니다.

{
"type": "object",
"required": ["from", "to"],
"properties": {
"from": { "type": "string" },
"to": { "type": "string" },
"guard": {
"type": "string",
"description": "Value DSL boolean expression. Transition only allowed if true."
}
}
}
속성타입필수설명
fromstring출발 상태입니다. states 배열에 포함되어야 합니다.
tostring도착 상태입니다. states 배열에 포함되어야 합니다.
guardstring아니오Value DSL 불리언 표현식입니다. 이 표현식이 true로 평가될 때만 전이가 허용됩니다.

Invariant

Entity에 대해 항상 참이어야 하는 비즈니스 규칙입니다. Invariant(불변식)는 Flow 실행 후, 트랜잭션 커밋 전에 검사됩니다 (Execution Contract의 6단계).

{
"type": "object",
"required": ["name", "expression"],
"properties": {
"name": { "type": "string", "description": "camelCase assertion-style name" },
"expression": { "type": "string", "description": "Value DSL boolean expression" },
"message": { "type": "string", "description": "Error message when violated" }
}
}
속성타입필수설명
namestringcamelCase, 단언문 스타일 이름입니다 (예: orderTotalPositive, endDateAfterStart).
expressionstringValue DSL 불리언 표현식입니다. Entity가 유효하려면 true로 평가되어야 합니다.
messagestring아니오Invariant가 위반되었을 때 반환되는 사람이 읽을 수 있는 에러 메시지입니다.

시스템 필드

모든 Entity는 다음 시스템 필드를 자동으로 포함합니다. fields 객체에 이 필드들을 직접 정의하지 마십시오 -- 플랫폼이 자동으로 주입합니다.

필드타입설명
iduuid기본 키. 생성 시 자동 생성됩니다.
createdAtdatetime생성 시 자동으로 설정되는 타임스탬프입니다.
updatedAtdatetime매 업데이트 시 자동으로 설정되는 타임스탬프입니다.
deletedAtdatetime (nullable)레코드가 소프트 삭제될 때 설정됩니다. 활성 레코드의 경우 null입니다.
versioninteger낙관적 잠금 카운터입니다. 1에서 시작하며, 매 업데이트 시 증가합니다.

예시

완전한 Reservation Entity 스펙입니다:

{
"name": "Reservation",
"version": 1,
"description": "A vehicle reservation made by a customer",
"fields": {
"customerId": {
"type": "reference",
"referenceTo": "Customer",
"required": true,
"indexed": true,
"description": "The customer who made the reservation"
},
"vehicleId": {
"type": "reference",
"referenceTo": "Vehicle",
"required": true,
"description": "The reserved vehicle"
},
"startDate": {
"type": "datetime",
"required": true,
"description": "Reservation start date and time"
},
"endDate": {
"type": "datetime",
"required": true,
"description": "Reservation end date and time"
},
"totalPrice": {
"type": "number",
"required": true,
"description": "Total price for the reservation period"
},
"notes": {
"type": "string",
"description": "Optional notes from the customer"
}
},
"relationships": {
"customer": {
"type": "belongsTo",
"target": "Customer",
"foreignKey": "customerId"
},
"vehicle": {
"type": "belongsTo",
"target": "Vehicle",
"foreignKey": "vehicleId"
},
"payments": {
"type": "hasMany",
"target": "Payment"
}
},
"statusMachine": {
"states": ["pending", "confirmed", "in_progress", "completed", "cancelled"],
"initialState": "pending",
"transitions": [
{ "from": "pending", "to": "confirmed" },
{ "from": "pending", "to": "cancelled" },
{ "from": "confirmed", "to": "in_progress", "guard": "now() >= startDate" },
{ "from": "confirmed", "to": "cancelled" },
{ "from": "in_progress", "to": "completed", "guard": "now() >= endDate" }
]
},
"invariants": [
{
"name": "endDateAfterStart",
"expression": "endDate > startDate",
"message": "End date must be after start date"
},
{
"name": "totalPricePositive",
"expression": "totalPrice > 0",
"message": "Total price must be positive"
}
],
"rowLevelAccess": true,
"ownerField": "customerId"
}

관련 문서