1.Validating User Input
CAP(Cloud Application Programming)에서는 어노테이션을 사용하여 서버 측에서 입력 데이터를 검증할 수 있습니다. 이를 통해 잘못된 데이터가 데이터베이스에 저장되지 않도록 방지할 수 있습니다. 이 글에서는 주요 어노테이션과 그 사용 예시를 다룹니다.
주요 어노테이션
@mandatory
- 기능: 해당 필드가 비어 있지 않도록 강제합니다. null 값이나 공백 문자열(트림된 값)을 허용하지 않습니다.
- 동작: 입력값이 없을 경우, HTTP 상태 코드 400 (Bad Request)와 함께 "값이 필요합니다(Value is required)"라는 오류 메시지를 반환합니다.
- 사용 예시:
- Books 엔티티의 title 필드는 반드시 입력되어야 합니다.
- Authors 엔티티의 name 필드에도 동일하게 적용할 수 있습니다.
코드 예시:
entity Books : cuid, managed {
title : localized String(255) @mandatory; // 제목은 필수 입력값
stock : NoOfBooks default 0; // 기본값은 0
}
오류 메시지 예시:
"error": {
"code": "400",
"message": "Value is required",
"target": "title",
"@Common.numericSeverity": 4
}
@assert.target
- 기능: 관리되는 to-one 연관관계에서 참조 대상 엔티티가 실제로 존재하는지 확인합니다.
- 동작: 연관된 외래 키가 데이터베이스에 존재하지 않을 경우, HTTP 상태 코드 400 (Bad Request)와 함께 "값이 존재하지 않습니다(Value doesn't exist)"라는 오류 메시지를 반환합니다.
- 사용 예시:
- Books 엔티티의 author 필드는 반드시 존재하는 저자 ID를 참조해야 합니다.
- 이 어노테이션은 CREATE 및 UPDATE 작업 중에 유효성을 검사합니다.
코드 예시:
entity Books : cuid, managed {
author : Association to Authors @mandatory @assert.target; // 저자는 필수이며 존재해야 함
}
오류 메시지 예시:
"error": {
"code": "400",
"message": "Value doesn't exist",
"target": "author_ID",
"@Common.numericSeverity": 4
}
@assert.range
- 기능: 숫자 또는 날짜/시간 타입의 필드에 대해 값 범위를 정의하거나, 열거형(enum) 타입의 값 제한을 설정합니다.
- 동작: 값이 지정된 범위를 벗어나거나 열거형에 정의되지 않은 값을 입력할 경우, HTTP 상태 코드 400 (Bad Request)와 함께 오류 메시지를 반환합니다.
- 사용 예시:
- Books 엔티티의 genre 필드는 열거형 값(1 또는 2)만 허용됩니다.
코드 예시:
entity Books : cuid, managed {
genre : Genre @assert.range: true; // 장르는 열거형 값만 허용
}
type Genre : Integer enum {
fiction = 1; // 소설
non_fiction = 2; // 비소설
}
오류 메시지 예시:
"error": {
"code": "400",
"message": "Value 3 is invalid according to enum declaration {1, 2}",
"target": "genre",
"@Common.numericSeverity": 4
}
기본값 설정
SQL과 마찬가지로 CAP에서도 필드에 기본값을 설정할 수 있습니다. 기본값은 INSERT 작업 중에 값이 제공되지 않을 경우 자동으로 채워집니다.
코드 예시:
entity Books : cuid, managed {
stock : NoOfBooks default 0; // 기본 재고는 항상 0으로 설정됨
}
실습: 입력 유효성 검증 구현
단계별 가이드
1. 스키마 정의 수정
프로젝트의 db/schema.cds 파일을 엽니다.
아래와 같이 어노테이션을 추가합니다:
- Books.title: @mandatory
- Books.author: @mandatory, @assert.target
- Books.genre: @assert.range: true
- Books.stock: 기본값 설정 (default 0)
2. HTTP 서버 실행
터미널에서 다음 명령어를 실행하여 서버를 시작합니다:
cds watch
3. 테스트 요청
테스트 폴더(test/8_input_validation)에서 제공된 HTTP 요청 파일(admin-service-requests.http)을 사용하여 다양한 시나리오를 테스트합니다.
요청 결과로 반환되는 오류 메시지를 확인합니다:
- 빈 제목, 잘못된 저자 ID 또는 잘못된 장르 값으로 책 생성 시 오류 발생.
- 기본값이 올바르게 적용되었는지 확인.
4. 서버 종료
테스트 완료 후 터미널에서 Ctrl + C를 눌러 서버를 종료합니다.
위 내용을 통해 CAP에서 제공하는 다양한 입력 유효성 검증 기능과 그 구현 방법을 이해할 수 있습니다.
2. Preventing Lost Updates
CAP는 데이터 무결성을 보장하기 위해 동시성 제어 메커니즘을 제공합니다. 이는 여러 요청이 동시에 데이터를 수정하려고 할 때 데이터 손실을 방지합니다. CAP는 낙관적 잠금(Optimistic Locking)과 비관적 잠금(Pessimistic Locking) 두 가지 접근 방식을 지원합니다.

낙관적 잠금 (Optimistic Locking)
ETag 기반 구현
- ETag는 엔티티의 특정 버전을 식별하는 태그입니다.
- 각 엔티티가 업데이트될 때마다 ETag 값이 변경됩니다.
- 클라이언트가 데이터를 읽고 수정 요청을 보낼 때, 이전에 받은 ETag 값을 함께 전송하여 데이터가 변경되지 않았음을 확인합니다.
구현 방법
@odata.etag 어노테이션 추가
Books와 Authors 엔티티의 modifiedAt 필드를 ETag로 설정합니다. 이 필드는 업데이트 시마다 고유하게 변경됩니다.
코드 예시:
entity Books : cuid, managed {
...
}
entity Authors : cuid, managed {
...
}
annotate Books with {
modifiedAt @odata.etag;
}
annotate Authors with {
modifiedAt @odata.etag;
}
ETag를 사용한 데이터 읽기 및 수정
- 데이터를 읽을 때, 서버는 ETag 값을 HTTP 응답 헤더에 포함하여 반환합니다.
- 데이터를 수정할 때, 클라이언트는 이전에 받은 ETag 값을 If-Match 헤더에 포함하여 요청합니다.
요청 및 응답 예시:
데이터 읽기 (GET 요청):
GET Authors(18f76942-bfb3-40c5-8300-f999f14e9cc2)
응답:
HTTP/1.1 200 OK
ETag: W/"2024-03-15T11:30:48.069Z"
데이터 수정 (PUT 요청):
PUT Authors(18f76942-bfb3-40c5-8300-f999f14e9cc2)
If-Match: W/"2024-03-15T11:30:48.069Z"
Content-Type: application/json
{
"name": "Updated Author Name"
}
동작 원리
- 서버는 수정 요청 시 If-Match 헤더의 ETag 값과 현재 데이터의 ETag 값을 비교합니다.
- 값이 일치하면 데이터가 수정되고 새로운 ETag 값이 생성됩니다.
- 값이 불일치하면 HTTP 상태 코드 412 Precondition Failed와 함께 요청이 거부됩니다.
오류 메시지 예시:
"error": {
"code": "412",
"message": "Precondition Failed",
"@Common.numericSeverity": 4
}
2. 비관적 잠금 (Pessimistic Locking)
기능
- 비관적 잠금은 데이터베이스 수준에서 잠금을 설정하여 다른 트랜잭션이 데이터를 읽거나 수정하지 못하도록 합니다.
- CAP는 두 가지 잠금을 지원합니다:
- 독점 잠금 (Exclusive Lock): 다른 트랜잭션이 데이터를 읽거나 수정할 수 없습니다.
- 공유 잠금 (Shared Lock): 다른 트랜잭션이 데이터를 읽을 수는 있지만 수정할 수 없습니다.
구현 방법
CAP의 쿼리 API를 사용하여 잠금을 설정할 수 있습니다.
독점 잠금 설정
forUpdate() 메서드를 사용하여 데이터를 독점적으로 잠급니다.
const tx = cds.transaction(req);
const lockedData = await tx.run(SELECT.from('Books').forUpdate());
const tx = cds.transaction(req);
const sharedLockedData = await tx.run(SELECT.from('Books').forShareLock());
제약 사항
SQLite에서는 비관적 잠금이 지원되지 않습니다.
실습: 낙관적 동시성 제어 구현
단계별 가이드
스키마 정의 수정
db/schema.cds 파일에서 Books와 Authors 엔티티에 @odata.etag 어노테이션을 추가합니다.
서버 실행
터미널에서 다음 명령어를 실행하여 서버를 시작합니다:
cds watch
테스트 시나리오
테스트 폴더(test/9_concurrency_control)에서 제공된 HTTP 요청 파일(admin-service-requests.http)을 사용하여 다음 단계를 수행합니다:
- 저자 생성 후 ID 저장 (authorID 변수).
- 동일한 저자를 두 번 읽고 각각의 ETag 저장 (ETag1, ETag2 변수).
두 ETag 값은 동일해야 함. - 첫 번째 ETag(ETag1)를 사용해 저자 업데이트.
업데이트 후 새로운 ETag 값 생성됨. - 두 번째 ETag(ETag2)로 업데이트 시도.
이 경우, HTTP 상태 코드 412 Precondition Failed가 반환됨.
서버 종료
테스트 완료 후 터미널에서 Ctrl + C를 눌러 서버를 종료합니다.
3. Exposing Denormalized Views
CAP(Cloud Application Programming)에서는 특정 사용 사례에 맞춘 비정규화된 뷰를 정의하여 데이터 모델을 효율적으로 노출할 수 있습니다. 이 글에서는 비정규화된 뷰의 개념, 구현 방법, 그리고 실제 예제를 통해 이를 자세히 설명합니다.
비정규화된 뷰 개념
- 정의: 비정규화된 뷰는 기본 데이터 모델의 일부를 선택적으로 노출하거나, 연관 데이터를 결합하여 특정 사용 사례에 최적화된 서비스를 제공합니다.
- 목적:
- 불필요한 데이터 노출 방지
- 특정 비즈니스 요구사항에 맞춘 데이터 제공
- 클라이언트 애플리케이션의 효율적인 데이터 처리 지원
CatalogService 예제
서비스 정의
using { com.sap.learning as db } from '../db/schema';
service CatalogService @(path:'/cat') {
entity Books as projection on db.Books {
key ID,
title,
author.name as writer,
author.dateOfBirth,
genre,
stock,
price.amount,
price.currency,
publCountry.name as publCountry
};
entity Authors as projection on db.Authors excluding {
placeOfBirth,
placeOfDeath
} {
*,
epoch.name as period
};
}
주요 특징
- 경로 표현식 (Path Expressions)
- author.name as writer: 연관된 저자의 이름을 'writer'로 노출
- price.amount, price.currency: 구조화된 'price' 요소의 개별 필드 노출
- publCountry.name as publCountry: 연관된 국가 코드 리스트에서 국가 이름 노출
- 스마트 선택자와 제외 절 (Smart Selector and Excluding Clause)
- *: Authors 엔티티의 모든 필드 선택
- excluding { placeOfBirth, placeOfDeath }: 특정 필드 제외
- epoch.name as period: 추가 필드로 시대 이름 포함
구현 단계
- srv 폴더에 cat-service.cds 파일 생성
- CatalogService 정의 코드 작성
- 서비스 테스트를 위한 HTTP 요청 파일 준비 (catalog-service-requests.http)
- 서버 실행: cds watch 명령어 사용
- 다양한 HTTP 요청을 통한 서비스 테스트:
- 서비스 문서 조회
- 메타데이터 문서 조회
- 영어/독일어로 모든 책 조회
- 영어/독일어로 모든 저자 조회
- 새로운 저자 생성
비정규화된 뷰의 장점
- 데이터 최적화: 필요한 데이터만 선택적으로 노출
- 성능 향상: 클라이언트 측에서의 데이터 처리 부담 감소
- 유연성: 다양한 사용 사례에 맞춘 맞춤형 뷰 제공 가능
- 보안: 민감한 데이터 필드를 제외하여 노출 제한 가능
주의사항
- 비정규화로 인한 데이터 중복 가능성 고려
- 뷰 업데이트 시 기본 모델과의 일관성 유지 필요
- 복잡한 뷰의 경우 성능에 영향을 줄 수 있으므로 주의 필요
이러한 비정규화된 뷰를 활용하면, 특정 비즈니스 요구사항에 최적화된 서비스를 효율적으로 제공할 수 있습니다. CAP의 이러한 기능을 통해 개발자는 더욱 유연하고 효과적인 애플리케이션을 구축할 수 있습니다.
4. Adding Custom Logic
CAP(Cloud Application Programming)에서는 표준 서비스 제공자가 대부분의 일반적인 작업을 처리합니다. 그러나 특정 도메인 요구사항에 맞는 사용자 정의 로직이 필요한 경우, 이를 구현하기 위해 **이벤트 핸들러(event handlers)**를 사용할 수 있습니다. 아래는 AdminService에서 저자의 생년월일(dateOfBirth)이 사망일(dateOfDeath)보다 앞서야 한다는 유효성 검사를 구현하는 과정을 설명합니다.
사용자 정의 서비스 구현
서비스 구현 파일 생성
- 서비스 정의 파일(admin-service.cds)과 동일한 이름의 .js 파일(admin-service.js)을 srv 폴더에 생성합니다.
- 이 파일에서 CAP의 기본 서비스 제공자인 cds.ApplicationService를 확장하여 사용자 정의 로직을 추가할 수 있습니다.
코드 예시:
const cds = require('@sap/cds'); // CAP API 모듈 가져오기
class AdminService extends cds.ApplicationService {
}
module.exports = AdminService; // 모듈 내보내기
이벤트 핸들러 등록
이벤트 핸들러 개념
- CAP에서는 특정 이벤트(예: CREATE, UPDATE)에 대해 로직을 실행하기 위해 이벤트 핸들러를 등록할 수 있습니다.
- 이벤트 핸들러는 다음 세 가지 단계에서 실행됩니다:
- before: 요청 처리 전에 실행
- on: 요청 처리 중 기본 동작 대신 실행
- after: 요청 처리 후 실행
before 핸들러 등록
- CREATE 및 UPDATE 작업에 대해 Authors 엔티티의 데이터를 검증하는 before 핸들러를 등록합니다.
- 이를 위해 init() 메서드를 재정의하고, this.before() 메서드를 호출합니다.
코드 예시:
class AdminService extends cds.ApplicationService {
init() {
const { Authors } = this.entities; // Authors 엔티티 가져오기
// CREATE 및 UPDATE 작업에 대해 before 핸들러 등록
this.before(['CREATE', 'UPDATE'], Authors, this.validateLifeData);
return super.init(); // 부모 클래스의 init() 호출
}
}
데이터 유효성 검사 로직 구현
validateLifeData 함수
- 요청 데이터에서 생년월일(dateOfBirth)과 사망일(dateOfDeath)을 추출하여 유효성을 검사합니다.
- 사망일이 생년월일보다 빠른 경우, 오류 메시지를 반환합니다.
코드 예시:
validateLifeData(req) {
const { dateOfBirth, dateOfDeath } = req.data; // 요청 데이터에서 필드 추출
if (!dateOfBirth || !dateOfDeath) return; // 필드가 없으면 검증 생략
const birth = new Date(dateOfBirth);
const dead = new Date(dateOfDeath);
if (dead < birth) {
req.error(`The date of death (${dateOfDeath}) is before the date of birth (${dateOfBirth}).`);
}
}
동작 원리
- req.data: 요청 본문 데이터를 포함하며, 여기서 필요한 필드를 추출합니다.
- req.error(): 오류 메시지를 수집하고, 요청 처리를 중단합니다.
- 날짜 비교:
- new Date(): 문자열을 날짜 객체로 변환하여 비교 가능하게 만듭니다.
- 사망일이 생년월일보다 빠른 경우 오류를 반환합니다.
테스트 및 실행
테스트 준비
터미널에서 서버 실행:
cds watch
테스트 파일 준비:
- HTTP 요청 파일(admin-service-requests.http)을 사용하여 다양한 시나리오를 테스트합니다.
테스트 시나리오
- 유효하지 않은 데이터 전송:
- 사망일이 생년월일보다 빠른 저자 생성 요청.
- 결과: HTTP 응답에 오류 메시지 포함.
- 유효한 데이터 전송:
- 정상적인 생년월일과 사망일로 저자 생성 요청.
- 결과: 성공적으로 데이터베이스에 저장.
전체 코드 통합
최종 코드 (admin-service.js):
const cds = require('@sap/cds');
class AdminService extends cds.ApplicationService {
init() {
const { Authors } = this.entities;
this.before(['CREATE', 'UPDATE'], Authors, this.validateLifeData);
return super.init();
}
validateLifeData(req) {
const { dateOfBirth, dateOfDeath } = req.data;
if (!dateOfBirth || !dateOfDeath) return;
const birth = new Date(dateOfBirth);
const dead = new Date(dateOfDeath);
if (dead < birth) {
req.error(`The date of death (${dateOfDeath}) is before the date of birth (${dateOfBirth}).`);
}
}
}
module.exports = AdminService;
4. Debugging a CAP Application
SAP Business Application Studio (BAS)에서 CAP 프로젝트를 디버깅하는 방법에 대해 설명합니다. BAS는 Node.js 런타임에 대한 내장 디버깅 지원을 제공하여 CAP 프로젝트를 쉽게 디버깅할 수 있습니다.
디버깅 시작

- Run and Debug 뷰 열기: 왼쪽 사이드바에서 Run and Debug 아이콘을 클릭하여 뷰를 엽니다.
- CDS serve 구성 선택: 기본적으로 "CDS serve" 구성이 선택되어 있어야 합니다.
- 디버깅 시작: Play 버튼을 클릭하거나 F5 키를 누르거나, 메뉴에서 "Run > Start Debugging"을 선택하여 CAP 애플리케이션을 디버깅 모드로 시작합니다.
브레이크포인트 설정

- 브레이크포인트 추가: 에디터의 왼쪽 가장자리를 클릭하거나 현재 줄에서 F9 키를 눌러 브레이크포인트를 설정합니다. 설정된 브레이크포인트는 빨간 점으로 표시됩니다.
- 브레이크포인트 제거: 설정된 브레이크포인트를 다시 클릭하면 제거됩니다.
코드 흐름 제어
- 변수 확인: Variables 섹션에서 현재 변수의 값을 확인할 수 있습니다.
- 감시 표현식 추가: Watch 섹션에서 특정 표현식을 추가하여 코드를 실행하는 동안 감시할 수 있습니다.
- 호출 스택 확인: Call Stack 섹션에서 현재 호출 스택을 확인할 수 있습니다.
- 디버깅 도구: 디버깅 모드의 툴바에서 코드 흐름을 제어할 수 있습니다.
- 계속 실행: Continue 버튼을 클릭하여 코드 실행을 계속합니다.
- 스텝 오버: Step Over 버튼을 클릭하여 현재 줄을 건너뛰고 다음 줄로 이동합니다.
- 스텝 인: Step In 버튼을 클릭하여 함수 내부로 이동합니다.
- 스텝 아웃: Step Out 버튼을 클릭하여 함수 외부로 돌아옵니다.
- 디버깅 세션 중지: 빨간 정사각형 버튼을 클릭하여 디버깅 세션을 중지합니다.
사용자 정의 이벤트 핸들러 디버깅
- admin-service.js 파일 열기: srv 폴더에 있는 admin-service.js 파일을 엽니다.
- 브레이크포인트 설정: validate life data 메서드의 시작 부분에 브레이크포인트를 설정합니다.
- 테스트 파일 실행: test 폴더의 11_before_event_handler 하위 폴더에서 admin-service-requests.http 파일을 엽니다.
- CAP 애플리케이션 시작: Run and Debug 뷰에서 CDS serve 구성의 Play 버튼을 클릭하여 CAP 애플리케이션을 디버깅 모드로 시작합니다.
- 요청 전송: 테스트 파일에서 요청을 전송하여 새로운 저자를 생성합니다. 요청이 브레이크포인트에 도달하면 디버거가 실행을 일시 중지합니다.
- 변수 및 표현식 확인: 변수와 감시 표현식을 확인하고 코드 흐름을 제어합니다.
- 디버깅 세션 종료: 디버깅이 완료되면 빨간 정사각형 버튼을 클릭하여 세션을 중지합니다.
6. Implementing after Custom Event Handlers
after 핸들러는 on 핸들러 이후에 실행되며, 주로 아웃바운드 데이터를 강화하는 데 사용됩니다.
예제: 책 제목에 할인을 추가하는 .after 핸들러
다음은 책의 재고가 200개 이상인 경우 책 제목에 " -- 11% Discount!"를 추가하는 .after 핸들러의 예제입니다.
cat-service.js 파일 생성
- srv 폴더에서 cat-service.js 파일 생성: srv 폴더에 cat-service.js 파일을 생성합니다. 이 폴더에는 이미 cat-service.cds 파일이 있어야 합니다.
CatalogService 클래스 구현
// cat-service.js
const { ApplicationService } = require('@sap/cds');
class CatalogService extends ApplicationService {
init() {
super.init();
this.after('READ', 'Books', this.grantDiscount);
}
grantDiscount(results, req) {
for (let b of results) {
if (b.stock > 200) { b.title += ' -- 11% Discount!'; }
}
}
module.exports = { CatalogService };
설명
- init 메서드 오버라이드: init 메서드를 오버라이드하여 .after 핸들러를 등록합니다.
- grantDiscount 함수 구현: grantDiscount 함수는 READ 작업 후에 실행됩니다. 이 함수는 results 배열을 반복하며 각 책의 재고가 200개 이상인 경우 제목에 할인 문자열을 추가합니다.
- cds watch 실행: 터미널에서 cds watch 명령을 실행하여 HTTP 서버를 시작합니다.
- 테스트: test 폴더의 catalog-service-requests.http 파일에서 요청을 전송하여 책 목록을 가져옵니다. 응답에서 재고가 200개 이상인 책의 제목에 할인이 추가된 것을 확인합니다.
사용자 정의 이벤트 핸들러 디버깅
.after 이벤트 핸들러를 디버깅하려면 SAP Business Application Studio에서 Run and Debug 뷰를 사용하여 디버깅 세션을 시작합니다. grantDiscount 함수에 브레이크포인트를 설정하고, 요청을 전송하여 디버거가 실행을 일시 중지하도록 합니다. 변수와 표현식을 확인하고 코드 흐름을 제어하여 .after 핸들러가 올바르게 작동하는지 확인합니다.
7. Providing Actions and Functions
CAP(Cloud Application Programming)에서는 OData 사양을 기반으로 Actions와 Functions를 지원하며, 이를 통해 도메인별 사용자 정의 작업을 구현할 수 있습니다. Actions는 데이터를 수정하는 데 사용되며, Functions는 데이터를 조회하는 데 사용됩니다.
Actions와 Functions의 차이점
- Actions: 서버에서 데이터를 변경하며, 주로 POST 요청으로 호출됩니다.
- Functions: 데이터를 조회하며, GET 요청으로 호출됩니다.
- Unbound: 특정 엔터티와 연결되지 않음.
- Bound: 특정 엔터티와 연결됨.
SAP는 구현과 호출이 간단한 Unbound Actions/Functions를 권장합니다.
예제: submitOrder Action 정의 및 구현
Action 정의 (cat-service.cds)
submitOrder는 책의 ID와 주문 수량을 입력받아 재고를 업데이트하고, 업데이트된 재고를 반환하는 Unbound Action입니다.
using com.sap.learning as db from '../db/schema';
service CatalogService @(path: '/cat') {
action submitOrder(book: db.Books:ID, quantity: Integer) returns {
stock: db.Books:stock;
};
}
- book: 주문할 책의 ID (Books 엔터티의 ID 데이터 타입 사용).
- quantity: 주문 수량 (정수형).
- returns: 업데이트된 책의 재고를 반환.
Action 핸들러 등록 및 구현 (cat-service.js)
핸들러 등록
init() 메서드에서 on() 메서드를 사용해 submitOrder Action에 대한 핸들러를 등록합니다.
const { ApplicationService } = require('@sap/cds');
class CatalogService extends ApplicationService {
init() {
this.on('submitOrder', this.reduceStock);
return super.init();
}
reduceStock(req) {
const { Books } = this.entities;
const { book, quantity } = req.data;
if (quantity < 1) {
return req.error('The quantity must be at least 1.');
}
let stock = 10; // 임시 값
return { stock };
}
}
module.exports = { CatalogService };
설명
- this.on('submitOrder', this.reduceStock):
- submitOrder Action에 대해 reduceStock 메서드를 핸들러로 등록.
- reduceStock(req):
- req.data에서 book과 quantity 값을 가져옵니다.
- 수량이 1보다 작으면 오류 메시지를 반환합니다.
- 현재는 데이터베이스 접근 없이 하드코딩된 값(10)을 반환합니다.
HTTP 요청을 통한 테스트
요청 예제
POST <service_url>/submitOrder
Content-Type: application/json
{
"book": "0ec991dc-95c5-4d8f-91e6-f81a92744781",
"quantity": 2
}
응답 예제
{ "stock": 10 }
테스트 시나리오
- quantity가 1 이상일 때:
- 성공적으로 재고 값을 반환합니다.
- quantity가 0일 때:
- 오류 메시지 반환: "The quantity must be at least 1."
Actions/Functions 호출 방법
- 프로그래밍 방식 호출
- CAP은 일반 API와 타입 지정된 API를 제공합니다.
- 타입 지정된 API는 서비스 모델에 정의된 Action/Function에 따라 자동 생성됩니다.
- HTTP 요청
- Actions는 POST 요청으로 호출됩니다.
- Functions는 GET 요청으로 호출됩니다.
8. Constructing and Executing Queries
Constructing Queries
SELECT 및 UPDATE 쿼리
- SELECT: 특정 책의 재고를 조회하여 주문 가능한지 확인합니다.
- UPDATE: 주문 후 재고를 감소시킵니다.
기본 쿼리 작성(Fluent API)
let query1 = SELECT.one.from(Books).where({ ID: book }).columns(b => { b.stock });
let query2 = UPDATE(Books).where({ ID: book }).with({ stock: { '-=': quantity } });
- SELECT.one: 단일 결과를 반환.
- UPDATE: 재고 값을 감소시킴.
- .where(): 조건 지정.
태그 템플릿
let query1 = SELECT.one.from(Books).where`ID=${book}`.columns`stock`;
let query2 = UPDATE(Books).where`ID=${book}`.with`stock = stock - ${quantity}`;
- 태그 템플릿은 가독성을 높이고 코드 작성을 간결하게 만듭니다.
Executing Queries
작성된 쿼리는 cds.db.run() 메서드를 사용하여 실행됩니다.
SELECT 실행
let query = SELECT.one.from(Books).where({ ID: book }).columns(b => { b.stock });
const b = await cds.db.run(query);
- await cds.db.run(query): 데이터베이스에서 쿼리를 실행하고 결과를 기다림.
- 결과가 없으면 b는 undefined.
Awaiting Queries
async reduceStock(req) {
const { Books } = this.entities;
const { book, quantity } = req.data;
if (quantity < 1) {
return req.error(400, 'The quantity must be at least 1.');
}
// 현재 재고 조회
const b = await SELECT.one.from(Books).where({ ID: book }).columns(b => { b.stock });
if (!b) {
return req.error(404, 'Book not found');
}
if (b.stock < quantity) {
return req.error(400, 'Not enough stock available');
}
// 재고 업데이트
await UPDATE(Books).where({ ID: book }).with({ stock: { '-=': quantity } });
return { stock: b.stock - quantity };
}
주요 기능:
- 유효성 검사:
- 수량이 1 이상인지 확인.
- 책 ID가 존재하는지 확인.
- 재고가 충분한지 확인.
- 쿼리 실행:
- SELECT로 현재 재고 조회.
- UPDATE로 재고 감소.
- 응답 반환:
- 업데이트된 재고 반환.
테스트 시나리오
HTTP 요청 예제
POST /submitOrder
Content-Type: application/json
{
"book": "0ec991dc-95c5-4d8f-91e6-f81a92744781",
"quantity": 2
}
응답 예제
{
"stock": 8
}
에러 시나리오
수량이 0 이하일 경우:
{
"error": "The quantity must be at least 1."
}
책 ID가 없을 경우:
{
"error": "Book not found"
}
재고 부족 시:
{
"error": "Not enough stock available"
}
9. Quiz
1. When implementing optimistic locking, which HTTP header is used to send the ETag value back to the server in a change request?
한글 번역: Optimistic Locking 구현 시, 변경 요청에서 ETag 값을 서버로 다시 전송하기 위해 사용되는 HTTP 헤더는 무엇인가요?
보기:
- Content-Type
- Authorization
- Accept
- If-Match
- User-Agent
정답: If-Match
설명: If-Match HTTP 헤더는 변경 요청에서 ETag 값을 서버로 전송하여 데이터 충돌을 방지하는 데 사용됩니다.
2. The cds.ql module exposes an SQL-like API for constructing database queries. Which of the following are names of global objects provided by the cds.ql module for query construction?
한글 번역: cds.ql 모듈에서 데이터베이스 쿼리 작성을 위한 전역 객체의 이름은 무엇인가요? (두 개 선택)
보기:
- QUERY
- SELECT
- WRITE
- READ
- CREATE
- UPDATE
정답: SELECT, UPDATE
설명: cds.ql 모듈은 SELECT, INSERT, UPSERT, UPDATE, DELETE와 같은 전역 객체를 제공합니다.
3. The most common way to provide custom service implementations in a .js file is to create a subclass that inherits from the class with the default service provider implementation. What is the name of this superclass?
한글 번역: 사용자 정의 서비스 구현을 제공하기 위해 .js 파일에서 기본 서비스 제공자 구현 클래스를 상속받아 서브클래스를 생성하는 것이 일반적입니다. 이 슈퍼클래스의 이름은 무엇인가요?
보기:
- cds.DefaultImpl
- cds.GenericProvider
- cds.ApplicationService
- cds.FacadeHandler
정답: cds.ApplicationService
설명: cds.ApplicationService는 기본 서비스 제공자 구현을 위한 슈퍼클래스입니다.
4. What is the expected outcome when a PUT request is made with an outdated ETag value?
한글 번역: 오래된 ETag 값으로 PUT 요청이 이루어지면 예상 결과는 무엇인가요?
보기:
- The request fails with status code 412 (Precondition Failed).
- The request is processed and the data is updated.
- CAP automatically updates the outdated ETag value to match the current value.
- The request is ignored without any error.
정답: The request fails with status code 412 (Precondition Failed).
설명: 오래된 ETag 값으로 PUT 요청이 이루어지면 서버는 "412 Precondition Failed" 상태 코드로 요청을 거부합니다.
5. What kinds of event handlers are available in CAP that can be registered for different phases during the processing of an event?
한글 번역: CAP에서 이벤트 처리의 다양한 단계에 등록할 수 있는 이벤트 핸들러 유형은 무엇인가요? (세 개 선택)
보기:
- init handlers
- on handlers
- after handlers
- exit handlers
- before handlers
정답: on handlers, after handlers, before handlers
설명: CAP에서는 before, on, after 핸들러를 사용하여 이벤트 처리의 다양한 단계를 처리할 수 있습니다.
6. Let's assume you want to use the @mandatory annotation to ensure that the String element title of an entity has non-empty input. Which of the following is a correct implementation of the @mandatory annotation for this purpose?
한글 번역: String 요소 title이 비어 있지 않도록 보장하기 위해 @mandatory 어노테이션을 사용하려고 합니다. 올바른 구현은 무엇인가요?
보기:
- title : String; @mandatory: true
- title : String; @mandatory
- @mandatory title : String;
- title : String @mandatory;
정답: title : String @mandatory;
설명: CAP에서 올바른 구문은 title : String @mandatory;입니다.
7. What is the @assert.target annotation used for in CAP?
한글 번역: CAP에서 @assert.target 어노테이션은 무엇을 위해 사용되나요?
보기:
- for setting a value for an element of an entity
- for ensuring that the value of an element of an entity matches a specified format
- for checking for managed to-one associations of an entity whether the entity to which the association refers exists
- for ensuring that the value of an element of an entity is within a specified value range
정답: for checking for managed to-one associations of an entity whether the entity to which the association refers exists
설명: @assert.target은 to-one 연관에서 참조된 엔터티가 존재하는지 확인합니다.
8. What type of event handler is used to implement actions/functions?
한글 번역: 액션/함수를 구현하기 위해 어떤 유형의 이벤트 핸들러가 사용되나요?
보기:
- before handler
- on handler
- after handler
정답: on handler
설명: 액션과 함수는 on 핸들러를 통해 구현됩니다.
9. Assume you want to define an Authors entity as a projection on the db.Authors entity from the domain model. All elements of the db.Authors domain entity are to be included in the projection with the exception of the books element. Which of the following syntax variants is the correct one to achieve this?
한글 번역: db.Authors 도메인 모델에서 books 요소를 제외한 모든 요소를 포함하는 Authors 엔터티를 프로젝션으로 정의하려고 합니다. 올바른 구문은 무엇인가요?
보기:
- entity Authors as projection on db.Authors﹛ * ﹜excluding books;
- entity Authors as projection on db.Authors﹛ * ﹜excluding﹛ books ﹜;
- entity Authors as projection on db.Authors﹛ *, excluding books ﹜;
- entity Authors as projection on db.Authors﹛ * ﹜﹛ excluding books ﹜;
정답: entity Authors as projection on db.Authors﹛ * ﹜excluding﹛ books ﹜;
설명: 프로젝션에서 특정 요소를 제외하려면 excluding { books } 구문을 사용합니다.
10. The easiest way to add custom service implementations is to place an equally named .js file next to the .cds file that contains the respective service definition. As an alternative to this approach, which annotation can you use to explicitly reference the service definition in the .cds file to another .js file that should contain the service implementation?
한글 번역: 서비스 정의가 포함된 .cds 파일을 다른 .js 파일에 명시적으로 연결하려면 어떤 어노테이션을 사용할 수 있나요?
보기:
- @path
- @provider
- @handler
- @impl
정답: @impl
설명: @impl 어노테이션은 .cds 파일과 해당 서비스 구현이 포함된 .js 파일 간의 연결을 명시적으로 지정합니다.