#SAP CAP#Node.js#CDS#BTP

Handlers en CAP: escribe menos JavaScript, no más

El runtime genérico de CAP ya resuelve el CRUD. Los handlers en Node.js son para la lógica que el modelo no puede expresar: acciones, funciones y validaciones. Con un cancelOrder real.

Por Logali Tech 2 min de lectura

El malentendido más caro con CAP es creer que hay que programar cada operación. No. El runtime genérico de CAP ya sirve create, read, update, delete, paginación, filtros $filter, expansiones $expand y hasta value helps, todo derivado del modelo CDS. Si estás escribiendo un handler para un READ que solo lee la tabla, lo estás haciendo mal.

Dónde acaba el modelo y empieza el código

Los handlers en Node.js son para lo que el modelo no puede declarar:

  • Validaciones de negocio que dependen del estado (no de un @mandatory).
  • Acciones y funciones: operaciones que no son CRUD.
  • Efectos colaterales: enviar un correo, llamar a un servicio externo, recalcular.
flowchart TD
  R["Petición OData"] --> G["¿Es CRUD puro?"]
  G -->|Sí| H["Runtime genérico - sin código"]
  G -->|No| C["Handler en srv/service.js"]
  C --> B["Lógica de negocio y acciones"]

Funciones y acciones: se declaran en CDS

Primero declaras el contrato en el .cds, ligado a la entidad:

extend service CatalogService with {
  function getClientTaxRate(clientEmail : String(65)) returns Decimal(4,2);

  action cancelOrder(clientEmail : String(65)) returns {
    status  : String enum { Succeeded; Failed; };
    message : String;
  };
}

Fíjate en la distinción que CAP toma en serio: una función (getClientTaxRate) lee y no cambia estado; una acción (cancelOrder) modifica. No es cosmética: OData mapea la función a GET y la acción a POST.

El handler: solo la lógica que importa

En srv/catalog-service.js implementas el comportamiento con la API srv:

module.exports = (srv) => {
  const { Orders } = srv.entities;

  srv.on('cancelOrder', async (req) => {
    const { clientEmail } = req.data;
    const order = await SELECT.one.from(Orders).where({ clientEmail });

    if (!order) return req.error(404, 'Pedido no encontrado');
    if (order.Status === 'A') {
      return { status: 'Failed', message: 'El pedido ya estaba aprobado' };
    }

    await UPDATE(Orders).set({ Status: 'C' }).where({ ID: order.ID });
    return { status: 'Succeeded', message: `Cancelado para ${order.FirstName} ${order.LastName}` };
  });
};

Tres cosas que hacen bien este handler:

  1. Usa CQL (SELECT, UPDATE) en vez de SQL crudo: agnóstico de la base de datos.
  2. Devuelve errores con req.error, no lanza excepciones sueltas.
  3. No reimplementa el CRUD de Orders: solo la regla de cancelación. Todo lo demás lo sigue sirviendo el runtime.

El anti-patrón habitual

Handlers srv.on('READ', ...) que hacen un SELECT y devuelven exactamente lo que el runtime ya devolvería. Eso no aporta nada y te quita paginación y filtrado gratis. Si tu handler no añade una regla que el modelo no puede expresar, bórralo.

En CAP el mejor handler es el que no existe. El segundo mejor es el que cabe en quince líneas porque solo contiene la regla de negocio.

En el próximo post: CAP contra RAP, cuándo cada uno, sin bandos.

¿Te ha resultado útil?

Comentarios

    Deja un comentario

    Responsable: Logali Tech. Finalidad: moderar y publicar tu comentario y, si dejas tu correo, avisarte. Derechos: acceso, rectificación y supresión en [email protected].