← Todos los artículos

Tu primer servicio SAP CAP: de cero a Fiori en 30 minutos

Volvemos a construir la app de solicitudes de equipos del artículo de RAP, esta vez con SAP Cloud Application Programming Model (CAP), Node.js y OData V4, y la desplegamos en BTP.

CAPBTPCDSNode.jsOData V4

En el artículo de RAP creamos una app de solicitud de equipos sobre la pila ABAP. Aquí construimos la misma app con CAP, el Cloud Application Programming Model de SAP, en Node.js. Mismo negocio, distinto runtime: una buena forma de ver dónde brilla cada modelo.

¿RAP o CAP?

RAP CAP
Se ejecuta en ABAP (S/4HANA, entorno ABAP de BTP) Node.js o Java (BTP Cloud Foundry / Kyma)
Modelo de datos CDS view entities + tablas DDIC Entidades CDS (la base de datos se genera)
Lógica Behavior pool ABAP, EML Handlers de eventos en JavaScript/Java
Uso típico Extensiones cerca de los datos de S/4 Apps side-by-side, productos nuevos, escenarios multisistema

Ninguno es «mejor»: si la lógica pertenece a S/4HANA, RAP es la elección natural; para extensiones side-by-side en BTP encaja CAP.

1. Crear el proyecto

Necesitas Node.js (LTS) y las herramientas de CAP. Sirven tanto VS Code como SAP Business Application Studio.

terminal.sh
# once per machine
npm install -g @sap/cds-dk

# new project
cds init equipment-requests
cd equipment-requests
npm install

# start with live reload (in-memory SQLite + sample data)
cds watch

2. Modelo de datos

db/schema.cds describe los datos. Los aspectos cuid y managed añaden una clave UUID y los campos de creación/modificación: el equivalente CAP de los campos administrativos que escribimos a mano en RAP.

schema.cds
namespace sap.equipment;

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

entity Requests : cuid, managed {
  requestNo     : Integer @readonly;
  description   : String(80) @mandatory;
  equipmentType : String(20) @mandatory;
  estimatedCost : Decimal(15, 2);
  currency      : Currency;
  status        : String(1) default 'O' @readonly enum {
    Open     = 'O';
    Approved = 'A';
    Rejected = 'X';
  };
}

Los datos de ejemplo van en db/data/sap.equipment-Requests.csv; CAP los carga automáticamente:

sap.equipment-Requests.csv
ID;requestNo;description;equipmentType;estimatedCost;currency_code;status
6f1c2a90-1b3e-4c55-9d7a-0e7f5b1a2c01;1;Laptop for new developer;Laptop;1450.00;EUR;A
6f1c2a90-1b3e-4c55-9d7a-0e7f5b1a2c02;2;Ergonomic keyboard;Accessory;89.00;EUR;X
6f1c2a90-1b3e-4c55-9d7a-0e7f5b1a2c03;3;Docking station for meeting room;Docking;320.00;EUR;O

3. Definición del servicio

srv/service.cds expone una proyección como servicio OData V4. @odata.draft.enabled da el mismo manejo de borradores que with draft en RAP, y las dos acciones vinculadas se declaran directamente en la entidad.

service.cds
using { sap.equipment as db } from '../db/schema';

service EquipmentService @(path: '/equipment') {
  @odata.draft.enabled
  entity Requests as projection on db.Requests actions {
    action approve() returns Requests;
    action reject()  returns Requests;
  };
}

4. Lógica propia

srv/service.js registra handlers de eventos: el equivalente CAP de determinaciones, validaciones e implementaciones de acciones.

service.js
const cds = require('@sap/cds');

module.exports = class EquipmentService extends cds.ApplicationService {
  init() {
    const { Requests } = this.entities;

    // Next request number when a draft is activated (demo: MAX + 1)
    this.before('CREATE', Requests, async (req) => {
      const { max } = await SELECT.one`max(requestNo) as max`.from(Requests);
      req.data.requestNo = (max ?? 0) + 1;
    });

    // Validation – the message is attached to the field in Fiori
    this.before(['CREATE', 'UPDATE'], Requests, (req) => {
      const { estimatedCost } = req.data;
      if (estimatedCost !== undefined && estimatedCost <= 0) {
        req.error(400, 'Estimated cost must be greater than zero', 'estimatedCost');
      }
    });

    this.on('approve', Requests, (req) => this.setStatus(req, 'A'));
    this.on('reject', Requests, (req) => this.setStatus(req, 'X'));

    return super.init();
  }

  async setStatus(req, status) {
    const { Requests } = this.entities;
    const [{ ID }] = req.params;
    const request = await SELECT.one.from(Requests, ID).columns('status');
    if (!request) return req.reject(404, 'Request not found');
    if (request.status !== 'O') return req.reject(409, 'Only open requests can be approved or rejected');

    await UPDATE(Requests, ID).with({ status });
    return SELECT.one.from(Requests, ID);
  }
};
  • before('CREATE') → como una determinación al guardar: asigna el siguiente número.
  • before(['CREATE','UPDATE']) → validación; req.error asocia el mensaje al campo.
  • on('approve') / on('reject') → implementaciones de acciones con control de estado.

5. Ejecutar

Ejecuta cds watch. CAP compila el modelo, crea una base SQLite en memoria, carga el CSV y sirve el servicio, y se recarga con cada guardado.

cds watch: modelo cargado, datos desplegados, servidor escuchando en el puerto 4004
cds watch: modelo cargado, datos desplegados, servidor escuchando en el puerto 4004

Abre http://localhost:4004. La página de bienvenida muestra los endpoints y un enlace a la vista previa de Fiori.

La página de bienvenida de CAP y la respuesta OData
La página de bienvenida de CAP y la respuesta OData

Para probar sin interfaz, un archivo .http con la extensión REST Client es muy práctico:

requests.http
### List requests
GET http://localhost:4004/odata/v4/equipment/Requests?$filter=IsActiveEntity eq true

### Create a draft
POST http://localhost:4004/odata/v4/equipment/Requests
Content-Type: application/json

{ "description": "Second monitor", "equipmentType": "Monitor", "estimatedCost": 289.90, "currency_code": "EUR" }

### Activate the draft (replace <ID>)
POST http://localhost:4004/odata/v4/equipment/Requests(ID=<ID>,IsActiveEntity=false)/EquipmentService.draftActivate
Content-Type: application/json

{}

### Approve (replace <ID>)
POST http://localhost:4004/odata/v4/equipment/Requests(ID=<ID>,IsActiveEntity=true)/EquipmentService.approve
Content-Type: application/json

{}

6. Anotaciones de Fiori Elements

Como la metadata extension en RAP, las anotaciones de UI van en un archivo aparte, app/annotations.cds:

annotations.cds
using EquipmentService as service from '../srv/service';

annotate service.Requests with @(
  UI.HeaderInfo      : {
    TypeName       : 'Equipment Request',
    TypeNamePlural : 'Equipment Requests',
    Title          : { Value: description },
    Description    : { Value: requestNo }
  },
  UI.SelectionFields : [ requestNo, equipmentType, status ],
  UI.LineItem        : [
    { Value: requestNo,     Label: 'Request No.' },
    { Value: description,   Label: 'Description' },
    { Value: equipmentType, Label: 'Equipment Type' },
    { Value: estimatedCost, Label: 'Estimated Cost' },
    { Value: status,        Label: 'Status' },
    { $Type: 'UI.DataFieldForAction', Action: 'EquipmentService.approve', Label: 'Approve' },
    { $Type: 'UI.DataFieldForAction', Action: 'EquipmentService.reject',  Label: 'Reject' }
  ],
  UI.Facets          : [{
    $Type  : 'UI.ReferenceFacet',
    Label  : 'General Information',
    Target : '@UI.FieldGroup#General'
  }],
  UI.FieldGroup #General : { Data: [
    { Value: description },
    { Value: equipmentType },
    { Value: estimatedCost },
    { Value: currency_code },
    { Value: status }
  ]}
);

Recarga la vista previa de Fiori: list report, object page, borrador y los botones Approve/Reject ya están ahí.

7. Desplegar en BTP

Para producción, CAP añade con un solo comando SAP HANA Cloud, autenticación XSUAA, un approuter y un descriptor MTA:

deploy.sh
# production building blocks: SAP HANA Cloud, XSUAA, approuter, MTA descriptor
cds add hana,xsuaa,approuter,mta

# build the multi-target archive and deploy it to Cloud Foundry
npm install
mbt build
cf login -a https://api.cf.eu10.hana.ondemand.com
cf deploy mta_archives/equipment-requests_1.0.0.mtar

Tras el despliegue, asigna las role collections en el cockpit de BTP y añade la app al launchpad (SAP Build Work Zone).

Lo que conviene recordar

  • Con CAP pasas de una carpeta vacía a un servicio OData V4 con borradores en minutos.
  • Los conceptos se corresponden con RAP: entidades ↔ view entities, handlers ↔ behavior implementation, anotaciones ↔ metadata extensions.
  • Decide según dónde pertenece la lógica: dentro de S/4HANA → RAP; side-by-side en BTP → CAP.

Comentarios

Deja un comentario