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:
- Usa CQL (
SELECT,UPDATE) en vez de SQL crudo: agnóstico de la base de datos. - Devuelve errores con
req.error, no lanza excepciones sueltas. - 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?
Gracias por tu valoración.
Comentarios
Aún no hay comentarios.