miércoles, 10 de mayo de 2017

SAP UI5 y oData: Asociaciones, navegación y expand (II, oData y ABAP)




Habíamos estado hablando de lo que eran las asociaciones, la navegación y el expand en los servicios oData. Así, en plan teórico. Y poníamos como ejemplo una posible aplicación para mostrar los personajes de las películas de ciencia ficción.

Esta es la aplicación que queríamos crear... a ver cómo creamos el servicio oData


Pues ahora nos vamos a poner con la parte práctica, vamos a meterle mano a la máquina. Lo primero es crear el servicio oData, y eso es lo que vamos a hacer aquí. En el siguiente post ya lo enlazaremos con la aplicación en SAP UI5.

Si nunca has creado un servicio oData en SAP, quizá te interese revisar este post anterior.

Es importante destacar que la asociación la vamos a crear relacionando propiedades. Esto no siempre es necesario, pero a la hora de codificar en ABAP nos va a facilitar mucho el trabajo. Me dejaré pendiente hacer un post para explicar otra manera de codificar las navegaciones.

Este ejemplo de aplicación hecho en el Web IDE hace los siguientes tipos de llamada, que nos sirve como idea para determinar lo básico que necesitamos implementar.

  • Servicio_de_saga/Sagas
  • Servicio_de_saga/Sagas('STARWARS')
  • Servicio_de_saga/Sagas('STARWARS')/LosPersonajesDeEsaSaga

 Pero nos complicaremos un poco más la vida para obtener más información y aprender más:

  • Servicio_de_saga/LosPersonajes?$expand=SuSaga

Los pasos que seguiremos son:

  • Crear las estructuras y tablas que necesitaremos, incluyendo datos (SE11 y SM30).
  • Crear el proyecto en la SEGW y activar el servicio oData en la /IWFND/MAINT_SERVICE.
  • Programar los métodos para permitir la funcionalidad de la aplicación estándar (entidad de saga, entityset de saga y navegación hacia personajes).
  • Reprogramar el método que nos permite expandir la saga de un personaje (navegación de un personaje hacia su saga).

Como he metido mucha chicha, siempre podéis leer en diagonal y prestar atención a las imágenes ;).
 

Crear estructuras y tablas


El primer paso es crear las estructuras y tablas que necesitaremos en la SE11 y rellenarlas con datos por la SM30. Eso ya no es ni oData, ni SAP UI5 ni nada, es SAP puro y duro de toda la vida. ¡No todo va a ser hacer cosas nuevas!

En la estructura y tabla de Saga crearemos un campo de Id como campo clave que identificará cada entrada de forma unívoca, y en la tabla de Personajes crearemos un campo Saga que servirá para almacenar el id de la saga. Es decir, ese campo Personajes-Saga es el que nos sirve para relacionar ambas tablas.

Las dos tablas con datos

Crear el proyecto en la SEGW


El servicio lo creamos como un nuevo proyecto en la transacción SEGW. Mi servicio luce tal que así, con dos tipos de entidad y cada uno con sus correspondientes propiedades, enlazadas con las estructuras definidas en la SE11.

Qué bonito que me ha quedado

Hasta aquí no hemos visto nada nuevo. Pero como diría el capitán Tani (¿o era general?), ¡al turrón!

Lo primero es crearse una asociación, ya que las navegaciones se basan en asociaciones. En el apartado Associations, con el botón derecho, escogemos Create para crearnos nuestra primera asociación.

A hacer la parte más difícil: El comienzo

Los datos que le ponemos son:

  • Un nombre chulo. Es obligatorio que el nombre sea chulo. Yo le he puesto PersonajesXSaga, la bomba de nombre.

  • Después, elegimos entidad principal y entidad dependiente:
    • La entidad principal sera Saga, y a partir de esa entidad obtendremos los personajes. Las navegaciones podrán ir en ambos sentidos (sólo creamos una asociación para ambos sentidos), pero cuando relacionemos propiedades, de la entidad principal sólo podremos relacionar las propiedades clave.
    • La entidad dependiente será Personaje.

  • En Cardinality, indicamos la cardinalidad que va a tener cada "nodo" de la asociación. Para entenderlo con un ejemplo, en nuestro caso para cada saga puede haber de 0 a N personajes, así que en la entidad de Personaje indicamos cardinalidad "0...n". Para cada personaje, puede que tenga Saga o no, pero si tiene sólo tendrá una. Así que le ponemos cardinalidad "0...1". También le podríamos haber puesto 1, obligando a que un personaje siempre tenga Saga. A gusto del consumidor.

  • Marcamos la opción de "Create related Navigation Property" para que nos cree la navegación de forma automática, tanto para la entidad principal como para la dependiente. Para nuestra aplicación básica sólo necesitaríamos la navegación principal, pero voy a crear las dos navegaciones para aprender más.

  • Y le ponemos nombre a las navegaciones. Este nombre es el que usaremos cuando llamemos a la navegación o hagamos el expand, para indicar "que ruta seguiremos para recuperar los datos relacionados". Podríamos poner el mismo nombre de la entidad relacionada, pero a mí me gusta diferenciarlos para no liarme luego. Les pongo LosPersonajes y LaSaga.

Todo esto lo resumo en una captura, que anda que no me enrollo.

Seguimos con el proceso de creación. El siguiente paso es indicar las propiedades relacionadas. En nuestro caso, la relación es entre Saga-Id y Personaje-Saga.

Relación creada

El último paso es crear el set de asociación y con eso ya habremos creado nuestra asociación.

Último paso para tenerlo todo listo

¡Tachán! Ya tenemos la asociación creada, y las correspondientes navegaciones.


¿Nos falta algo? Por supuesto, activar los cambios.

Activando en 3,2,1...

Con esto, ya tenemos el servicio oData creado. Ahora lo podemos activar en la transacción /n/IWFND/MAINT_SERVICE, para ponerlo disponible para su consumo.

Con estas navegaciones y si ABAPeamos todas las posibilidades, podríamos usar combinaciones como las siguientes (todas comenzando con el nombre del servicio, /sap/opu/odata/sap/zsagas_srv):

  • Sagas?$expand=LosPersonajes para obtener todas las sagas y sus personajes.
  • Sagas('STARWARS')/LosPersonajes para obtener los personajes de Star Wars.
  • Sagas('FIREFLY')?$expand=LosPersonajes para obtener los datos de Firefly así como sus personajes.
  • Personajes?$expand=LaSaga para todos los personajes y, para cada uno, los datos de su saga.
  • Personajes('ADAMA')/LaSaga para obtener los datos de la saga a la que pertenece Adama.

Codificando


Sí, ya hemos creado el servicio, pero no nos va a devolver nada. Hay que recordar que para recuperar los datos, tenemos que programar en ABAP las clases que la SEGW nos ha generado (las Data Provider Classes).

Así que vamos a la SE24 para codificar. ¿Qué clase es? Pues como he usado el nombre propuesto por la SEGW al activar, mi clase es ZCL_SAGAS_DPC_EXT.

¿Y qué tenemos que codificar? Podríamos codificar los siguientes métodos:

  • SAGAS_GET_ENTITY: Nos permitirá consultar Sagas('STARWARS') y la saga de los distintos personajes mediante navegación y expand, como por ejemplo Personajes?$expand=LaSaga.
  • SAGAS_GET_ENTITYSET: No permitirá consultar todas las sagas (Sagas). No hace falta navegación ni expand, porque cada personaje sólo tendrá una saga. 
  • PERSONAJES_GET_ENTITY: Nos permitirá consultar un personaje específico, como Personajes('MALCOM').
  • PERSONAJES_GET_ENTITYSET: Nos permitirá consultar todos los personajes (Personajes) así como los personajes de las sagas mediante navegación y expand, como por ejemplo Sagas('STARWARS')/LosPersonajes y Sagas?$expand=LosPersonajes.

Para la aplicación sencillita que nos vamos a marcar con el Web IDE (un Master con las sagas y un Detail con los personajes de la saga seleccionada), lo mínimo que necesitamos es:

  • Al cargar el Master se llama a Sagas: Implementar el método SAGAS_GET_ENTITYSET en plan sencillito, un simple SELECT como ejemplo nos valdría. No hace falta implementar navegación.
  • Al seleccionar una saga carga sus datos: Implementar el método SAGAS_GET_ENTITY, inicialmente también sin navegación, recuperando en IT_KEY_TAB el id de la saga y haciendo un SELECT SINGLE. Más adelante le aplicaremos la navegación, aunque para la aplicación básico no es necesario.
  • Al seleccionar una saga, carga sus personajes, Sagas('STARTREK')/LosPersonajes: Implementar el método PERSONAJES_GET_ENTITYSET para recuperar todos los personajes. Aquí sí que añadiríamos el código para permitir la navegación y el expand. Aquí empiezan las diferencias con lo que ya sabíamos de oData.

Codificando la navegación LosPersonajes


Así que vamos a implementar PERSONAJES_GET_ENTITYSET para permitir obtener los datos sin y con navegación (con Personajes y con Sagas('STARWARS')/LosPersonajes).

Primero debemos identificar si hemos navegado o no para llegar a este método. Obtener esa información es sencillo, y podemos usar uno de los dos siguientes parámetros de entrada, el que mejor nos venga:


  • IV_SOURCE_NAME, con el nombre de la entidad origen que ha hecho la navegación (Saga). Yo voy a implementar el código sobre este parámetro.
  • IT_NAVIGATION_PATH, con los datos de la navegación (LosPersonajes).


Si IV_SOURCE_NAME viene con el valor Saga, es que estamos navegando desde la Saga ¿Pero, de qué saga? Pues de la que nos venga en el parámetro IT_KEY_TAB. Sólo tenemos que leer ese parámetro y añadir un WHERE en nuestro SELECT.

Así que el código tendrá dos SELECT diferentes, uno si no viene de navegación (devolverá todos los personajes), y otro si viene de la navegación (devolverá todos los personajes de una saga específica).

En LV_ID guardamos el id de la Saga cuando hay expand. Si no hay expand, LV_ID vendrá vacío.

Así que con este código ya tendremos dos funcionamientos diferentes del mismo entityset. ¡Genial! Podemos comprobar la diferencia simplemente invocando el servicio de estas formas y poniendo un breakpoint, para ver cómo funciona:

  • Si ponemos /sap/opu/odata/sap/ZSAGA_SRV/Personajes, nos devolverá a todos los personajes.
  • Si ponemos /sap/opu/odata/sap/ZSAGA_SRV/Saga('STARWARS')/?$expand=LosPersonajes, sólo nos devolverá a Luke Skywalker y sus compañeros de saga.

El resultado cuando hacemos el expand es algo como lo siguiente:

El resultado lo he visto en el navegador web, lo que pasa que lo he editado con el Notepad++

Obtener la Saga de los personajes: Codificando la navegación LaSaga


Con el apartado anterior ya tendríamos suficiente para crear nuestra aplicación de forma rápida con el Web IDE.

Pero ahora vamos a implementar otra navegación para complicar un poco la materia. Vamos a obtener los datos de saga de cada personaje mediante Personajes?$expand=LaSaga.

En ese caso, como lo que tenemos es una (o ninguna) saga por personaje, cada iteración de personaje pasará por el método SAGAS_GET_ENTITY.

El funcionamiento es similar al caso anterior, en IV_SOURCE_NAME nos vendrá el nombre del tipo de entidad del que venimos. Sólo hay una diferencia: En un GET_ENTITY siempre nos viene relleno el parámetro IT_KEY_TAB, y tenemos que datos nos vienen ahí:

  • Si no hemos navegado, nos vendrá el id de la Saga que estamos buscando. Haremos el tratamiento sencillo.
  • Si hemos navegado, nos vendrá el id del Personaje. Buscaremos ese id en la tabla de personajes y de ahí obtendremos el id de su saga. Ya sólo nos queda buscar en la tabla de sagas.

Así quedaría el código


El resultado (editado en el Notepad++ para verlo bonito) sería:



Podemos rizar más el rizo, para cuando la llamada sea Personaje('SKYWALKER')/?$expand=LaSaga. Ahí ya codificaríamos el método PERSONAJES_GET_ENTITY. Más de lo mismo, la idea ya está ahí ;).

Y después...


¡Dios mío, que coñazo de post! La verdad que me ha tocado retocar mucho el post porque esto del oData es todo un lío. Espero que haya quedado algo claro.

Ahora ya nos queda la parte entretenida, que es hacer la aplicación SAP UI5. Pero eso lo veremos en el siguiente post.

3 comentarios:

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