SuperAPI AI Directory

cds-modeling

Coding & development

View repository

Updated 10-16 02:37

About this skill

CDS Modeling — CAP Best Practices

Primary reference: https://cap.cloud.sap/docs/cds/cdl Domain modeling: https://cap.cloud.sap/docs/guides/domain-modeling Reuse types: https://cap.cloud.sap/docs/cds/common

Always start from @sap/cds/common reuse types

using { cuid, managed, temporal, Currency, Country, Language } from '@sap/cds/common';

entity Products : cuid, managed { title : localized String(111); description : localized String(1024); price : Decimal(9,2); currency : Currency; // association to sap.common.Currencies category : Association to Categories; items : Composition of many OrderItems on items.product = $self; }


- `cuid` → UUID key aspect — write `entity Foo : cuid` NOT `key ID : UUID` or `key ID : UUID = cuid()`
- `cuid` is an **aspect** (mixin), not a function — `cuid()` is WRONG syntax and will fail
- `managed` → createdAt/By, modifiedAt/By for free — NEVER define these manually
- `temporal` → validFrom/validTo (use for time-variant data)
- `localized` → generates _texts table + i18n support automatically

### ❌ Wrong vs ✅ Correct — the most common mistakes

```cds
// ❌ WRONG — manual UUID key
entity Products {
  key ID : UUID @default: #uuidv4;
  name   : String;
}

// ❌ WRONG — cuid() is not a function
entity Products {
  key ID : UUID = cuid();
  name   : String;
}

// ❌ WRONG — manual audit fields
entity Products {
  createdAt  : Timestamp;
  createdBy  : String;
  modifiedAt : Timestamp;
}

// ✅ CORRECT — use aspects
entity Products : cuid, managed {
  name     : String(111);
  price    : Decimal(9,2);
  currency : Currency;
}

Associations vs Compositions

// Association: reference to an independent entity (no cascade delete)
product   : Association to Products;

// Composition: child is part of the parent (cascade delete, deep-insert supported)
items     : Composition of many Items on items.order = $self;

Rule: use Composition when the child cannot exist without the parent (order items, addresses on a person). Use Association for cross-entity references.

Aspects for reusable patterns

// Define once, reuse everywhere
aspect Auditable {
  approvedBy  : String;
  approvedAt  : Timestamp;
  status      : String enum { Draft; Approved; Rejected; } default 'Draft';
}

entity PurchaseOrders : cuid, managed, Auditable { ... }
entity SalesOrders    : cuid, managed, Auditable { ... }

Projections and Views

// Projection — thin wrapper, updatable
entity MyProductView as projection on Products {
  key ID, title, price, currency
};

// View with expressions — read-only
entity ProductSummary as select from Products {
  key ID,
  title,
  price * 1.19 as grossPrice : Decimal(9,2)
};

Use projections in service definitions, not the base entity, to control exposure.

Enumeration types

type OrderStatus : String enum {
  Draft   = 'D';
  Open    = 'O';
  Shipped = 'S';
  Closed  = 'C';
}

entity Orders : cuid {
  status : OrderStatus default 'D';
}

Prefer enums over plain strings for status fields — they generate value help automatically in Fiori.

Annotations: UI and ODM alignment

annotate Products with @(
  title : '{i18n>Products}',
  UI.LineItem : [
    { Value: title,  Label: '{i18n>Title}' },
    { Value: price,  Label: '{i18n>Price}' },
  ],
  UI.FieldGroup #General : {
    Data: [
      { Value: title },
      { Value: description },
    ]
  }
);

Keep annotations in a separate annotations.cds file, not inline in the entity — improves readability.

Naming conventions

Item Convention Example
Entities PascalCase, plural Products, SalesOrders
Elements camelCase orderDate, netAmount
Services PascalCase + Service CatalogService, AdminService
Actions/Functions camelCase verb submitOrder, calculateTax
Namespaces reverse-DNS com.acme.procurement

Declarative Constraints (@assert, GA since April 2026)

Validate field values directly in the CDS model without custom handler code:

entity Products : cuid, managed {
  title  : String(111)  not null;
  price  : Decimal(9,2) @assert: (
    case
      when price is null then 'PRICE_MUST_BE_PROVIDED'
      when price < 0     then 'PRICE_MUST_BE_POSITIVE'
    end
  );
  stock  : Integer @assert: (stock >= 0 ? null : 'STOCK_CANNOT_BE_NEGATIVE');
}

CAP evaluates constraints on CREATE and UPDATE and returns the key (e.g. PRICE_MUST_BE_POSITIVE) as the error code — define it as an i18n message key for localized messages.


Common mistakes to avoid

  • ❌ key ID : UUID @default: #uuidv4 — use cuid aspect instead

  • ❌ key ID : UUID = cuid() — cuid is an aspect, not a function

  • ✅ Always: entity Foo : cuid, managed { ... }

  • ❌ Defining createdAt, modifiedAt manually — use managed aspect

  • ❌ Exposing db entities directly in a service without a projection:

    // ❌ WRONG
    service CatalogService {
      entity Products;   // exposes db entity directly
    }
    // ✅ CORRECT
    service CatalogService {
      entity Products as projection on db.Products;
    }
    
  • ❌ Using String without length in HANA deployments (defaults may differ)

  • ❌ Forgetting on condition for managed associations in compositions exposed via service