Hay que ser muy friki para escribir un blog, pero más friki hay que ser para seguirlo 😜

Fundamentos técnicos para desarrollar un proyecto en CAP

Índice
Node Package Manager y NPM Registry
Core Data Services
Core Schema Notation
Definition languaje
Query languaje
CDS Commands
Associations y Compositions

Node Package Manager y NPM Registry

Node Package Manager (npm) y NPM registry de SAP son herramientas clave que puedes usar para facilitar el desarrollo en Node.js.

Node Package Manager

Node Package Manager (npm) es un repositorio online para publicar proyectos de código abierto en JavaScript, especialmente proyectos en Node.js. Los desarrolladores usan npm para compartir librerías de Node.js y proyectos publicados para npm en http://npmjs.org.

NPM Registry

SAP mantiene su propio registro NPM (http://npm.sap.com), donde, listos para usar, se encuentran disponibles paquetes específicos de SAP en Node.js.

Puedes usar el comando npm para configurar el registro destino al registro NPM de SAP con la siguiente sintaxis:

npm config set @sap:registry https://npm.sap.com

También puedes añadir @sap:registry=”https://npm.sap.com” al archivo de configuración .npmrc.

Digamos que estás creando un proyecto en Node.js y quieres usar herramientas y librerías comúnmente disponibles de Node.js. También puedes querer usar librerías específicas de SAP en Node.js, que es donde npm es útil. Sólo necesitas añadir las dedpendencias en tu archivo package.json y ejecutar el comando npm install. Este comando descarga todas las librerías Node.js mencionadas en el archivo package.json y las añade a nuestro proyecto.

NPM help

El comando más importante/útil es npm help. Este comando te da una vista general de todos los comandos posible npm, También puedes proporcionar un parámetro para saber más sobre ese comando específico. Por ejemplo, el comando npm help install mostrará detalles sobre el comanto npm install.

HelloWorld $ npm help

Usage: npm <command>

where <command> is one of:
    access, adduser, audit, bin, bugs, c, cache, ci, cit,
    clean-install, clean-install-test, completion, config,
    create, ddp, dedupe, deprecate, dist-tag, docs, doctor,
    edit, explore, fund, get, help, help-search, hook, i, init,
    install, install-ci-test, install-test, it, link, list, ln,
    login, logout, ls, org, outdated, owner, pack, ping, prefix,
    profile, prune, publish, rb, rebuild, repo, restart, root,
    run, run-script, s, se, search, set, shrinkwrap, star,
    stars, start, stop, t, team, test, token, tst, un,
    uninstall, unpublish, unstar, up, update, v, version, view,
    whoami

npm <command> -h  quick help on <command>
npm -l            display full usage info
npm help <term>   search for help on <term>
npm help npm      involved overview

Specify configs in the ini-formatted file:
    /home/user/.npmrc
or on the command line via: npm <command> --key value
Config info can be viewed via: npm help config

npm@6.14.6 /opt/nodejs/node-v10.22.0-linux-x64/lib/node_modules/npm
Npm install

npm install  es el comando más usado. Instala todos los módulos listados en el archivo package.json en una carpeta local llamada node_modules.

También puedes pasar un parámetro para instalar un componente específico, npm install @sap/cds instalará los módulos @sap/cds en la carpeta node_modules del actual directorio raíz del paquete (instalación local).

También puede pasar el parámetro -g para instalar estos módulos globalmente. Por ejemplo, npm install -g @sap/cds instalará el módulo @sap/cds como paquete global poniendo el contenido en la carpeta /usr/local o donde quieras que se instale el módulo.

npm start

El comando npm start ejecuta un comando arbitrario especificado en archivo package.json en la propiedad start dentro del objeto script. Si no se especifica la propiedad start en el archivo package.json, el sistema ejecutará el comando node server.js, que a su vez ejecuta el archivo server.js definido en el nivel raíz.

[wp-svg-icons icon=”bookmarks” wrap=”i”] Para más información consultad la página https://docs.npmjs.com/cli/npm

Core Data Services

Core data services (CDS) es un lenguaje de definición para crear modelos de datos y definiciones de servicios. Originándose con SAP HANA CDS, luego ABAP CDS, CDS ha encontrado su camino en SAP CAP.

CDS es una colección de lenguajes específico de dominios para ayudar a los desarrolladores a focalizarse en la lógica core del negocio al tener el framework que se encarga de las tareas repetitivas. CDS es la base para SAP CAP. Todos los modelos de datos y servicios están implementados usando CDS, que se analiza por SAP CAP en el correspondiente artefacto en tiempo de ejecución.

CDS ayuda a los desarrolladores a declarar definiciones de servicios, modelos de datos, consultas, y expresiones como modelos CDS. Modelos CDS son objetos planos en JavaScript que cumplen con Core Schema Notation (CSN).

Los modelos CDS pueden declararse en muchos lenguajes – por ejemplo, JavaScript Object (JSON), JavaScript plano, YAML Ain’t Marktup Language (YAML) – y pueden compilarse a varios lenguajes destino, por ejemplo, OData, SQL data definition (DDL), SAP HANA DDL, etc.

namespace my.bookshop;

entity Books {
  key ID : Integer;
  title  : String;
  stock  : Integer;
}

Core Schema Notation

Puedes usar Core Schema Notation (CSN) para crear representaciones compactas de JSON de los modelos CDS. Aunque CSN es similar a JSON, CSN tiene muchas más capacidades para capturar modelos completos y definiciones de servicios.

Cuando construyes/compilas un modelo CDS, el sistema genera el formato correspondiente CSN. CSN es el formato ideal y optimizado para compartir definiciones de modelos.  Puedes generar fácilmente modelos destino, por ejemplo, interfaces OData/EDM, interfaces OpenAPI, o modelos persistentes para SQL/NoSQL de CSN.

Modelo CDS:

namespace my.bookshop;

entity Books {
  key ID : Integer;
  title  : String;
  stock  : Integer;
}

Formato generado en CSN:

{
  "definitions": {
    "CatalogService": {
      "@source": "srv/cat-service.cds",
      "kind": "service"
    },
    "CatalogService.Books": {
      "kind": "entity",
      "@readonly": true,
      "query": {
        "SELECT": {
          "from": {
            "ref": [
              "my.bookshop.Books"
            ]
          }
        }
      },
      "elements": {
        "ID": {
          "key": true,
          "type": "cds.Integer"
        },
        "title": {
          "type": "cds.String"
        },
        "stock": {
          "type": "cds.Integer"
        }
      },
      "$syntax": "projection"
    },
    "my.bookshop.Books": {
      "kind": "entity",
      "elements": {
        "ID": {
          "key": true,
          "type": "cds.Integer"
        },
        "title": {
          "type": "cds.String"
        },
        "stock": {
          "type": "cds.Integer"
        }
      }
    }
  },
  "meta": {
    "creator": "CDS Compiler v1.35.0"
  },
  "$version": "1.0",
  "namespace": "my.bookshop"
}

Definition languaje

CDS es una colección de lenguajes específicos de dominio, y CDS Definition Language (CDL) es uno de los lenguajes CS más importantes. CDL permite a los desarrolladores definir modelos de datos en una sintaxis fácil de leer.

Namespace

Puedes especificar un espacio de nombre al principio del archivo CDS. Cada entidad creada tendrá el espacio de nombre como prefijo en su nombre, lo cual ayuda a evitar conflictos de nombre. En este ejemplo la entidad final inclurá el prefijo my.bookshop.Books.

namespace my.bookshop;

entity Books {
  key ID : Integer;
  title  : String;
  stock  : Integer;
}

Entities

Las entidades normalmente se usan para crear tablas en base de datos. En el despliegue final a base de datos, las entidades son analizadas para crear las tablas de base de datos.  El nombre de la tabla siempre está precedido por el espacio de nombres. Por ejemplo,  la entidad será traducida a una tabla de base de datos (my.bookshop) con tres columnas: ID, titlestock.

namespace my.bookshop;

entity Books {
  key ID : Integer;
  title  : String;
  stock  : Integer;
}

Fíjate que no hemos especificado ningún código relacionado con base de datos en nuestro ejemplo. La base de datos subyacente puede ser cualquier base de datos soportada, por ejemplo, SQLite o SAP HANA. En relación a la base de datos, ya que la sintaxis CDS es agnística, el framework CDS generará las tablas de base de datos basándose en la entidad.

Cutom types

Los custom types se usan para declarar tipos que se usan frecuentemente. Estos custom types se usan en las entidades u otros tipos. En nuestro ejemplo, hemos declarado el tipo Price, que se usa en la entidad Books.

type Prize{
    value : Decimal(10, 3);
    currency: Currency;
}

entity Books {
  key ID : Integer;
  title  : String;
  stock  : Integer;
  price  : Prize
}

Enums

Enums se usan para especificar los posibles valores de un tipo específico (como los típicos dominios en ABAP ♥). En nuestro ejemplo se muestra el tipo Genre que es un entero que solo accepta 5 valores, que se especifica usando la palabra clave enum.

type Genre : Integer enum {
      Action = 1;
      Comedy = 2;
      Drama  = 3;
      Fantasy = 4;
      Adventure = 5;  
};

entity Books {
  key ID : Integer;
  title  : String;
  stock  : Integer;
  price  : Prize;
  genre  : Genre
}

Aspects

Aspects en CDS ayudan a ampliar la modularidad permitiéndote agrupar comportamientos en partes manejables que pueden usarlas varias entidades. Esta característica está basada en el concepto de programación orientada a objetos. En nuestro ejemplo, managedBy es un aspect que se usa en la entidad Products que será traducida a la tabla de base de datos con cuatro columnas ID, title, createdAt y createdBy.

aspect managedBy {
    createdAt : Timestamp;
    createdBy : String
}

entity Product : managedBy {
    key ID    : Integer;
        title : String;
}

Views

Las entidades pueden exponerse como otras entidades usando los operadores as select from o as projection. Estas nuevas entidades se mapean a una vista SQL en la base de datos.

El siguiente ejemplo crea una nueva entidad usando los operadores as select fromas projection:

entity AllProducts as select from Products;
entity OneProduct as select from Products where Products.ID = 1;
entity MoreProducts as projection on Products;

La mayor diferencia entre estos dos operadores es que as select from permite cláusulas SQL, joins, y uniones, mientras as projection on no los permite.

Service Definitions

Puedes definir un servicio en CDS exponiendo entidades dentro de un bloque de servicio. Dentro de un bloque de servicio, puedes exponer entidades como proyecciones o entidades de los modelos e datos. Estas definiciones de vistas pueden usar tanto as select from comoo as projection on. Por ejemplo, el siguiente código generaría un servicio con dos entidades OData: ProductsProducts_1.

service ProductService {
    entity AllProducts as select from Products;

    entity OneProduct  as
        select from Products
        where
            Products.ID = 1;
}

Query language

CDS Query Language (CQL) , también conocido como CDS, es una extensión de SQL y actualizaciones SQL con algunas características realmente útiles, como postfix projections, expresiones de ruta, casts estilo CDL, y algo más.

Postfix Projections

CQL te permite usar una cláusula SELECT después de una cláusula FROM, dentro de corchetes. Como, por ejemplo, los siguientes estamentos son equivalentes:

entity OneProduct  as
    select from Products {ID, title}
    where
        Products.ID = 1;
entity OneProduct  as
    select ID, title from Products 
    where
        Products.ID = 1;

Path expressions

Puedes usar las expresiones de ruta para navegar a través de las asociaciones y elementos estructurados.

Asumamos que existe una asociación entre Products y SalesInvoice. La siguiente consulta encontrará sólo la SalesInvoice donde el ID de Product sea 1:

entity OneInvoice as select from SalesInvoice[ID=1].Products;
CDL-Style Casts

CDS no soporta el tipo de cast de estilo SQL. En su lugar, puedes usar casteos de estilo CDL. Como, por ejemplo, los siguientes dos estamentos son equivalentes:

select cast (price+100 as Decimal) as newPrice from Products;
select from Products { price+100 as newPrice : Decimal };

Los estamentos hacen exáctamente lo mismo (suman 100 al valor Price y saca este valor como un nuevo valor NewPrice de la entidad Products) pero con diferentes sintaxis.

SAP CDS Commands

CDS viene con una herramienta de línea de comandos también, que puede ser útil para los desarrolladores.

Antes de ejecutar comandos CDS, debes instalar la herramienta de interfaz de línea de comandos CDS (CLI). Instalaremos la herramienta CLI usando comandos npm.

Ejecuta los siguientes comandos para instalar la herramienta SAP CDS globalmente:

 

npm i -g @sap/cds
npm i -g -force @sap/cds-dk

Una vez que se ejecutan estos comandos, ejecuta cds version para validar si todo funciona correctamente. El resultado de salida debería incluir la versión CDS:

@sap/cds: 4.1.9
@sap/cds-compiler: 1.39.0
@sap/cds-dk: 3.0.0
@sap/cds-foss: 2.0.0
@sap/cds-reflect: 2.13.0
@sap/cds-runtime: 2.3.0
HelloWorld: 1.0.0
Node.js: v10.22.0

cds help

El comando cds help puede ser muy útil ya que este comando muestra todos los posibles comandos CDS y sus parámetros. Puedes usar este parámetro para aprender más sobre un comando específico, por ejemplo, usando icds help init.

cds init

El comando cds init genera la estructura de proyecto recomendada y crea las carpetas iniciales del proyecto, incluyendo las carpetas dbsrv, el archivo package.json, etc.

Por ejemplo, el comando init Howarts-Service creará una nueva estructura de proyecto para un proyecto llamado Howarts-Service:

cds deploy

El comando cds deploy despliega el modelo de datos a base de datos. Este comando sólo soporta (hasta la fecha) bases de datos SQLite y SAP HANA.

Uno de los parámetros más importantes para este parámetro es –to <database>, que especifica la base de datos destino.

cds deploy --to hana
cds deploy --to sqlite

El comando cds deploy siempre debería ejecutarse en la raíz del proyecto. Este comando es uno de los más complicados y ejecuta muchas actividades por detrás.

El comando cds deploy –to sqlite ejecuta las siguientes tareas:

  • Crea un archivo de base de datos local SQLite en un proyecto. El nombre y localización del fichero de base de datos depende del parámetro.
  • Elimina las tablas/vistas existentes y las vuelve a crear de a cuerdo al modelo CDS.
  • Despliega los valores del archivo separados por comas (CSV). Los archivos deben localizarse en las carpetas db/csv, db/data/, db/src/csv.

El comando cds deploy –to hana despliega las partes de base de datos del proyecto a una instancia SAP HANA en Cloud Foundry. Este comando ejecuta las siguientes tareas:

  • Compila el modelo CDS a un archivo SAP HANA.
  • Genera el archivo .hdbtabledata para el archivo CSV en el proyecto. Los archivos deben localizarse en las carpetas db/csv, db/data/, db/src/csv.
  • Crea un servicio en Cloud Foundry de tipo hdi-shared , que crea un contenedor SAP HANA Deployment Infraestructure (HDI). También puedes especificar explícitamente un nombre, por ejemplo con el comando cds deploy –to hana <nombre_de_mi_servicio>.
  • Pone el archivo default-env.json en la raíz del proyecto. Con esta información, el comando cds serve puede conecatrse al contenedor HDI en tiempo de ejecución.
  • Inicia localmente @sap/hdi-deploy. Las credenciales del despliegue se encuentran en el archivo generado db/default-env.json en el paso previo. Estas credenciales contienen todas las claves secretas para conectar el contenedor HDI.

cds compile

El comando cds compile compila los modelos CDS a una salida destino específica. Pasarás, como parámetro, el modelo que quieres procesar y especificar el formato de salida. Por ejemplo, el comando cds compile products.cds –to json compila el modelo CDS del archivo products.cds al formato JSON.

cds compile products.cds --to json
cds compile products.cds --to sql
cds compile products.cds --to hana
cds compile products.cds --to yaml

El uso principal del comando cds compile es verificar la salida destino. Por ejemplo, el comando cds compile products.cds -to sql devuelve las sentencias DDL creados de nuestro modelo, y estas sentencias son las sentencias reales que se pueden usar una vez la aplicación esté subida y ejecutándose y la base de datos haya sido creada. Con este comando, puedes verificar qué se creará exactamente durante el despliegue.

cds watch

El comando cds watch es particularmente útil para ejecutar nodemon. Esta herramienta busca cambios en cualquier parte del proyecto y ejecuta (o re-ejecuta) el servidor con cada cambio detectado. Este comando también despliega el modelo de datos a una base de datos in-memory SQLite antes de ejecutar el servidor.

Associations y Compositions

Usarás las asociaciones y composiciones para mantener las relaciones entre entidades.

Associations

En una asociación, todos los objetos tienen su propio ciclo de vida, y no hay propiedad. Veamos el ejemplo de un libro y un autor. Un libro puede asociarse con uno o más autores. De manera similar, un autor puede asociarse con uno o más libros. Sin embargo, ambos libros y autores pueden existir independientemente y tener diferentes ciclos de vida, y no hay propiedad entre ellos.

Asociaciones sin gestión

En una asociación sin gestión, especificarás la cláusula de enlace usando una clave foránea. La entidad Books tiene una asociación sin gestión con la entidad Authors.

entity Books {
    key ID    : Integer;
        title : String;
        stock : Integer;
        price : Prize;
        genre : Genre;
        author_ID: Integer;
        authod: Association to Authors on ID = author_ID;
}

entity Authors {
    key ID   : Integer;
<EntityType Name="Books">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.Int32" Nullable="false"/>
<Property Name="title" Type="Edm.String"/>
<Property Name="stock" Type="Edm.Int32"/>
<Property Name="price_value" Type="Edm.Decimal" Scale="3" Precision="10"/>
<Property Name="genre" Type="Edm.Int32"/>
<Property Name="author_ID" Type="Edm.Int32"/>
<NavigationProperty Name="author" Type="CatalogService.Authors">
<ReferentialConstraint Property="author_ID" ReferencedProperty="ID"/>
</NavigationProperty>
</EntityType>

Si validas el servicio OData usando el operador $expand, verás los detalles sobre los autores asociados a Books.

/catalog/Books?$expand=author

{
"@odata.context": "$metadata#Books(author())",
"value": [
{
"ID": 1,
"title": "Wuthering Heights",
"stock": 100,
"price": null,
"genre": 2,
"author_ID": 2,
"author": {
"ID": 2,
"name": "M. Rajoy"
}
},
{
"ID": 2,
"title": "Jane Eyre",
"stock": 500,
"price": null,
"genre": 3,
"author_ID": 1,
"author": {
"ID": 1,
"name": "Jhon Smith"
}
}
]
}

Asociaciones con M-1

En una asociación con M-1, no puedes especificar una cláusula de enlace. En este caso, el CDS automáticamente resuelve y añade los elementos de clave foránea de la clave primaria de la entidad origen. En el ejemplo, la entidad Books tiene una asoiación con M-1 con la entidad Authors:

entity Books {
    key ID    : Integer;
        title : String;
        stock : Integer;
        price : Decimal(10, 3);
        genre : Genre;
        author_one: Association to Authors //asociación M-1
}
<NavigationProperty Name="author_one" Type="CatalogService.Authors">
<ReferentialConstraint Property="author_one_ID" ReferencedProperty="ID"/>
</NavigationProperty>
<Property Name="author_one_ID" Type="Edm.Int32"/>
</EntityType>

Este ejemplo es equivalente al ejemplo de la asociación sin gestión de antes excepto porque la clave foránea author_one_ID se añade automáticamente a la entidad Books cuando el modelo CDS se despliega a base de datos.

Aquí os dejo, dentro de una misma Entidad dos asociaciones de este tipo.

entity Books {
    key ID    : Integer;
        title : String;
        stock : Integer;
        price : Decimal(10, 3);
        genre : Genre;
        author_ID: Integer;
        author_no: Association to Authors on author_no.ID = author_ID; //asociación no gestionada
        author_one: Association to Authors //asociación M-1
}
<EntityType Name="Books">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.Int32" Nullable="false"/>
<Property Name="title" Type="Edm.String"/>
<Property Name="stock" Type="Edm.Int32"/>
<Property Name="price" Type="Edm.Decimal" Scale="3" Precision="10"/>
<Property Name="genre" Type="Edm.Int32"/>
<Property Name="author_ID" Type="Edm.Int32"/>
<NavigationProperty Name="author_no" Type="CatalogService.Authors">
<ReferentialConstraint Property="author_ID" ReferencedProperty="ID"/>
</NavigationProperty>
<NavigationProperty Name="author_one" Type="CatalogService.Authors">
<ReferentialConstraint Property="author_one_ID" ReferencedProperty="ID"/>
</NavigationProperty>
<Property Name="author_one_ID" Type="Edm.Int32"/>
</EntityType>

/catalog/Books?$expand=author_no,author_one

{
"@odata.context": "$metadata#Books(author_no(),author_one())",
"value": [
{
"ID": 1,
"title": "Wuthering Heights",
"stock": 100,
"price": null,
"genre": 2,
"author_ID": 2,
"author_one_ID": 3,
"author_no": {
"ID": 2,
"name": "M. Rajoy //asociación no gestionada"
},
"author_one": {
"ID": 3,
"name": "Lola Flores //asociación M-1"
}
},
{
"ID": 2,
"title": "Jane Eyre",
"stock": 500,
"price": null,
"genre": 3,
"author_ID": 1,
"author_one_ID": 4,
"author_no": {
"ID": 1,
"name": "Jhon Smith //asociación no gestionada"
},
"author_one": {
"ID": 4,
"name": "Paquita Salas //asociación M-1"
}
}
]
}

Asociaciones M-N

En una asociación M-N, un libro está asociado con uno o muchos autores. En este caso, la entidad Authors contiene un vínculo de vuelta (digamos un libro), que se especifica en la entidad Books usando el operador on.

Este vínculo de vuelta es una asociación M-1 con la parte “many” que vincula de vuelta a la parte 1.

entity Books {
    key ID    : Integer;
        title : String;
        stock : Integer;
        price : Decimal(10, 3);
        genre : Genre;
        author_many: Association to many Authors on author_many.book = $self;
}

entity Authors {
    key ID   : Integer;
        name : String;
        book: Association to Books;
}

La principal diferencia entre una asociación M-1 y una M-N desde una perspectiva de tablas de base de datos es que, en caso de que la asociación sea M-1, la clave foránea author_ID se añade automáticamente a la entidad Books. Sin embargo, en caso de que sea M:N, se añade automáticamente la clave foránea book_id a la entidad Authors.

<EntityType Name="Authors">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.Int32" Nullable="false"/>
<Property Name="name" Type="Edm.String"/>
<NavigationProperty Name="book" Type="CatalogService.Books" Partner="author_many">
<ReferentialConstraint Property="book_ID" ReferencedProperty="ID"/>
</NavigationProperty>
<Property Name="book_ID" Type="Edm.Int32"/>
</EntityType>

Ya que se ha creado un vínculo de vuelta, tanto la entidad Authors y la entidad Books pueden usar el parámetro $expand;

/catalog/Books?$expand=author_many

{
"ID": 3,
"title": "La biblia //gestión M-N",
"stock": 4,
"price": null,
"genre": 0,
"author_ID": 0,
"author_one_ID": null,
"author_many": [
{
"ID": 5,
"name": "San Juan",
"book_ID": 3
},
{
"ID": 6,
"name": "San Pedro",
"book_ID": 3
}
]
}

/catalog/Authors?$expand=book

{
"ID": 5,
"name": "San Juan",
"book_ID": 3,
"book": {
"ID": 3,
"title": "La biblia //gestión M-N",
"stock": 4,
"price": null,
"genre": 0,
"author_ID": 0,
"author_one_ID": null
}
},
{
"ID": 6,
"name": "San Pedro",
"book_ID": 3,
"book": {
"ID": 3,
"title": "La biblia //gestión M-N",
"stock": 4,
"price": null,
"genre": 0,
"author_ID": 0,
"author_one_ID": null
}
}

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

One comment

  1. Pingback: Crear una aplicación con Node.JS usando CAP paso a paso – SAP Workbench

Leave a Reply

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