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

3단계: 주문

모델​

blueprint/petstore/model/entity.yml
  # ---- orders -----------------------------------------------------------------

PurchaseOrder:
# 주문은 로그인한 고객이 자기 것으로, 조회·취소는 본인, 배송·삭제는 관리자
meta: entity @root @expose(20) @event(Placed(total), Shipped, Cancelled) @owner(customerId)
@access(read=owner, create=user, cancel=owner) @terms(order, purchase, 구매) -- 주문
fields:
orderId: string(36) @id @genid
customer: Customer @ManyToOne # -> customerId
orderDate: date @behavior
status: OrderStatus
deliveryAddress: Address @Embedded
creditCard: CreditCard! @Embedded
totalWithoutVat: money(12.2) @default(0) @compute(sum(lines.subTotal))
vatRate: decimal(5.2) @default(0) @behavior
vat: money(12.2) @default(0) @behavior
discountRate: decimal(5.2) @default(0) @behavior
discount: money(12.2) @default(0) @behavior
total: money(12.2) @default(0) @behavior
lines: List<OrderLine> @OneToMany(mappedBy="order") @dtype(List<OrderLineDTO>) @minsize(1, 'the cart is empty')
version: long @jpa:Version
states: # 주문의 생명주기: 명령마다 어디서 어디로
status:
initial: PLACED
ship: PLACED -> SHIPPED
cancel: PLACED -> CANCELLED
delete: CANCELLED # 지울 수 있는 상태: 취소된 주문만

OrderLine:
meta: entity @behavior -- 주문 줄
fields:
lineId: string(36) @id @genid
order: PurchaseOrder @ManyToOne
item: Item @ManyToOne # -> itemId
quantity: quantity! @min(1)
unitCost: money(10.2) @snapshot(item.unitCost) # 주문 당시 가격을 복사해 둠
subTotal: money(12.2) @compute(unitCost * quantity)
blueprint/petstore/model/enum.yml
  OrderStatus:
meta: enum @db(code) @json(code) -- 주문 상태
fields:
code: string(3) @code
label: string(20)
enum:
PLACED: PLC, Placed
SHIPPED: SHP, Shipped
CANCELLED: CAN, Cancelled
blueprint/petstore/model/dto.yml (일부)
dto:
PurchaseOrderDTO:
meta: dto @template(PurchaseOrder)

OrderLineDTO:
meta: dto @template(OrderLine)
fields:
itemName: string @ref(item.name) # 응답에 품목 이름

각 부분이 하는 일​

부분효과
@owner(customerId) @access(read=owner, create=user, cancel=owner)로그인한 고객이 주문하고(요청이 무엇을 보내든 그 사람이 주인), 자기 것만 조회·검색·취소. ship 과 delete 는 관리자
states:POST /{orderId}/ship, /cancel. 잘못된 상태에서의 이동은 409, 삭제는 CANCELLED 일 때만
status: OrderStatusDTO 에서 읽기 전용 - 요청으로 정할 수 없음
@snapshot(item.unitCost)줄은 주문 당시 가격을 남기고, 나중에 가격이 바뀌어도 그대로
@compute(...)소계와 totalWithoutVat 를 저장할 때마다 계산. 보낸 값은 무시
@behavior 필드루트의 메서드만 정함 - 상점의 VAT 와 할인 (아래)
@minsize(1, 'the cart is empty')줄 없는 주문([] 또는 없음)은 400
@event(Placed(total), Shipped, Cancelled)ship·cancel 이 Shipped·Cancelled 를 냄. Placed 는 아래 코드가 냄
version: long @jpa:Version같은 주문을 동시에 두 번 저장하면 나중 것이 409
PurchaseOrder 에 @editable 필드 없음PUT 없음: 주문은 명령으로만 바뀜

상점의 정책은 손으로​

VAT 와 할인은 설정에서 오는 상점의 정책입니다 - DSL 이 말하지 않는 주문의 유일한 부분입니다. 루트 PurchaseOrder 는 나뉘어 있어서, PurchaseOrderBase 는 다시 생성되고 PurchaseOrder 는 내 것입니다.

domain/model/PurchaseOrder.java (내 것)
@Entity
@Table(name = "ps_purchase_order")
@DynamicUpdate
public class PurchaseOrder extends PurchaseOrderBase {

private static final BigDecimal HUNDRED = BigDecimal.valueOf(100);

public void place(LocalDate today, Function<Long, Address> homeOf, BigDecimal vatRate, BigDecimal discountRate) {
if (getOrderDate() != null) throw new InvariantViolationException("order " + getOrderId() + " is already placed");
if (getDeliveryAddress() == null) setDeliveryAddress(homeOf.apply(getCustomerId()));
if (getDeliveryAddress() == null) throw new InvariantViolationException("where should the order go? no delivery address");

Money withoutVat = getTotalWithoutVat();
Money vat = percent(withoutVat, vatRate);
Money discount = percent(withoutVat.plus(vat), discountRate);

setOrderDate(today);
setVatRate(vatRate);
setVat(vat);
setDiscountRate(discountRate);
setDiscount(discount);
setTotal(withoutVat.plus(vat).minus(discount).rounded(2));
raisePlaced();
}

private static Money percent(Money amount, BigDecimal rate) {
return amount.times(rate.divide(HUNDRED, 6, RoundingMode.HALF_UP)).rounded(2);
}
}
application/PurchaseOrderService.java (내 것, 줄임)
@Override
protected void beforeSave(PurchaseOrder order) {
if (order.getOrderDate() == null) {
order.place(LocalDate.now(clock), this::homeOf, vatRate, discountRate);
}
}

비율은 application.yml 에서 옵니다:

application.yml
petstore:
vat-rate: 5.5 # percent, added to the lines
discount-rate: 2.25 # percent, taken off the VAT-inclusive amount

응답​

예제 애플리케이션에 보낸 실제 요청과 응답:

주문 (로그인 안 함)                      → 401 "Sign in to create PurchaseOrder"
주문 (단가 0.01, 상태 SHP 를 보내 봄) → 200 단가 10.00, 상태 PLC - 서버가 정함
배송 (고객) → 403 "ship PurchaseOrder is for the administrator"
취소 (배송된 주문) → 409 "PurchaseOrder cannot cancel when status is SHIPPED; it must be [PLACED]"
취소 (배송된 주문, Accept-Language: ko) → 409 "주문 상태가 SHIPPED이면 취소할 수 없습니다 ..." code: state.PurchaseOrder.status.cancel
삭제 (배송된 주문) → 409 "PurchaseOrder cannot be deleted when status is SHIPPED; it must be [CANCELLED]"
남의 주문 조회 → 404

주문 요청 본문 (로그인한 상태):

{ "lines": [ { "itemId": 1000, "quantity": 2 } ],
"creditCard": { "creditCardNumber": "4111111111111111", "creditCardType": "VISA", "creditCardExpDate": "12/30" } }

응답에는 줄마다 unitCost 와 subTotal, 합계들, 배송지(보내지 않았으면 집 주소)가 있고 카드 번호는 없습니다. 검증이 권한 검사보다 먼저라, 카드 없이 보낸 같은 요청은 방문자에게도 400 creditCard: must not be null 입니다.

다음: 백오피스, 이벤트, 지표, 메시지.