한 줄로 끝내는 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 핸들러 조합이면 스키마 변경 없이 파생 값을 안전하게 노출할 수 있습니다.