본문으로 건너뛰기
ElastiCORE 3.0 은 릴리스 후보(RC)입니다: Maven Central 의 3.0.261022-rc1. 2.2 라인 프로젝트는 2.2 문서를 보세요.
버전: 3.0

생명주기 (states:) 와 업무 명령 (commands:)

상태가 어디서 어디로만 바뀌는지는 코드 대신 DSL 에 씁니다. 명령 메서드의 본문까지 생성됩니다. architecture = 'ddd' 나 'hexagonal' 이 필요합니다.

세 곳이 이렇게 연결됩니다​

enumeration:
OrderStatus: # (1) 상태 목록 = enum 의 상수 이름
meta: enum @db(code) @json(code)
fields:
code: string(3) @code
label: string(20)
enum:
PLACED: PLC, Placed
SHIPPED: SHP, Shipped
CANCELLED: CAN, Cancelled

entity:
PurchaseOrder:
meta: entity @root @expose(20) @event(Placed(total), Shipped, Cancelled)
fields:
orderId: string(36) @id @genid
status: OrderStatus # (2) 상태를 담는 필드
states:
status: # (3) 키 = 필드 "이름"
initial: PLACED # 새 주문의 시작 상태
ship: PLACED -> SHIPPED # 명령: 시작 -> 도착
cancel: PLACED -> CANCELLED
delete: CANCELLED # 지울 수 있는 상태
  • states: 아래의 키는 필드 이름(status)입니다. 타입 이름이 아닙니다.
  • 상태 이름은 enum 의 상수 이름(PLACED)입니다. 코드값(PLC)이 아닙니다.
  • initial 과 delete 말고 나머지 키는 모두 명령입니다: ship: 은 ship() 과 POST /api/.../{id}/ship 을 만듭니다. 파라미터가 필요할 때만 commands: 에 같은 이름으로 적습니다.
  • 시작 상태가 여럿이면 쉼표: cancel: PLACED, SHIPPED -> CANCELLED.
  • 화살표가 나가지 않는 상태는 끝 상태입니다.

생성되는 것​

생성물내용
private OrderStatus status = OrderStatus.PLACED시작 상태. initial 이 없으면 새 행의 상태가 비어 어떤 명령도 통과하지 못함
PurchaseOrderBase.ship()지금 상태 확인 -> 이동 -> 도착 상태와 같은 이름의 @event 가 있으면 raiseShipped(). 시작 상태가 아니면 409
PurchaseOrderBase.canShip()지금 이 명령을 할 수 있는지
POST /{id}/ship, 유스케이스, 서비스commands: 와 같음
TypeScript canShip(dto)버튼을 보일지 정할 때
DTO 의 status읽기 전용 - 생성 요청에 "status": "SHP" 를 넣어도 무시. 상태는 명령으로만 바뀜

시작 상태가 아닌데 명령이 오면:

POST /api/petstore/purchaseOrder/{id}/cancel        (주문이 SHIPPED)

HTTP/1.1 409
{ "title": "Not allowed in this state",
"detail": "PurchaseOrder cannot cancel when status is SHIPPED; it must be [PLACED]",
"code": "state.PurchaseOrder.status.cancel", "status": 409 }

Accept-Language: ko 와 한국어 번역이 있으면 제목과 문장이 한국어로 나갑니다 - 메시지와 언어.

생성된 Base 의 주석에 생명주기가 글로 남아 DSL 을 열지 않아도 읽을 수 있습니다:

/**
* Lifecycle of status (OrderStatus), from the DSL's states:
* start: PLACED
* ship: PLACED -> SHIPPED
* cancel: PLACED -> CANCELLED
* end: SHIPPED, CANCELLED - no command leaves them
*/

이동에 일을 더 하고 싶을 때​

이동은 생성 코드가 하고, 더 할 일(배송 번호 기록 등)은 행위 클래스에서 override 합니다:

PurchaseOrder.java (내 것)
public class PurchaseOrder extends PurchaseOrderBase {
@Override
public void ship() {
// 이동 전에 확인할 것이 있으면 여기서
super.ship(); // 상태 확인 + 이동 + 이벤트
// 이동 후에 할 일
}
}

다른 애그리거트나 포트가 필요한 일(택배사 호출)은 확장 서비스에서 ship(orderId) 를 override 합니다.

지울 수 있는 상태: delete:​

delete: CANCELLED (또는 [CANCELLED, LOST]) 는 그 상태일 때만 행을 지울 수 있게 합니다. 명령이 아니라 상태 목록이라 엔드포인트가 더 생기지 않습니다.

생성물내용
canDelete()지금 지울 수 있는지
checkDeletable()다른 상태면 409 (state.PurchaseOrder.status.delete), 행은 그대로. JPA @PreRemove 라 모든 삭제 경로에 걸림
TypeScript canDelete(dto)삭제 버튼
DELETE /api/petstore/purchaseOrder/{배송된 주문}
HTTP/1.1 409
{ "code": "state.PurchaseOrder.status.delete",
"detail": "PurchaseOrder cannot be deleted when status is SHIPPED; it must be [CANCELLED]" }

delete: 가 없으면 삭제는 생명주기와 무관합니다. delete: [] 는 어떤 상태에서도 지울 수 없음입니다. 누가 지울 수 있는지는 @access(delete=admin) 이 정합니다. JPQL 일괄 삭제, 네이티브 SQL, ON DELETE CASCADE 는 JPA 콜백을 거치지 않습니다.

생명주기가 여럿일 때​

enum 필드 둘에 각각 생명주기를 둘 수 있고, 같은 명령 이름이 둘에 있으면 그 명령은 둘을 함께 바꿉니다:

  Shipment:
meta: entity @root @expose(20) @event(Sent, Cancelled, Refunded)
fields:
shipmentId: string(36) @id @genid
status: ShipmentStatus
fee: FeeStatus
states:
status:
initial: READY
send: READY -> SENT
cancel: READY -> CANCELLED
fee:
initial: DUE
pay: DUE -> PAID
cancel: PAID -> REFUNDED # 같은 명령 cancel: 둘을 함께 바꿈

cancel() 은 모든 생명주기를 먼저 확인하고 모두 통과할 때만 이동합니다 - 아니면 409 이고 어느 필드도 바뀌지 않습니다. canCancel() 은 조건의 AND 입니다.

파라미터가 있는 명령: commands:​

SalesOrder:
meta: entity @root @expose(20)
fields:
orderId: string(36) @id @genid
status: OrderStatus @behavior
commands:
confirm: # 파라미터 없음
ship: # 파라미터는 필드처럼 쓴다
warehouse: string(20)!
생성물성격
POST /api/erp/salesOrder/{orderId}/ship (camelCase 명령은 kebab-case 경로)새 컨트롤러에 생성
SalesOrderShipCommand - 파라미터가 있는 명령의 요청 DTO재생성
SalesOrderUseCase.ship(orderId, command)재생성
SalesOrderCoreService.ship(...) - 불러오기, entity.ship(warehouse), 저장, 트랜잭션재생성
SalesOrderBase: abstract void ship(String warehouse)재생성 - 쓸 때까지 컴파일 오류
SalesOrder.ship(String) - 규칙내 것 (처음엔 예외를 던지는 틀)

파라미터는 값입니다. 다른 애그리거트는 식별자로 받습니다. states: 의 이동이기도 한 명령은 본문까지 생성됩니다.

빌드 경고​

경고결과
states.status names SHIPED, which OrderStatus does not have (it has PLACED, SHIPPED, CANCELLED)생명주기 생략. 명령은 추상 메서드로 남음
states names status, which PurchaseOrder has no field for생략
states names status, which is not an enum of the model생략
states.status.ship: '...' is not FROM -> TO그 이동만 생략
has @default and states.status.initialinitial 이 이김

경고는 [elcore3] DSL validation warnings: 아래에 모이고 빌드는 멈추지 않습니다. 생명주기가 빠지면 컴파일할 때 PurchaseOrder is not abstract and does not override ship() 으로 드러납니다.