miércoles, 28 de diciembre de 2016

Cómo extraer los datos de SAP: Gateway y servicios oData (y II)

Queremos hacer una aplicación megachula en SAPUI5 en plan profesional. Hemos pintado la aplicación (ya sea en una servilleta de papel o en una aplicación de diseño como el BUILD, disponible como servicio del SAP HCP) y luego hemos picado el código con el Web IDE.

Imaginemos que ya tenemos esa aplicación todo pintona, pero sólo nos muestra datos ficticios (el mock data que llaman), que hemos cargado mediante código o leyendo un fichero plano en formato JSON.

Entonces nos preguntamos, ¿cómo hacemos para mostrar datos de verdad? Pues necesitaremos una fuente de la que extraigamos los datos, que en nuestro caso será un servidor SAP. Y como ya vimos anteriormente la teoría (básica, muy básica) de los servicios oData y el SAP NetWeaver Gateway 2.0, ahora vamos a ver si somos capaces de ponerlo en práctica.


Crear el servicio

Vamos a crear un servicio sencillito, uno que muestre varias líneas de texto en un determinado idioma. Esas líneas de texto las tendremos guardadas en unas tablas en SAP. Podrían ser textos OTR o cualquier otra cosa, pero no nos vamos a complicar y crearemos una tabla específicamente para el ejemplo.

El servicio que queremos tendrá sólo una entidad, que es la que nos permitirá obtener los textos dependiendo de nuestro idioma. Cada entrada tendrá los siguientes datos:

  • Un identificador unívoco. Será un campo clave.
  • Un idioma, para saber si el texto está en español, inglés o klingon. También será un campo clave.
  • El texto en sí.

Los campos clave son aquellos que nos permiten identificar unívocamente cada entrada. Eso quiere decir que si yo tengo un id específico (el 1234) y un idioma (ES), el resultado será único. No puedo obtener varias cadenas de texto con ese mismo id e idioma.

Los pasos que seguiremos serán los siguientes:
  • Debemos tener cierta autorización en SAP  (S_RFCACL) y un alias de sistema que nos conecte el Front-End y el Back-End, incluso aunque ambos sean el mismo sistema.
  • Por cada entidad que necesitemos en nuestro servicio, necesitaremos una estructura, que crearemos por la SE11. Esta estructura es la que nos va a permitir mapear los datos extraídos en SAP con la información que mostrará el servicio.
  • El servicio lo crearemos mediante un proyecto en la transacción SEGW (Gateway Builder). Aquí definiremos las entidades y colecciones que usaremos, así como las propiedades (campos) de cada entidad. Cada entidad estará enlazada con una estructura creada en el punto anterior. Cuando activemos el servicio, se nos generarán por detrás unas clases ABAP con las que trabajaremos para obtener los datos de SAP: las Data Provider Classes, DPC, y Model Provider Classes, MPC.
  • Para que podamos usar el servicio, debemos activarlo. Eso lo hacemos en la transacción /n/IWFND/MAINT_SERVICE... ¿que por qué pongo el /n delante? Prueba a ponerlo sin /n o sin /o a ver qué pasa :P.
    Aquí activaremos el servicio oData, indicaremos un alias de sistema y lo publicaremos al exterior, activándolo en la SICF,
  • Para que el servicio oData entienda lo que le preguntan (la solicitud realizada por la aplicación web) y sepa lo que tiene que enviar, tiraremos código en las clases que nos ha generado el proyecto. Y no nos preocuparemos por tener que parsear la URL que nos llega, ni por tener que generar un fichero XML. Lo haremos todo en ABAP y es el Gateway el que hará de traductor oficial con la aplicación web.

Permisos y alias de sistema

Antes de nada, vamos a necesitar un alias de sistema para que el Gateway sepa donde consultar los datos del servicio que expone. Si alguien ya ha montado Fiori o alguna aplicación independiente antes que nosotros,  seguramente ya estará creado y podemos utilizarlo. Pero a lo mejor nos toca crearlo a nosotros. Es más, si nadie ha montado ningún servicio oData hasta ahora puede que la parametrización esté en pelotas y este tutorial se quede corto.

El alias de sistema es una parametrización que nos indica dónde está realmente el servicio oData. ¿Qué quiere decir esto? Pues que el servicio oData realmente se genera en un Back-End, que es el servidor SAP donde están los datos. Pero la aplicación que llama al servicio está en el Front-End, que es otro servidor SAP. El Gateway del Front-End usa los alias para saber en qué servidor está cada servicio oData.

Por supuesto, podemos usar como Back-End y Front-End el mismo servidor (distribución integrada), pero el alias lo necesitaremos igualmente.

El alias se puede crear en la IMG, SAP NetWeaver -> Gateway -> OData Channel -> Configuration -> Connection Settings -> SAP NetWeaver Gateway to SAP System -> Manager SAP System Aliases.

Cada entrada apunta a una back-end en particular. Si nuestro Back-End y Front-End es el mismo, nos valdría algo como lo siguiente:
  • SAP System Alias: LOCAL. O el nombre que quieras.
  • Local GW: Marcado.
  • RFC Destination: NONE. Apunta a sí mismo.
  • Software version: DEFAULT.
Podríamos crearnos un alias usando una RFC o indicando directamente un sistema y un mandante, pero esto último en un entorno de desarrollo-calidad-productivo nos daría problemas porque esta tabla se transporta, y el alias se asigna al servicio oData... así que al final tendríamos un servicio oData en producción asignado a un alias de sistema que apunta a un back-end de desarrollo. Así que mejor tirar de RFC que de sistema-mandante.

Además de tener en cuenta el alias de sistema, debemos tener en cuenta los permisos.

Digamos que trabajamos en un entorno de desarrollo donde tenemos SAP_ALL. Genial, estamos en modo Dios, ¿todo solucionado? Pues no.

Para los servicios oData se utiliza un objeto de autorización que no está incluido en el SAP_ALL. así que tenemos que asignárnoslo igualmente. Este objeto de autorización es el S_RFCACL. Por no entrar en detalles de configuración, podemos crearnos un rol con este objeto y todo asteriscos, sólo para pruebas. Lo suyo es generarlo bien y no con *, pero no es el objetivo de la práctica.

También se necesita una autorización específica para cada servicio oData creado, pero como todavía no tenemos nuestro servicio creado, ¿cómo narices vamos a crear esta autorización? Eso lo vemos una vez creemos el servicio. Se puede obtener más información en este enlace.

La estructura y la tabla donde tenemos los datos

Para poder trabajar en SAP con los datos, nos crearemos una estructura en el diccionario de datos (la SE11). En nuestro caso, sería algo como esto.

 El mandante no haría falta en la estructura, es que se me ha colado ;)
Después, nos creamos una tabla con la misma estructura (aquí sí que iría mandante, claro). El Id y el idioma (spras) serán campos clave.  Hacemos la tabla de tipos aplicación, le creamos una vista... vamos, lo normal para que podamos introducir datos por la SM30 y no nos pida orden de transporte.

Como ves, entradas muy curradas y sofisticadas

Crear el proyecto

Ahora entramos en la SEGW, para crear el proyecto. Una vez creado y activado el proyecto se nos creará el servicio oData y las clases ABAP.

Nada más entrar, le damos a la hoja en blanco para crear el proyecto.

Paquete local, que no queremos transportar nada, estamos probando

Después, definimos las entidades. En nuestro caso, sólo una entidad.



Cuando la creamos, nos pregunta el nombre y si queremos añadir una nueva colección (entity set) relacionada con dicha entidad. Marcamos el checkbox para que nos la cree automáticamente.


Tenemos que enlazar la entidad con la estructura ABAP en la que se va a basar.



Después definimos los campos que va a tener la entidad. Estos campos no tenemos por qué llamarlos igual que los de la estructura, ni tenemos por qué usar todos. CUIDADO: El nombre de las propiedades es case-sensitive (sí, ABAP nos tiene mal acostumbrados).

Para cada propiedad hay que especificar de qué tipo es (por ejemplo, Edm.String si queremos que sea una cadena de caracteres) y mapearla con un campo de la estructura. El tipo que elijamos es algo específico del protocolo oData, así que no se va a ajustar al 100% a nuestros elementos de datos de ABAP (al activar puede darnos muchos warning diciendo algo así como "ojo, que el ajuste no es perfecto"... poco le podemos hacer).



Un cambio importante si vamos a utilizar el servicio oData con el Web IDE, es marcar la casilla Addressable para la colección. Para este tutorial no nos va a aportar nada, pero sí que nos influirá al construir nuestra aplicación en el Web IDE, ya que si no lo marcamos no nos aparecerá en los desplegables del Template. Esto lo veremos en el siguiente tutorial.

Por ahora, lo que hacemos es ir a la carpeta Entity Set, seleccionar la colección (doble clic) y nos aparecerá a la derecha un listado con todas las colecciones. Pues ahí marcamos la casilla Addressable.



Tras estos cambios, activamos.



Al hacer esto nos aparece una pantalla preguntándonos que nombre le queremos dar a las Provider Classes, que son las clases con las que vamos a trabajar. Específicamente trabajaremos con la Data Provider Class (la que acaba con _DPC_EXT, aunque la podemos renombrar).

También nos pregunta el nombre que recibirá el modelo (que se usará en diversas parametrizaciones) y el nombre técnico del servicio. Este último campo es precisamente el nombre con el que llamaremos a nuestro servicio oData (/sap/opu/odata/sap/NOMBRE_DE_TU_SERVICIO).

A este servicio lo llamaré desde la aplicación web con /sap/opu/odata/sap/zmis_textos_srv

Leer la información

Al activar el servicio, nos crea las clases con las que vamos a trabajar. De esas cuatro clases, la que más nos interesa ahora mismo es la DPC_EXT.

Esa clase (que hereda de la DPC), tendrá cinco métodos por cada entidad para tratar los datos: Uno para leer un registro específico, otro para leer una colección, otro para actualizar datos, otro para crear y otro para borrar. Es lo que llaman operaciones CRUD: Create, Read, Update and Delete. En la SEGW podemos ver qué método tenemos que implementar para realizar la operación correspondiente.

Los métodos con una línea debajo están implementados... en nuestro caso, todavía no tenemos nada implementado

No hay por qué implementar todas las operaciones, sólo las que realmente nos interesen. Si sólo queremos leer datos, ¿por qué vamos a definir una operación de borrado?

Activar el servicio

Que hayamos creado el servicio no quiere decir que podamos usarlo.

Antes de eso debemos activarlo. Y eso lo haremos con la transacción /n/IWFND/MAINT_SERVICE. O, para los amantes de la IMG, SAP NetWeaver -> Gateway -> OData Channel -> Administration -> General Settings -> Activate and Maintain Services.



Pulsamos el botón "Añadir servicio" para buscar el servicio que nos ha generado la SEGW. Elegimos nuestro Alias de Sistema y pulsamos enter. Nos aparecerán un montón de servicios externos, entre los que buscamos el nuestro. Lo seleccionamos y pulsamos "Añadir servicios seleccionados".



Lo siguiente es rellenar los datos del servicio. En general, podemos dejar lo que nos proporciona (sólo nos hará falta especificar el paquete, yo le he puesto el paquete local).


Pues si todo va bien y volvemos atrás, ya tendremos nuestro servicio creado. Además, nos habrá activado dos cosas: La entrada en la ICF (transacción SICF), que en la imagen siguiente es el recuadro inferior izquierdo, y la asignación al alias que hayamos escogido, en el recuadro inferior derecho.

El servicio oData en la SICF está localizado en /sap/opu/odata/sap/nombre_del_servicio_externo... pero podemos gestionarlo todo desde esta ventana sin necesidad de entrar en la SICF.

Autorización al servicio

Ojo, si no tenemos un SAP_ALL vamos a necesitar autorización para usar dicho servicio. Se necesitaría un rol en el Front-End y otro en el Back-End, cada uno con una entrada de menú de tipo Propuesta de autorización - Servicio TADIR:
  • En el Back-End, se usaría el tipo de objeto IWSV e indicariamos el servicio.
  • En el Front-End, se usaría el tipo de objeto IWSG y nuestro servicio.


Una vez asignado el menú, iríamos a autorizaciones a dejar que se genere automáticamente la autorización.

A ver quién es el listo que crea eso a mano

Probar el servicio

Tras activar el servicio, ya lo podemos probar. Podemos hacerlo dentro del Gateway o en un navegador.

Si es en el Gateway, vamos a la opción "Gateway Client", donde obtenemos una pantalla como la siguiente. Ahí podemos indicar, en Request URI, nuestro servicio. Si invocamos la colección que hemos creado, lógicamente no nos mostrará datos, ¡todavía no hemos tirado ni una línea en ABAP!

Resultado del documento metadata

A tirar código

Ahora vamos a añadir el código. Nos vamos a la SE24, a la clase y método que nos indicaba en la SEGW. El método estará sin implementar (hereda de la clase DPC), así que pulsaremos en el botón de "Volver a definir método" para implementarlo.

Primero podemos crear el método GET_ENTITY. Este método nos devolverá una única entrada, y requiere que se le pasen todos los parámetros clave en la URL del navegador. Eso se pasa en este formato: /sap/opu/odata/sap/ZMIS_TEXTOS_SRV/TextosGeneralesSet(Id='00000001',Idioma=''). Ojo, no dejes espacios en blanco entre los parámetros clave. 

Podemos imaginar que el get_entity equivale a un select single. 

En el parámetro del método IT_KEY_TAB recibiremos los parámetros pasados desde la URL, y en el parámetro ER_ENTITY devolveremos el resultado. ¿Qué estructura tiene ER_ENTITY? La que definimos al principio, en la SE11, y mapeamos con el servicio oData en la SEGW.

Si el idioma viene en blanco, coge el idioma del usuario en SAP.

Después definiremos el método GET_ENTITYSET. Este método nos devolverá un listado de valores, no sólo uno. Aquí no pasaremos campos clave, sino un filtro. Por ejemplo, algo como esto...

/sap/opu/odata/sap/ZMIS_TEXTOS_SRV/TextosGeneralesSet

...para obtener toda la colección, o esto...

/sap/opu/odata/sap/ZMIS_TEXTOS_SRV/TextosGeneralesSet?$filter=Idioma eq 'EN'

...para obtener todos los textos en inglés. 

El parámetro filter no es automático. Si queremos que se filtre por una propiedad específica, ¡lo tenemos que programar! Si ponemos un filtro que no se usa, simplemente no se tratará. A la hora de determinar el uso del servicio seremos nosotros los que establezcamos como se tiene que usar.

El filtro lo podemos recuperar de varios parámetros de entrada del método. En el ejemplo se recupera del parámetro it_filter_select_options, que es una tabla de select options. 

En este ejemplo sólo trato el filtro para el idioma e ignoro si viene con un EQ, BT, LT, etc... lo trato siempre como EQ... porque yo lo valgo. Y si no se indica idioma, cojo el idioma por defecto del usuario SAP.



Ver los resultados

Cuando ya hemos programado el ABAP, ya podemos ver los resultados en el Gateway Client. 

Ahí tenemos un campo de texto, Request URI, donde podemos indicar la consulta que realizaremos. Nos puede resultar muy útil los dos botones en la parte superior derecha: EntitySet, que nos mostrará un matchcode con las colecciones que podemos usar; y 'Add URI Option', para añadir cadenas chulas como pueden ser $format=json para ver el resultado en formato JSON en lugar de en XML o $metadata para leer el documento metadata. 

Podemos probar las tres siguientes líneas para ver los resultados:

/sap/opu/odata/sap/ZMIS_TEXTOS_SRV/TextosGeneralesSet?$format=json



/sap/opu/odata/sap/ZMIS_TEXTOS_SRV/TextosGeneralesSet?$filter=Idioma eq 'EN'&$format=json


/sap/opu/odata/sap/ZMIS_TEXTOS_SRV/TextosGeneralesSet(Id='00000001',Idioma='')?$format=json



Y con esto, ya hemos generado nuestro primer servicio oData y ya exponemos datos al exterior. El siguiente paso es enlazarlo con nuestra aplicación web. Pero eso es ya otra historia. 


24 comentarios:

  1. Muy buena explicación, la verdad es que aprender a implementar servicios con SAP GATEWAY será demasiado esencial para el futuro de SAP como base de datos y su exposición a sistemas de MICROSOFT como sahare point, excel, además de otros sistemas, sin la necesidad de tener que configurar proxys o procesos en PI, ya que el protocolo oData está siendo ampliamente usado en arquitecturas REST. Gracias por el tutorial.

    ResponderEliminar
  2. Buenisimo material, estoy esperando la sengunda parte.

    ResponderEliminar
    Respuestas
    1. ¡Gracias! Aunque esta era ya la segunda parte de la extracción de servicios oData. Si te refieres a como enlazarlo con la parte web, mira en la página de tutoriales para ver los distintos post relacionados ;) https://uxsap.blogspot.com.es/p/tutoriales.html

      Eliminar
  3. Una duda, la estructura y la tabla la creo en mi back end y alla la lleno ? sirve conectarme como back end pero a desarrollo? es que algo asi me dijeron que esta configurado

    ResponderEliminar
    Respuestas
    1. Si, la estructura y el proyecto con la transacción segw se crean en backend, en desarrollo, y posteriormente lo vas transportando a otros entornos.
      La clase que editas para rellenar datos también está en el backend. Y lo suyo es que tu web ide o eclipse se conecten con desarrollo para que crees tu app web, hasta que lo despliegues en desarrollo->calidad->producción

      Eliminar
    2. ok muchas gracias esa era mi duda, ese rol que creo en mi back end luego de transportar de mi back end a mi servidor front-fiori luego de esto es que creo el rol con permiso de usar ese servicio no ?Muchas gracias !

      Eliminar
    3. Hola Jorge, Carlos, los felicito por el blog!
      Consulta, si tengo el Gateway y Backend en distintos servidores. Antes creaba el servicio odata en el Gateway e invocaba a RFC al Backend. Entiendo que seria mejor, crear el servicio odata desde el Backend para evitar el uso de RFCs y declarar tantas estructuras, pero como logro llegar al gateway que está en otro servidor?, sólo por la configuración de los "Alias" en la vista "/IWFND/V_MGDEAM"?...necesito aclarar esta parte. Agradezco sus comentarios! Saludos.

      Eliminar
    4. Hola huanguelito,

      Tienes que crearte, en el front-end, un alias que apunte al back-end. Recomendación, no uses máquina-mandante al creártelo ya que hay que transportarlo, mejor crea una RFC (SM59) en el front-end que apunte al back-end y usa esa RFC en la tabla de alias.

      Una vez creado el alias, te creas el servicio oData en el back-end y, cuando lo tengas terminado, en el front-end lo añades usando dicho alias.

      Eliminar
    5. Gracias Jorge!, pero desde el Front-end, debo generar por la SEGW nuevamente el servicio con el mismo nomrbre que en el Back-end?, Cómo lo agregaria desde el GW al servicio odata del Back-end?

      Eliminar
    6. No, sólo se genera en el back-end.

      En el front-end, en la transacción /IWFND/MAINT_SERVICE, cuando vas a añadir un nuevo servicio, utilizas el alias creado que apunta al back-end y así ya te aparece para añadirlo.

      Eliminar
    7. Muchas gracias!, logré que funcionara, aunque al principio no podia por la version del componente SAP_GWFND que tenia.

      Eliminar
  4. Buen Material, quisiera que me puedan orientar de como configurar el Destination para el Odata en el Web Ide.
    En mi caso el servicio ya está publicado http://201.230.xxx.155:8000/sap/opu/odata/sap/ZPERSONA_SRV/ZPERSONASET , en cual se puede acceder desde internet.
    Ya he configurado el Destination con la url anterior, pero al momento de querer seleccionar el Service URL no se muestra el Destination.

    ResponderEliminar
    Respuestas
    1. Hola,

      Quizá te falten los parámetros necesarios en la configuración del destination, como por ejemplo el WebIDEEnabled = true.

      Mira en los siguientes post, a ver si te ayudan a resolver tu duda:

      http://www.uxsap.com/2017/01/destination-para-odata.html
      http://www.uxsap.com/2017/08/desplegar-app-sapui5-onpremise.html

      Eliminar
  5. Muchas gracias Jorge, muy interesante y útil.

    ResponderEliminar
  6. Hola, muchas gracias por el aporte. Logré hacer que funcione el servicio, pero solo puedo acceder a desde el Gateway Client, no puedo acceder desde un Explorador o desde el Postman. Sabes que configuración hace falta para poder visualizar el servicio desde fuera de Sap. Saludos!

    ResponderEliminar
    Respuestas
    1. ¿Está bien la url de tu servidor en el navegador, incluyento https o http y el puerto? Desde el propio Gateway puedes llamar al navegador para que te acceda al servicio, en lugar de llamar al Gateway Client (el botón que está justo al lado, llamar al navegador).

      Eliminar
  7. Excelente aporte, sólo una consulta, estoy intentando crear un Entity Type de una Data Source la cual es un RFC pero al momento de importar me pide que asigne al menos un campo clave, las estructuras que utilizo tanto en el import como en el export no requieren campos claves, ¿cómo puedo importar mi RFC así de la misma manera en que se utiliza en el proceso de creación de servicios web SOAP en SAP mediante el wizard? Gracias.

    ResponderEliminar
    Respuestas
    1. Hola Daniel, cuando te creas un Entity Type siempre tiene que haber al menos un campo clave. Si tienes en la RFC variables de tipo import o changing, quizá las puedas usar precisamente como campo clave.

      Por ejemplo, si tengo un módulo de funciones al que le tengo que pasar un número de personal para que me devuelva el nombre del empleado, está claro que el número de personal tiene que ser un campo clave.

      En cualquier caso, si no quieres usar un campo clave, o haces un poco de trampas y marcas uno como clave pero que siempre llegará vacío o con un valor constante (un poco guarrete y ni siquiera sé si te valdrá), o te creas una función import con un complex type. Tendrás que codificar en el método del EXECUTE_ACTION en lugar de mapear la RFC, pero ya no necesitarás campo clave.

      Eliminar
  8. Buen aporte JorGCalleja, como funcionaria si quiero enviarle parametros.

    Saludos

    ResponderEliminar
  9. tendras un tutorial usando Odata con un RFC? es muy buen material muchas gracias

    ResponderEliminar

Nota: solo los miembros de este blog pueden publicar comentarios.