Hay que ser muy friki para escribir un blog, pero m谩s friki hay que ser para seguirlo 馃槣

Crear una aplicaci贸n con Node.JS usando CAP paso a paso

Por favor lee esta entrada antes de crear el proyecto y aseg煤rate de tener todos los conceptos claros antes de seguir [wp-svg-icons icon=”wink” wrap=”i”].

Modelo de datos

Entramos en el Business Application Studio y ejecutamos:

cds init Hogwarts-Service

Con esto crearemos toda la estructura del proyecto dentro de la carpeta聽Hogwarts-Service recomendada por CAP:

  • app: esta carpeta contendr谩 todas las apps UI.
  • db: esta carpeta contendr谩 todo el contenido relacionado con base de datos.
  • srv: esta carpeta contendr谩 la implementaci贸n del servicio.
  • packaje.json: Este archivo es el descriptor del proyecto y contiene los metadatos del proyecto, por ejemplo, la versi贸n del proyecto, dependencias, etc.

Nos posicionamos dentro del directorio que acabamos de crear:

cd Hogwarts-Service

Abre el archivo聽package.json y presta atenci贸n al metadata del proyecto, especialmente a la linea que empieza por聽@sap/cds.聽Esta l铆nea indica que nuestro proyecto depende de聽@sap/cds.

npm install

Ejecutamos el comando聽npm install para instalar todos los m贸dulos listados como dependencias en el聽packaje.js. Las dependencias se instalar谩n en la carpeta聽node_modules.

Ahora vamos a crear el modelo de datos para las posiciones en el fichero .cds.

Para hacer la aplicaci贸n escalable, primero vamos a crear la entidad聽Producto. La entidad Producto es una entidad gen茅rica y podemos reusarla, no solo para las pociones, sino que tambi茅n para otro tipo de productos.

Entidad productOS
namespace db.HogwartsService;

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

type Puntuacion : Integer enum {
    Mejor = 5;
    Bien  = 4;
    Media = 3;
    Pobre = 2;
    Peor  = 1;
}

type Peso : {
    cantidad : Decimal(4, 2);
    unidad   : Unidades;
}

type Unidades : String enum {
    Kilogramo  = 'KG';
    Gramo      = 'G';
    Litro      = 'L';
    Centilitro = 'CL';
    Mililitro  = 'ML';
}

entity Productos : cuid, managed {
    nombre     : String;
    puntuacion : Decimal(2,1)
}

 

  • Usamos el espacio de nombre db.HogwartsService.聽El espacio de nombres se especifica al principio del fichero CDS. T茅cnicamente, s贸lo son prefijos fijados autom谩ticamente delante del nombre de una entidad. Cada entidad que crees tendr谩 el espacio del nombre prefijado en el nombre.
  • La entidad Productos聽hereda campos de la entidad聽cuid聽y managed, que se definen en el fichero聽common.cds bajo聽@sap/cds.
  • Las anotaciones聽de聽@cds.on.insert y聽@sap.on.update llenan el valor en los elementos correspondientes autom谩ticamente.
  • Las variables聽$now y聽$user se llaman聽pseudo variables. La variable聽$now se reemplaza con el tiempo del servidor en UTC mientras que la variable聽$user se reemplaza con el ID del usuario actual.

El punto m谩s importante, sin embargo, es entender el poder del CDS. Con un par de tecleos, puedes crear una entidad bastante potente que tiene un identificador universal 煤nico (UUID) como clave y campos relacionados con la gesti贸n que tambi茅n se pueden rellenar autom谩ticamente.

ENtidad pociones

Para crear la entidad聽Pociones, crea un fichero llamado聽pociones.cds en la carpeta聽db.

namespace db.HogwartsService;

using {
    db.HogwartsService
} from '../db/productos';
using {
    cuid,
    managed
} from '@sap/cds/common';


entity Pociones :  HogwartsService.Productos {
    peso       : HogwartsService.Peso;
    referencia : String;
    proveedor  : Association to Proveedores
}

entity Proveedores : cuid {
    nombre     : String;
    puntuacion : HogwartsService.Puntuacion
}

entity Reviews : cuid, managed {
    puntuacion: HogwartsService.Puntuacion;
    objeto: UUID;
}
  • La entidad聽Pociones hereda de la entidad聽Productos. Como resultado,聽Pociones tendr谩 sus propios campos (por ejemplo referencia, peso, etc)
  • La entidad聽Posiciones tiene una asociaci贸n con聽Proveedores.
  • Tambi茅n hemos definido una entidad para聽Reviews. el campo puntuacion es un entero que s贸lo debe contener valores del 1 al 5. Por eso, hemos usado enum.
Servicio

Creamos un fichero llamado聽pociones-srv.cds en la carpeta聽srv.

namespace srv.HogwartsService;

using {db.HogwartsService} from '../db/pociones';

service ArmarioSnapeSrv {
    entity Pociones    as projection on HogwartsService.Pociones;
    entity Proveedores as projection on HogwartsService.Proveedores;
    entity Reviews     as projection on HogwartsService.Reviews
}
  • La ruta聽../db/pociones referencia al archivo pociones.cds, que contiene las entidades usadas para definir el servicio.
  • Hemos expuesto las tres entidades:聽Pociones, Proveedores聽y聽Reviews.
L贸gica custom con funciones node.js

Cada vez que una entidad聽Reviews se cree, actualice o modifique, queremos que el sistema calcule la media de la review y actualice el campo puntuaci贸n de la entidad聽Pocion. Este tipo de l贸gica custom puede implementarse f谩cilmente creando un archivo聽.js con el mismo nombre que聽pociones-srv.cds (p.e.聽pociones-srv.js). Dentro de este fichero, codificaremos una funci贸n Node.js para actualizar el campo聽puntuacion de聽Pociones聽cada vez que se cree, modifique o borre聽Reviews.

Creamos el nuevo archivo聽pociones-srv.js en la carpeta聽srv.

const cds = require('@sap/cds')
module.exports = srv => {
    srv.after(['CREATE', 'UPDATE', 'DELETE'], 'Reviews', async (_, req) => {
        const { subject } = req.data
        const tx = cds.transaction(req)

        const result = await tx.run(SELECT.one(['round(avg(puntuacion),1) as puntuacion']).from('db.HogwartsService.Reviews').where({ subject }))
        if (result) {
            req.on('succeded', () => {
                const payload = { subject, puntuacion: result.puntuacion }
                cds.run(UPDATE(db.HogwartsService.Pociones).set({ puntuacion: result.puntuacion }).where({ ID: payload.subject }))
            })
        }
    })
}
sqlite

Necesitaremos una base de datos persistente. Para ello utilizaremos SQLite, ejecuta los siguientes comandos:

npm i sqlite3 -D
cds deploy --to sqlite:db/pociones-service.db

Estos comandos crean una base de datos persistemente almacenada en el archivo local聽pociones-service.db en la carpeta聽db.聽La configuraci贸n tambi茅n se actualiza en el archivo聽package.json.

Se pueden usar varios comandos SQLite para validar que pas贸 mientras de deployaba la base de datos. Ejecuta los siguientes comandos y explora la salida:

sqlite3 db/pociones-service.db .dump
sqlite3 db/pociones-service.db .tables

El primer comando saca toda la informaci贸n de la base de datos. El segundo lista todas las tablas. En ambos comandos especificamos la ubicaci贸n de la base de datos, la cual es聽db/pociones.db.

Probar el servicio localmente

Abre un terminal y ejecuta el comando cds watch, para ejecutar la herramienta nodemon.

El servidor CDS se iniciar谩, y el sistema mostrar谩 la URL para para acceder al servicio. Ya que estamos ejecutando el servicio localmente, la URL es聽http://localhost:4004

Validaremos este servicio en detalle accediendo al metadata. Haz click en el enlace聽$metadata.

  1. La versi贸n del OData es la V4.
  2. El servicio OData tiene tres entidades:聽Pociones, Proveedores聽y聽Reviews.
  3. La entidad聽Pociones tiene todos los campos definidos en la entidad聽Pociones en el archivo聽pociones.cds m谩s los campos heredados de la entidad聽Productos.
  4. La entidad聽Pociones navega a la entidad聽Proveedores como definimos en el archivo pociones.cds.
TEST

Ejecutemos algunas operaciones crear, leer, actualizar y borrar (CRUD) usando este servicio.

  1. Crea una nueva carpeta llamada聽tests y crea el archivo聽pociones.http
  2. Dentro del archivo pegamos este c贸digo que no es m谩s que una petici贸n POST
    Content-Type: application/json
    
    { "nombre":"Los m谩s finos productos de limpieza de Madame Glossy S.R.L.", "puntuacion": 4 }

    Hacemos click en enviar petici贸n y nos crear谩 el primer proveedor

    {
    "@odata.context": "$metadata#Proveedores",
    "value": [
    {
    "ID": "e5efa590-4ff0-491d-918c-c9cb122b0ec3",
    "nombre": "Los m谩s finos productos de limpieza de Madame Glossy S.R.L.",
    "puntuacion": 4
    }
    ]
    }
  3. Ahora vamos a crear una entrada de聽Pociones. Ya que la entrada聽Pociones tiene un campo (proveedores_ID) que apunta al聽ID de la entidad de Proveedores,聽copiamos el ID del proveedor que acabamos de generar.
    POST http://localhost:4004/armario-snape-srv/Pociones HTTP/1.1
    Content-Type: application/json;IEEE754Compatible=true
    
    { "peso_cantidad": "1", "peso_unidad":"L", "nombre":"Abrillantador para zapatos autopulibles de Madame Glossy", "referencia":"37774LLK9", "proveedor_ID":"1e5a7335-efa6-457d-adb2-add19e7d3028"}
    
    

    La respuesta tambi茅n muestra los campos聽ID, createdAt, createdBy, modifiedAt,聽y聽modifiedBy que han sido autom谩ticamente rellenados. Ya que no hemos proporcionado una puntuaci贸n, este campo contiene valor聽null.

  4. Ahora vamos a ejecutar una operaci贸n POST en el tercer servicio, Reviews.聽Copia el valor del聽ID de la entidad聽Pociones que acabamos de crear, que deber铆a ser el valor del campo聽objeto en la entidad聽Reviews.
    Primero, primero ejecutamos una operaci贸n聽POST en el servicio聽Reviews. Esta operaci贸n聽POST crear谩 un registro en la entidad聽Reviews. Implementamos una l贸gica custom para actualizar el campo聽puntuacion de la entidad聽Pociones cada vez que se cree una entidad聽Reviews. Por lo que, esta operaci贸n聽POST actualizar谩 el campo聽puntuaciones聽de聽Pociones.

    POST  http://localhost:4004/armario-snape-srv/Reviews HTTP/1.1
    Content-Type: application/json
    
    { "puntuacion":"3", "objeto": "93b50087-f648-4580-acbf-5cc6dbdec0e9" }

    Para verificar que nuestra l贸gica custom haya funcionado, ejecutaremos una operaci贸n聽GET sobre la entidad聽Pociones y validaremos el campo puntuacion.

    {
    "@odata.context": "$metadata#Pociones",
    "value": [
    {
    "ID": "8df7e852-f211-49a1-bc88-87be4d83d5ca",
    "createdAt": "2020-09-25T18:12:01.774Z",
    "createdBy": "anonymous",
    "modifiedAt": "2020-09-25T18:28:29.873Z",
    "modifiedBy": "anonymous",
    "nombre": "Abrillantador para zapatos autopulibles de Madame Glossy",
    "puntuacion": 4,
    "peso_cantidad": 1,
    "peso_unidad": "L",
    "referencia": "37774LLK9",
    "proveedor_ID": "33aa19c2-d337-4bb3-9fcf-16278949958a"
    }
    ]
    }
DESPLIEGUE

Hasta ahora hemos ejecutado y probado nuestros servicios en una base de datos SQLite. Ahora, cambiemos a SAP HANA. No necesitas cambiar ning煤n c贸digo de SQLite a SAP HANA. S贸lo necesitas cambiar el valor de configuraci贸n del archivo聽package.json a “hana”.

Tambi茅n necesitas a帽adir la dependencia de SAP HANA ejecutando el comando聽@sap/hana-client.

Este comando a帽adir谩 como dependencia聽@sap/hana-client en el archivo聽package.json y tambi茅n instalar谩 la dependencia localmente dentro de la carpeta聽node_modules.

{
  "name": "Hogwarts-Service",
  "version": "1.0.0",
  "description": "A simple CAP project.",
  "repository": "<Add your repository here>",
  "license": "UNLICENSED",
  "private": true,
  "dependencies": {
    "@sap/cds": "^4",
    "@sap/hana-client": "^2.5.109",
    "express": "^4"
  },
  "devDependencies": {
    "sqlite3": "^4.2.0"
  },
  "scripts": {
    "start": "npx cds run"
  },
  "cds": {
    "requires": {
      "db": {
        "kind": "hana",
        "credentials": {
          "database": "db/pociones-service.db"
        }
      }
    }
  }
}

Ejecuta el comando聽cds build para construir el proyecto.

Este comando genera una tabla SAP HANA, define vistas, y crea un archivo聽manifest.yaml. Dentro del archivo聽manifest.yaml, puedes ver una entrada para el enlace del servicio para SAP HANA.

[wp-svg-icons icon=”pencil-2″ wrap=”i”] Desplegar aplicaciones a SAP Cloud Platform crear谩 tablas SAP HANA y vistas como se especificaron por las entidades definidas en el archivo CDS. Para crear estos artefactos de base de datos, es obligatorio una definici贸n espec铆fica de base de datos. Por ejemplo, para crear una tabla en SAP HANA, es obligatorio un archivo聽.hdbcds. CDS proporciona proporciona una herramienta para generar todos los archivos de base de datos obligatorios para generar los artefactos finales (p. ej. las tablas necesarias). Esta caracter铆stica ayuda a escribir c贸digo agn贸stico de la base de datos.

Otro archivo importante que genera el comando聽cds build es el archivo聽manifest.yaml. Este archivo es obligatorio para todas las aplicaciones Cloud Foundry. El comando聽cds build autom谩ticamente genera este archivo y mantiene importantes configuraciones.

Ejecuta el siguiente comando para crear una instancia de servicio de SAP HANA:

cd create-service hanatrial hdi-shared Hogwarts-Service-db

Finalmente, ejecuta el siguiente comando para desplegar nuestra app:

cf push -f gen/db
cf push -g gen/srv --random-route

Una vez que hayamos desplegado la aplicaci贸n en la propiedad聽route tendremos la direcci贸n de acceso a nuestro servicio.

[wp-svg-icons icon=”github-3″ wrap=”i”] https://github.com/saradasilva/sapworkbench-CAP-HogwartsSRV

Leave a Reply

Your email address will not be published. Required fields are marked *