[CAP for Node] Association vs Composition — 데이터 모델 관계 설계 완전 가이드
![[CAP for Node] Association vs Composition — 데이터 모델 관계 설계 완전 가이드](https://btpstacks.com/uploads/7a67009f-3681-496e-b9ab-b4eee2c22873.png)
📖 개요 및 학습 목표
CAP(Cloud Application Programming) CDS에서 엔티티 간 관계를 정의하는 두 가지 핵심 키워드: Association과 Composition. 둘 다 "관계"를 표현하지만 데이터의 생명주기와 소유권에서 결정적인 차이가 있습니다. 이 차이를 모르면 Deep Insert가 안 되거나 부모 삭제 시 고아 데이터가 남는 등 실무에서 골치 아픈 버그를 만납니다.
이 글을 읽으면 다음을 할 수 있습니다:
- ✅ Association과 Composition의 의미적·기술적 차이를 명확히 구분
- ✅ Managed Association으로 외래키를 자동 관리하는 패턴 적용
- ✅ Composition으로 Deep INSERT/UPDATE/DELETE 구현
- ✅ One-to-One, One-to-Many, Many-to-Many 관계를 올바르게 설계
대상 독자: CAP 프로젝트에서 schema.cds를 작성하기 시작한 초·중급 개발자
📚 선수 지식
- CDS(Core Definition Language) 기본 문법 — entity, type, aspect
- 관계형 데이터베이스의 외래키(FK) 개념
- OData V4 기본 CRUD 이해
- Node.js + CAP 프로젝트 생성 경험 (
cds init)
🔧 환경 / 버전 / 준비물
- CAP: @sap/cds 8.x (2024년 10월 이후, capire 공식 문서 기준)
- Node.js: 20 LTS 이상
- 데이터베이스: SQLite (개발,
--in-memory) 또는 SAP HANA Cloud (프로덕션) - 도구: VS Code + SAP CDS Language Support 확장
- 프로젝트 생성:
cds init my-bookshop && cd my-bookshop && npm i

💡 핵심 개념
Association과 Composition의 차이를 회사 조직도로 비유하면:
- Association = "같이 일하는 사이" — 저자(Author)와 책(Book)의 관계. 저자가 퇴사해도 책은 도서관에 남아있습니다. 서로 독립적으로 존재할 수 있으며, 삭제가 전파되지 않습니다.
- Composition = "소속 관계" — 주문(Order)과 주문항목(OrderItem)의 관계. 주문이 삭제되면 주문항목도 함께 삭제됩니다. 자식은 부모 없이 존재할 수 없으며, 부모를 통해서만 생성/수정/삭제됩니다(Deep Operations).
# Association vs Composition 비교표
┌──────────────────┬─────────────────────┬─────────────────────┐
│ │ Association │ Composition │
├──────────────────┼─────────────────────┼─────────────────────┤
│ 생명주기 │ 독립적 │ 부모에 종속 │
│ 삭제 전파 │ X (부모 삭제해도 유지)│ O (부모 삭제 시 함께)│
│ Deep INSERT │ X │ O │
│ Deep UPDATE │ X │ O │
│ OData $expand │ O │ O │
│ 외래키 위치 │ 참조하는 쪽 │ 자식 쪽 │
│ 대표 예시 │ Book → Author │ Order → OrderItems │
└──────────────────┴─────────────────────┴─────────────────────┘
Managed Association: CDS에서 author : Association to Authors라고만 쓰면, CAP이 자동으로 author_ID 외래키 필드를 생성하고 JOIN 조건을 추가합니다. 이것이 "Managed" Association이며, 항상 이 방식을 권장합니다.
흔한 오개념:
- ❌ "Composition은 to-many에서만 쓴다" → ⭕ to-one Composition도 가능합니다. 예:
Order : Composition of one ShippingAddress - ❌ "Association으로도 Deep INSERT가 된다" → ⭕ 일반적으로 Association에서는 Deep INSERT가 지원되지 않습니다. 자식을 함께 생성하려면 Composition을 사용해야 합니다.
💻 실전 코드 — 3단계
1단계: 기본 예제 — Association과 Composition 정의
// db/schema.cds
namespace bookshop;
using { cuid, managed } from '@sap/cds/common';
// Authors — 독립 엔티티
entity Authors : cuid, managed {
name : String(100);
country : String(2);
books : Association to many Books on books.author = $self;
}
// Books — Author와 Association (독립적 존재)
entity Books : cuid, managed {
title : String(200);
price : Decimal(10,2);
stock : Integer;
author : Association to Authors; // → author_ID 자동 생성 (Managed)
genre : String(50);
reviews: Composition of many Reviews on reviews.book = $self; // Composition!
}
// Reviews — Book에 종속 (Composition)
entity Reviews : cuid, managed {
book : Association to Books; // 역참조 (자동 생성)
rating : Integer;
text : String(1000);
}
결과: Books 테이블에 author_ID 컬럼이 자동 생성됩니다. Reviews는 Books와 Composition 관계이므로, Book을 통해서만 CRUD가 가능합니다.
2단계: 실무 시나리오 — Deep INSERT와 Cascade DELETE
// srv/cat-service.js
const cds = require('@sap/cds');
module.exports = class CatalogService extends cds.ApplicationService {
async init() {
const { Books, Authors } = this.entities;
// Deep INSERT — Composition 덕분에 Book + Reviews 동시 생성
this.on('CREATE', 'Books', async (req) => {
// 이런 요청이 가능:
// POST /catalog/Books
// { "title": "CAP 입문", "reviews": [
// { "rating": 5, "text": "최고!" },
// { "rating": 4, "text": "좋아요" }
// ]}
// → Book 1건 + Review 2건이 한 트랜잭션으로 생성됨
console.log('Deep INSERT:', JSON.stringify(req.data));
return super.onCreate(req);
});
// Association은 Deep INSERT 불가 — 별도 처리 필요
this.on('createBookWithAuthor', async (req) => {
const { title, authorName } = req.data;
const db = await cds.connect.to('db');
// 1) Author 먼저 생성
const author = await db.run(
INSERT.into('bookshop.Authors').entries({ name: authorName })
);
// 2) Book에 author_ID 연결
const book = await db.run(
INSERT.into('bookshop.Books').entries({
title: title,
author_ID: author.req.data.ID // 외래키 직접 지정
})
);
return book;
});
// Cascade DELETE 테스트 — Book 삭제 시 Reviews 자동 삭제
this.after('DELETE', 'Books', (_, req) => {
console.log(`Book 삭제됨 → 연관 Reviews도 자동 삭제 (Composition)`);
});
await super.init();
}
};
3단계: 고급 — Many-to-Many와 프로덕션 고려사항
// db/schema.cds — Many-to-Many 관계 (링크 테이블)
entity Books : cuid {
title : String(200);
categories : Composition of many Books2Categories on categories.book = $self;
}
entity Categories : cuid {
name : String(50);
books : Association to many Books2Categories on books.category = $self;
}
// 링크 테이블 — Many-to-Many를 위한 중간 엔티티
entity Books2Categories {
key book : Association to Books;
key category : Association to Categories;
}
// 프로덕션 고려사항: $expand 성능
// BAD: 3단계 중첩 $expand → 성능 저하
// GET /catalog/Authors?$expand=books($expand=reviews($expand=...))
// GOOD: 필요한 단계만 확장 + $select로 필드 제한
// GET /catalog/Books?$expand=author($select=name),reviews($select=rating)&$top=20
// Draft 지원 시 Composition은 자동으로 Draft 전파
// @odata.draft.enabled 활성화하면 부모-자식 모두 Draft 상태 관리
⚠️ 흔한 실수 / 트러블슈팅
Q1: "Association to many"에서 on 절을 빠뜨렸어요
- 증상: CDS 컴파일 에러 또는 $expand 시 빈 결과
- 원인: to-many Association에는 반드시
on절이 필요합니다. 어느 필드로 연결할지 CDS가 추론할 수 없기 때문입니다. - 해결:
books : Association to many Books on books.author = $self;— 반드시 역방향 참조를 on 절에 명시하세요.
Q2: Deep INSERT 시 "ENTITY_NOT_FOUND" 에러
- 증상: Book과 Reviews를 동시에 POST 했는데 에러
- 원인: Reviews가 Association으로 정의되어 있을 수 있습니다. Deep INSERT는 Composition에서만 작동합니다.
- 해결:
Association to many Reviews를Composition of many Reviews로 변경하세요.
Q3: 부모 삭제 시 자식이 안 지워져요
- 증상: Order 삭제 후 OrderItems가 DB에 남아있음
- 원인: Association으로 정의하면 Cascade DELETE가 작동하지 않습니다.
- 해결: Composition으로 변경하면 CAP이 자동으로 자식 엔티티를 함께 삭제합니다. 또는 @Before 핸들러에서 수동 삭제를 구현합니다.
🚀 다음 단계 / 관련 주제
- CDS Aspects —
cuid,managed같은 공통 aspect를 커스텀으로 만들어 재사용 - OData V4 Draft — Composition과 Draft를 함께 사용하는 Fiori Elements 패턴
- @restrict 인가 — 엔티티별 역할 기반 접근 제어와 Composition의 상호작용
- HANA Cloud 배포 — SQLite → HANA 전환 시 Managed Association의 FK 매핑
- Remote Service 소비 — 외부 API를 CAP 서비스에서 Association으로 연결

📚 참고 자료
- CAP 공식 문서: CDL — Associations & Compositions 정의 문법
- CAP 공식 문서: Domain Modeling — 데이터 모델 설계 가이드
- CAP 공식 문서: Providing Services — Deep Operations 동작 설명
- SAP Learning: Using Associations and Compositions
- SAP Community: Understanding Relationship Types in CAP
- GitHub: SAP CAP Samples — Association/Composition 예제 포함
📌 본 게시물은 AI(Claude)가 공개된 자료를 기반으로 자동 생성한 콘텐츠입니다. 기술 내용의 정확성은 SAP 공식 문서 와 교차 확인하시기 바랍니다.
™ SAP, S/4HANA, ABAP, Fiori, SAP BTP 등은 SAP SE 또는 그 계열사의 등록 상표입니다. 본 사이트는 SAP SE 와 공식적인 관련이 없는 비공식 학습 자료 입니다.
📧 저작권 침해 / 오류 / 콘텐츠 신고: btpstacks.com 의 "문의" 메뉴를 이용해주세요.