엔티티
entity:
EntityName:
meta: entity [@annotations] -- 라벨
fields:
fieldName: fieldType [@annotations] -- 설명
fieldName: fieldType [@annotations] -- 라벨 | 설명
states: ... # 생명주기 (DDD 번들) - 생명주기 문서 참고
commands: ... # 업무 명령 (DDD) - 생명주기 문서 참고
-- 글 은 라벨(@label(글))이 되고, -- 라벨 | 설명 은 라벨과 설명을 함께 줍니다. 라벨은 컬럼 주석과 API 설명이
됩니다.
meta 줄
| 어노테이션 | 뜻 |
|---|---|
@expose(n) | REST 컨트롤러 + 서비스 + 검색 DTO. n 은 페이지 크기. 이것만으로 생성·조회·검색·페이지·REST |
@service(n) | 위와 같지만 컨트롤러 없음 |
@pageable | 페이징 지원 |
@root | 애그리거트 루트 (DDD 번들) - 다른 루트가 목록으로 가진 루트에만 필요 |
@aggregate(Root) | 그 루트의 애그리거트 멤버 (보통 추론됨) |
@editable | DDD 번들: 루트가 평범한 데이터 - 공개 setter, PUT. 없으면 루트는 잠김 |
@sort(name+) | 검색이 정렬을 말하지 않을 때의 순서: 속성 + + 오름차순 / - 내림차순, 쉼표로 여러 개 (@sort(orderDate-, orderId-)) |
@terms(order, purchase, 구매) | 업무에서 부르는 이름 - Ossie 로 나가는 동의어. 코드에는 영향 없음 |
@access(...), @owner(필드), @signin | 누가 무엇을 하는가 - 권한과 로그인 |
@event(Placed(total), Shipped) | 루트의 도메인 이벤트 - 파생 값과 이벤트 |
@expose(20, methods="read,search") | 컨트롤러가 적은 메서드만 제공 (create, read, search, update, delete). 클라이언트·유스케이스가 따르고 서비스는 모두 둠 - API 와 오류 응답 |
@restExclude(update, deleteById) | methods 로 대체되어 deprecated: 서비스·컨트롤러·유스케이스·TypeScript 클라이언트에서 메서드를 뺌 |
@table(name) / @schema(name) | 테이블 이름 / DB 스키마 |
@Index(name="uk_member_email", columnList="email", unique=true) | 테이블 인덱스. unique 면 중복이 409 |
@abstract | 추상 엔티티 - 상속 전용, 테이블 없음 |
@extend(Parent) | 부모의 필드를 상속 |
@rollup | 부모 테이블로 컬럼 통합 (단일 테이블 상속) |
@embeddable | 값 객체 - 관계와 값 객체 |
@audited | Hibernate Envers 감사 |
@sort 는 3.0 규칙 엔진 기능입니다. 2.x 생성기는 읽지 않습니다. 엔티티에 없는 속성을 적으면 DSL 경고
(@sort(...): x is not a field of Product) 후 그 속성만 빠집니다.
예시
entity:
AuditEntity:
meta: entity @abstract
fields:
createdBy: string(20) @updatable(false) @jpa:org.springframework.data.annotation.CreatedBy
createDate: datetime @updatable(false) @jpa:org.springframework.data.annotation.CreatedDate
Product:
meta: entity @root @editable @expose(20) @access(read=public) @sort(name+) -- 상품 (Angelfish, Bulldog ...)
fields:
productId: long @id @sequence
name: string(30)! @search(like)
description: string(3000)!
category: Category @ManyToOne
루트의 규칙 (DDD 번들)
architecture = 'ddd' 나 'hexagonal' 이 필요합니다.
@invariant(조건, '메시지') - 루트가 항상 참으로 유지하는 것
Order:
meta: entity @root @expose(30) @invariant(items.size > 0, 'an order has at least one line')
루트에 붙는 OrderInvariants 리스너가 행이 쓰일 때마다(@PrePersist / @PreUpdate) 검사합니다 - 어떤 경로로
저장하든 지나칠 수 없습니다. 어기면 422 입니다. 여러 번 쓸 수 있습니다. 필드 하나에 대한 규칙은 필드에 씁니다
(@min, @minsize) - DDD 번들에서는 그것도 저장 시점에 검사됩니다. @invariant 는 여러 필드를 함께 보는 규칙에 씁니다.
@specification(이름, 조건) - 이름을 가질 만한 도메인 규칙
Order:
meta: entity @root @specification(highValue, totalAmount > 100) @specification(hasLines, items notEmpty)
OrderSpecifications.highValue() 가 생깁니다 - Predicate<Order> 라서 규칙끼리 조합합니다:
highValue().and(hasLines()). 쿼리가 아니라 이미 읽어 온 것에서 고릅니다. ts-model 번들을 쓰면 TypeScript 클라이언트에도 같은 함수가 생깁니다.
@factory(place) - 유효한 애그리거트를 만드는 자리
OrderFactory.place(...) 가 한 번만 생성되는 틀로 생깁니다. 파라미터는 루트의 필수 필드(생성되는 식별자와 컬렉션
제외)이고, 본문은 직접 쓸 때까지 예외를 던집니다.
조건 문법
| 형태 | 뜻 |
|---|---|
items.size > 0 | to-many 의 멤버 수 |
totalAmount > 100 | 숫자 비교 (< 도 가능) |
notes notNull | 필드가 설정되어 있음 |
items notEmpty | to-many 에 무언가 들어 있음 |
필드·비교·리터럴만 받습니다. >=, <=, == 는 쓸 수 없고(애노테이션 파서가 = 를 키/값 목록으로 읽음), : 도
쓸 수 없습니다(YAML 이 매핑으로 읽음). 읽지 못한 조건은 빠지므로, 생성된 파일에 검사가 있는지 확인하세요.
@behavior - 루트의 메서드만 바꾸는 필드
나뉜 루트의 필드에 @behavior 를 붙이면 요청으로 받지 않고(READ_ONLY) creation() 에도 없으며, 내 메서드가
protected setter 로 바꿉니다. 루트는 엔티티에 @behavior 를 적지 않아도 XxxBase 와 내 Xxx 로 나뉩니다. 나뉘지
않는 엔티티의 필드 @behavior 는 DSL 경고만 남깁니다 - 거기서는 @readonly 를 씁니다.
잠긴 루트, @editable, creation() 은 DDD에 있습니다.
복합 키
@id 필드가 여럿이면 KeyDTO 클래스가 생깁니다:
OrderItem:
meta: entity
fields:
orderId: string @id
itemSeq: int @id
quantity: int
피할 이름
엔티티 이름이 테이블 이름이 되므로 SQL 예약어는 피합니다(Order, User, Group 대신 PurchaseOrder, Member,
Team). 필드 이름도 마찬가지입니다(order, group, user, select, from, where, table, index, key, value).