필드와 타입
필드는 한 줄입니다: 이름: 타입[(크기)][!] [@annotations] -- 라벨.
값 타입 한눈에
필드에 쓸 수 있는 값 타입 전부와, 생성되는 Java 타입, JSON 모양, TypeScript 타입, Apache Ossie 로 내보낼 때의 DataType.
| DSL 타입 | Java | JSON | TS | Ossie | 비고 |
|---|---|---|---|---|---|
string / string(n) | String | "text" | string | String | n = 최대 길이, ! = NOT NULL |
text / longtext | String | "text" | string | String | columnDefinition = "TEXT" / "LONGTEXT" |
clob | String | "text" | string | String | @Lob |
json | String | "{...}" (문자열) | string | Opaque | JSON 컬럼 |
int / Integer | Integer | 42 | number | Integer | |
long / Long | Long | 42 | number | Integer | |
biginteger | BigInteger | 123456789012345678901 | number | Integer | |
quantity | Quantity | 5 | number | Integer | 3.0 값 타입: 음수 없는 개수 |
decimal(p.s) / bigdecimal(p.s) | BigDecimal | 12.34 | number | Decimal | precision, scale |
money / money(p.s) | Money | 99.95 | number | Decimal | 3.0 값 타입: 금액, 기본 (19,2) |
float / double | Float / Double | 1.5 | number | Float | 근사값 - 금액에 쓰지 마세요 |
boolean | Boolean | true | boolean | Boolean | |
date | LocalDate | "2026-10-03" | string | Date | |
time | LocalTime | "09:30:00" | string | Time | |
datetime | LocalDateTime | "2026-10-03 09:30:00" | string | DateTime | 시간대 없음. ISO 의 T 가 아님 |
timestamp | java.time.Instant | "2026-10-03T00:30:00Z" | string | DateTimeTz | 한 순간. UTC 로 돌려줌 |
blob | byte[] | "AQID/w==" (base64) | string | Opaque | @Lob |
EnumName | 그 enum | 코드 ("PLC") | enum | String | @db(code) 의 값이 저장됨 |
EntityName | 참조 | (식별자 필드) | - | 관계 | 기본 @ManyToOne |
- 어느 시간 타입을 쓰나: 사람이 보는 날짜·시각(영업일, 예약 시각)은
date·datetime, "언제 일어났나"(결제 승인, 이벤트 발생, 감사)는timestamp. - 표에 없는 타입은 쓰지 않습니다:
uuid는string(36)(UUID 키는string @id @genid),short·byte는int,char는string(1),OffsetDateTime·ZonedDateTime은timestamp. - 컬렉션:
List<T>,Set<T>,Map<K,V>, 배열int[],string[]. - 표는 3.0 규칙 엔진 기준이고, 모든 타입을
elasticore-template-ddd의TypeRoundTripTest가 생성·저장·HTTP 왕복으로 확인합니다.
money 와 quantity
금액과 수량을 값 객체(io.elasticore.springboot3.domain)로 다룹니다. 컬럼과 JSON 은 그대로 decimal·integer 이고, 그
사이의 코드가 Money·Quantity 를 씁니다.
SalesOrderLine:
fields:
qty: quantity! @min(1)
unitPrice: money(12.2)! @min(0.01, '단가는 0보다 커야 한다')
amount: money(14.2)
line.setAmount(line.getUnitPrice().times(line.getQty()));
total = total.plus(line.getAmount());
stock.setOnHand(stock.getOnHand().minus(qty)); // 모자라면 422
Money: plus, minus, times(Quantity / long / BigDecimal), rounded(n)(반올림), isNegative - 10 과 10.00 은 같은
금액. Quantity 는 음수가 없습니다(0 밑으로 minus 하면 422 not enough). @default(0) 은 Money.of("0") /
Quantity.of(0) 이 됩니다. 부호 있는 변화량(입고 +, 출고 -)은 수량이 아니므로 int 로 둡니다.
키
| 쓰는 것 | 뜻 |
|---|---|
id: long @id @sequence | DB 가 번호를 매김. 생성할 때 보내지 않음 |
id: string @id @genid | UUID (UUID.randomUUID()) |
code: string(15) @id @genid | IdUtils.newId(15): 3자 접두어 + 영숫자 12자 |
id: string @id @genid(com.acme.Ids.next) | 직접 만든 정적 메서드 |
isbn: string(13)! @id | 클라이언트가 보내는 키. 이미 있는 키로 생성하면 409 |
검증
| 쓰는 것 | 뜻 |
|---|---|
string(100)! | 필수, 100자까지 - "" 은 통과 |
string(100)! @notblank | 필수, 빈 값·공백만 있는 값 거절 |
string(20)! @length(min=4) | 4~20자 |
string(100) @email | 이메일 형식 |
int @min(1) @max(10) | 1~10 정수 (정수 필드에는 정수 값만, decimal·money 필드는 @DecimalMin/@DecimalMax) |
money(10.2)! @min(0.01, 'price must be positive') | 최솟값과 메시지 - @DecimalMin |
date @past, date @future | 오늘 이전 / 이후 (@pastorpresent, @futureorpresent 는 오늘 포함) |
string(13)! @pattern('^[0-9]{13}$', 'ISBN must be 13 digits') | 값 전체가 정규식과 맞아야 함. 아니면 그 메시지로 400 |
lines: List<OrderLine> ... @minsize(1, 'an order needs a line') | 멤버 n개 이상. @maxsize(n, '...') 는 이하 |
@pattern: @pattern(regexp="...", message="...") 형식도 됩니다. 메시지를 생략하면 서버와 클라이언트 모두
"<라벨> is not in the expected form" 입니다. 백슬래시는 두 번(\\d) 쓰거나 [0-9] 같은 문자 클래스를 씁니다 -
\d 하나면 DSL 경고. DTO 와 TypeScript 클라이언트에만 들어갑니다(엔티티·검색 DTO 에는 없음). 날짜 필드의
@pattern(yyyyMMdd) 는 지금처럼 날짜 형식입니다.
@minsize(n) 은 n 이 1 이상이면 @NotNull 도 붙습니다: 빈 목록도, 목록을 아예 보내지 않아도 400 이고,
TypeScript 의 validate...() 도 같은 것을 묻습니다. DDD 번들에서는 루트가 저장 시점에도 검사합니다(422).
서버가 정하는 값
| 쓰는 것 | 뜻 |
|---|---|
joinedAt: date @readonly | 응답에는 나가고 요청으로는 받지 않으며 필수가 아님 - 내 코드(beforeSave)가 채움 |
grade: string(10)! @noinput | 같은 뜻(@noinput 은 별칭). 컬럼은 NOT NULL 그대로라 서버가 채워야 함 |
orderedAt: datetime @readonly @default(now) | 행을 만들 때의 시각 - 요청 값 무시, 수정해도 그대로 |
joinedOn: date @readonly @default(now) | 오늘 |
createdAt: datetime @updatable(false) @jpa:org.springframework.data.annotation.CreatedDate | 감사 필드 - @EnableJpaAuditing 필요 |
modifiedAt: datetime @jpa:org.springframework.data.annotation.LastModifiedDate | 저장할 때마다 바뀌는 값 (감사) |
v3 번들에서 @readonly 는:
- DTO 의
@NotNull·@NotBlank를 빼고(요청이 보낼 수 없는 값이므로)@Schema(accessMode = READ_ONLY)를 더합니다. - 매퍼가 DTO 에서 엔티티로 쓰지 않습니다. 서버 코드가 DTO 에 값을 넣고 저장하던 코드(
dto.setGrade("A")후service.save(dto))는 그 값이 저장되지 않습니다 - 엔티티에 넣으세요(beforeSave, 루트의 메서드). - 엔티티 필드에 붙이면 그 엔티티의 모든 DTO 에,
dto.yml의 DTO 필드에 붙이면 그 DTO 에만 적용됩니다.
@default(now) 는 datetime 이면 LocalDateTime.now(), date·time·timestamp(Instant.now())도 now 를
받습니다 - 엔티티의 초기값이고 DTO 의 기본값이 아닙니다. @createtime 은 없습니다.
그 밖의 필드 어노테이션
| 쓰는 것 | 뜻 |
|---|---|
@default(값) | 기본값: @default("ACTIVE"), @default(true), @default(UserRole.USER) |
@updatable(false) | @Column(updatable = false). DDD 번들의 참조에 붙이면 ID 필드가 물려받아 수정해도 저장된 값이 남음 |
@writeonly | 요청으로 받고 응답에는 내보내지 않음 (카드 번호) |
@password | 해시로만 저장 - 권한과 로그인 |
@access(admin) | 관리자만 쓰는 필드 - 권한과 로그인 |
@search(...) | 검색 조건 - 검색과 정렬 |
@formula("sql") | DB 가 계산하는 컬럼 (Hibernate) |
@Column(name="col", length=n) / @column(name) | 컬럼 매핑 |
@jpa:<어노테이션> | JPA·Spring Data 어노테이션을 그대로 |
@env:이름 | env.yml 의 config.annotations 에 정의한 어노테이션 |
@terms(price, 가격) | Ossie 로 나가는 동의어 |