한 줄로 끝내는 Calculated Field — DB 불필요 #shorts #SAP #CAP

Moderator · 조회 2

Virtual Element란

CAP에서 Virtual Element는 DB 테이블에 실제 컬럼이 없지만 OData 응답에 노출되는 가상 필드입니다. 집계, 변환, 외부 데이터 조합 같은 파생 값을 스키마에 추가할 때 마이그레이션 없이 즉시 적용할 수 있는 점이 강점입니다.

CDS 모델 정의

virtual 키워드로 컬럼을 선언하면 DB DDL에서 제외됩니다. @Core.Computed를 함께 붙여 클라이언트의 쓰기 시도를 차단하는 방식이 일반적으로 권장됩니다.

namespace my.shop;

entity Orders {
  key ID       : UUID;
  quantity     : Integer;
  unitPrice    : Decimal(9,2);
  customerName : String(100);

  // DB에 저장되지 않는 계산 필드
  virtual totalPrice : Decimal(11,2) @Core.Computed;
  virtual greeting   : String(120)   @Core.Computed;
}

Read 핸들러로 값 채우기

Virtual Element는 자동 계산되지 않으므로 서비스 레이어의 after READ 훅에서 값을 주입합니다. 단일/배열 응답 양쪽을 모두 처리하도록 가드를 두는 패턴이 안전합니다.

// srv/orders-service.js
const cds = require(@sap/cds);

module.exports = cds.service.impl(async function () {
  const { Orders } = this.entities;

  this.after(READ, Orders, (rows) => {
    const list = Array.isArray(rows) ? rows : [rows];
    for (const row of list) {
      if (!row) continue;
      const qty = row.quantity ?? 0;
      const price = row.unitPrice ?? 0;
      row.totalPrice = Number((qty * price).toFixed(2));
      row.greeting = `Hello, ${row.customerName ?? Guest}`;
    }
  });
});

Projection에서의 활용

서비스 Projection에서도 virtual을 추가해 원본 엔티티는 그대로 두고 노출 계층만 확장할 수 있습니다.

service ShopService {
  entity Orders as projection on my.shop.Orders {
    *,
    virtual null as discountedTotal : Decimal(11,2) @Core.Computed
  };
}

주의할 점

Virtual Element는 DB에 존재하지 않으므로 $filter, $orderby 같은 쿼리 옵션에서 사용할 수 없습니다. 정렬·필터링이 필요하다면 DB 컬럼화하거나 HANA Calculation View를 검토하는 편이 안정적입니다.

핵심 한 줄

Virtual Element와 after-READ 핸들러 조합이면 스키마 변경 없이 파생 값을 안전하게 노출할 수 있습니다.