viernes, 9 de noviembre de 2018

El poder de la certificación!!!

Ha sido un mes duro pero ayer conseguí aprobar la Certificación:
- SAP Development Associate - ABAP with SAP NetWeaver 7.50

Ya siento el poder de la certificación recorrer mis venas!!  Ja ja ja!!



lunes, 27 de agosto de 2018

READ TABLE .... BINARY SEARCH

BINARY SEARCH es una opción de la instrucción READ TABLE que realiza la búsqueda de un registro en concreto en una tabla interna utilizando un algoritmo de búsqueda binario. Los algoritmos de búsqueda binarios son mucho  más rápidos y eficientes que la búsqueda secuenciales. Pero, si utilizáis la opción BINARY SEARCH y no ordenáis correctamente la tabla,  al ejecutar el programa es  posible que no encuentre el registro en la tabla interna, aunque utilizando el debugger comprobereis que si que existe el registro que buscamos en la tabla interna.

En esta entrada del blog. Explicaremos la diferencia entre la búsqueda secuencial y la búsqueda binaria con la instrucción READ TABLE  y porque es necesario asegurarnos de que la tabla interna esta ordenada antes de realizar una búsqueda binaria.

READ TABLE: BÚSQUEDA SECUENCIAL

La instrucción READ TABLE busca un registro en la tabla interna especificada que cumpla una condición dada. Por defecto, a menos que la tabla interna sea de tipo SORTED o HASHED, la búsqueda del registro se realiza secuencialmente. Una búsqueda secuencial consiste en leer cada registro de la tabla de forma lineal, desde el primer registro al ultimo o  hasta encontrar un registro que cumpla la condición de la busqueda.

Búsqueda secuencial en una tabla interna

Da igual que ordenemos la tabla antes, la búsqueda sera siempre por defecto secuencial o lineal. Lo bueno de la búsqueda secuencial es que es muy simple de usar y siempre funciona. Por contra, como podéis ver en la imagen siguiente, es el método de búsqueda  que más tiempo consume.

Tiempos de acceso a una tabla interna con 100.000 registros


READ TABLE: BÚSQUEDA BINARIA

Una forma de acelerar la búsqueda es añadir la opción BINARY SEARCH a la instrucción READ TABLE. Esta opción realiza una búsqueda en la tabla interna utilizando un algoritmo de búsqueda binaria. Una búsqueda binaria comenzará examinando el registro central de la tabla interna. Si el registro es el que buscamos, hemos terminado. Si no es el registro correcto, podemos utilizar la naturaleza ordenada de la tabla para eliminar la mitad de los registros restantes. Si el registro que buscamos es mayor que el registro central, sabemos que no hace falta buscar en los registros anteriores al registro central. El registro que buscamos, si es que está en la tabla interna, debe estar en la mitad superior al registro central, ya que este es menor que el registro buscado. Podemos entonces repetir el proceso con la mitad superior. Buscamos el registro central de la parte superior y lo comparamos con el valor que estamos buscando. Una vez más, o lo encontramos o dividimos la lista por la mitad, eliminando por tanto otra gran parte de nuestro espacio de búsqueda posible.

Búsqueda binaria del registro con valor 20

Es importante ordenar la tabla antes por los campos que utilizamos para la búsqueda. Si no ordenamos la tabla antes de ejecutar la búsqueda binaria es posible que aunque el registro que buscamos exista en la tabla interna, nunca lo encuentre.

Por ejemplo, el siguiente código,  ordena la tabla antes de ejecutar la búsqueda binaria. 

Búsqueda binaria ordenando antes la tabla interna


El proceso de búsqueda seria:
  1. Busca el registro medio de la tabla y compara el valor
  2. El valor del registro 9 es mayor que el valor buscado
  3. Busca el registro medio entre los registros 0 y 9.
  4. El valor del registro 4 es mayor que el valor buscado
  5. Busca el registro medio entre los registros 0 y 4
  6. El valor del registro 2 es igual al valor buscado
  7. EL puntero <FS_KUNNR> apuntara al registro 2 de la tabla TI_KUNNR y retorna sy-subrc = 0.

En cambio, si no ordenamos la tabla antes, no encontrara nunca el registro.

Búsqueda binaria SIN ordenar antes la tabla interna



El proceso de búsqueda seria:
  1. Busca el registro medio de la tabla y compara el valor
  2. El valor del registro 9 es mayor que el valor buscado
  3. Busca el registro medio entre los registros 0 y 9.
  4. El valor del registro 4 es mayor que el valor buscado....
  5. Repite la búsqueda sin encontrarlo nunca porque ya no estan entre los registros seleccionados
  6. retorna sy-subrc = 4.

CONCLUSIÓN

 Recordar siempre ordenar la tabla interna antes de usar  READ TABLE...BINARY SEARCH.


¿Y que pasa si  la tabla interna es SORTED?

Si estamos utilizando una tabla interna de tipo sorted. No hace falta indicar la opción BINARY SEARCH. Si utilizamos parte o todos los campos que forman la clave de la tabla interna, la búsqueda sera binaria por defecto. En cambio, si utilizamos campos de la tabla interna que no pertenecen a la clave principal, la búsqueda sera  siempre secuencial.


¿ Y las tablas HASHED?

Nunca se utilizan búsquedas binarias con tablas interna de tipo HASHED. Con las tablas hashed  se accede a cada registro de la tabla con una clave hash. No es necesario utilizar búsquedas binarias porque cualquier búsqueda utilizando tablas hashed, es mucho más rápido que utilizando búsquedas binarias.

PROGRAMA DE EJEMPLO

En el repositorio de GITHUB del blog os dejo un programa para ilustrar el caso:

Programa ZZREAD_TABLE_BINARY.

  • El primer READ TABLE es secuencial.
  • EL segundo READ TABLE es binario sin ordenar la tabla.
  • El tercer READ TABLE es binario y ordena la tabla antes.

martes, 21 de agosto de 2018

Patrones de diseño con ABAP - MVC

El patrón MVC o Modelo-Vista-Controlador es un patrón de diseño de software que se centra en separar la lógica de negocio y los datos de la aplicación del código para su representación visual. Se fundamenta en separar el código en tres capas diferentes, acotadas por su responsabilidad. 
  • MODELO ("M"): Es la capa donde se trabaja con los datos. Esta formada por las clases que contienen la lógica de la aplicación. Estas clases no pueden contener ningún código relacionado con el interfaz de usuario. En ABAP, estas clases correspondería a una o varias clases globales definidas en la transacción SE24.
  • VISTA ("V"): Es la capa donde se va ha producir la visualización de los datos al usuario. Esta formada por las clases clases que contiene todo el código relaciona con el interfaz de usuario. En abap utilizariamos las clases ya existentes para generar ALV, Webdynpros, etc... reciben como parámetro de entrada los datos generados por la capa MODELO y generan la salida.
  • CONTROLADOR ("C"): Esta capa sirve de enlace entre las capas de Modelo y Vista. No puede contener ningún código que manipule datos, ni muestre datos, solamente debe servir como enlace entre los modelos y las vistas. En Abap correspondería a un programa ejecutable implementado en la transacción SE38.

Esta separación en capas otorga al software un grado muy alto de flexibilidad, permitiendo cambiar la lógica de negocio independientemente del interfaz de la aplicación y viceversa.


Patrón Modelo Vista Controlador MVC

Imaginemos una aplicación Abap donde el usuario requiera al programa realizar una acción determinada ( por ejemplo ver  los datos de un proveedor, cliente, pedido de venta, etc... ).

  1. El usuario ejecuta la transacción solicitando al controlador el listado.
  2. El controlador llamara al método del modelo para realizar la acción solicitada por el usuario. 
  3. El modelo retornara los datos solicitados por el usuario al controlador
  4. El controlador llamara al método de la vista encargado de mostrar los datos por pantalla. 
  5. La vista mostrara los datos solicitados al usuario.

En ningún momento de este proceso, el controlador busca o procesa los datos, ni realiza ninguna operación para mostrar los datos al usuario, su trabajo se centra en ser el enlace entre la capa modelo y la capa vista.

Para explicar mejor como utilizar el patrón MVC con ABAP , imaginemos un cliente que nos pide un programa ABAP con los siguientes requerimientos:
  1. Leer los datos de los pedidos de ventas requeridos por el usuario
  2. Elimine aquellos que sean de clientes bloqueados
  3. Muestre el resultado por pantalla utilizando un ALV
  4. El usuario podrá seleccionar registros del ALV
  5. Un pulsador para crear la entrega de salida de los pedidos seleccionados
En ingles a estos programas se les llama aplicaciones CRUD (Create , Read, Update, Delete).

Si implementamos el programa en la transacción SE38 con una estructura clásica ABAP , a grandes rasgos,  nos quedara un programa parecido al siguiente código:

REPORT zreport_clasico_abap.

* Definiciones de datos y estructuras globales
* Parametros de la pantalla de selección

STAR-OF-SELECTION.
  PERFORM leer_pedidos_ventas.
  PERFORM eliminar_bloqueados.
  PERFORM display_alv.

END-OF-SELECTION.

FORM leer_pedidos_ventas.
* Código para leer datos de las tablas VBAK/VBAP
* Almacenar los datos en una tabla interna
ENDFORM.

FORM eliminar_bloqueados.
* Código para eliminar pedidos con clientes bloqueados
* Leer datos de alguna tabla KN**
* Eliminar registros de la tabla interna
ENDFORM.

FORM display_alv.
* Mostar la tabla interna por pantalla en un ALV (REUSE_ALV_GRID_DISPLAY)
ENDFORM.

FORM user_command USING pi_ucomm rs_selfield.
  IF ( pi_ucomm EQ c_ucomm_save ).
    PERFORM crear_entregas.
  ENDIF.
ENDFORM.

FORM crear_entregas.
*  Código para  crear entregas de los pedidos seleccionados
ENDFORM

* Más y más subrutinas....


El principal problema del enfoque clásico es que, el código para obtener y manipular los datos esta mezclado con el código del interfaz de usuario o GUI. Con suerte, el programador encapsulara en subrutinas las operaciones de acceso de datos y para lanzar el ALV, pero en la mayoría de los casos nos encontraremos mezclado, dentro de la misma subrutina, tanto el código para acceder  a los datos  y el código para mostrar los datos. Para que os deis cuenta de los problemas que puede acarrear esta estructura, imaginaos que surge un nuevo requerimiento que diga que incluyamos la misma funcionalidad del programa en una función RFC (Remote Funcion Call). Despues de crear nuestro programa nos piden crear una función RFC que reciba una serie de números de pedidos de venta, elimine los que tiene clientes bloqueados y genere las entregas de salida.

En estos casos, lo que podemos hacer con una estructura de programación clásica de ABAP, es bastante limitado:
  1. Copiar el código del programa dentro de una bapi. No es muy buena idea... porque si cambia la lógica para seleccionar clientes bloqueados o se añaden nuevos campos, tendremos que adaptar ambos programas, es decir para una modificación habrá que adaptar múltiples programas en el sistema. Lo mejor para mantener un código siempre es que sea simple y evitar duplicidades.
  2. Incluir el código común en subrutinas y guardarlo en un fichero que pueda ser utilizado desde cualquier programa con la instrucción INCLUDE. Es una solución mejor que la anterior, pero la interfaz, la información que conpartiran las subrutinas al ejecutarse,  necesitara ser definida en las transacciones SE37/SE38 múltiples veces. Volvemos al caso anterior, si por ejemplo añadimos nuevos campos, necesitaremos modificar en múltiples puntos.
  3. Crear un grupo de funciones y convertir las subrutinas en funciones que puedan ser llamadas desde cualquier programa. La solución más elegante de las tres, pero si usas funciones jamas podrás utilizar las ventajas que te ofrece la programación orientada a objetos como la herencia, polimorfismo y la encapsulación.
Enfoquemos el problema utilizando programación orientada a objetos y el patrón MVC. El objetivo es  ver como nos puede ayudar a realizar aplicaciones, con un grado de flexibilidad ante los cambios o nuevos requerimientos mucho mayor que la clásica programación ABAP.

La primera ventaja la encontramos en la fase de diseño. Al utilizar programación orientada a objetos podemos utilizar el Lenguaje de Modelado Unificado o UML para diseñar un diagrama de nuestro programa siguiendo el patrón MVC. Para este ejemplo, el diagrama UML es bastante sencillo.

diagrama UML de la aplicación


Ambos controladores ( programa y la funcion RFC ) usaran la misma clase de modelo pero podrán después reconducir los datos y recibir las peticiones de sus modelos de vistas. La clase ZCL_MODELO_DATOS encapsula toda la lógica de la aplicación en sus métodos y no contiene ningún código referente al GUI de la aplicación. Por ultimo, la capa VISTA sera la función REUSE_ALV_GRID_DISPLAY y para la función RFC sera la llamada a la función RFC desde el programa externo. Como podéis observar cada capa tiene acotada su responsabilidad y no se encarga de nada que no le corresponda.

La clase ZCL_MODELO_DATOS la creamos como una clase global en la transacción SE24 para que podamos reutilizarla en todos los controladores que diseñemos.

 El código de la clase global seria más o menos el siguiente:
class ZCL_MODELO_DATOS definition public final create public .

  public section.

    types: BEGIN OF T_PARAM,
      s_vbeln TYPE RANGE OF vbak-vbeln,
      s_vbtyp TYPE RANGE OF vbak-vbtyp,
      s_kunnr TYPE RANGE OF vbak-kunnr,
     END OF t_param .
   
    types: BEGIN OF T_PEDIDOS,
      VBELN type vbak-vbeln,
      posnr type vbap-posnr,
      kunnr type vbak-kunnr,
      name1 type kna1-name1,
      matnr type vbap-matnr,
      maktx type makt-maktx,
     END OF t_pedidos .
   
    types:
      tt_pedidos type STANDARD TABLE OF t_pedidos with non-unique key TABLE_LINE.

    methods LEER_PEDIDOS_VENTAS
     importing
       !IS_PARAM type T_PARAM.
  
    methods ELIMINAR_BLOQUEADOS.
    methods GET_LISTA_PEDIDOS.
     returning
      value(RT_LISTA) type TT_PEDIDOS .
    methods CREAR_PEDIDOS.
  protected section.

  private section.
    data ZTT_PEDIDOS type TT_PEDIDOS .

ENDCLASS.

CLASS ZCL_MODELO_DATOS IMPLEMENTATION.

  method LEER_PEDIDOS_VENTAS.
    "Código para leer la VBAK/VBAP segun parametros y rellenar la tabla ZTT_PEDIDOS
  endmethod.


  method ELIMINAR_BLOQUEADOS.
    "Código para recorrer la tabla interna ZTT_PEDIDOS 
    "Eliminar clientes bloqueados consultando tablas KN**
  endmethod.

  method GET_LISTA_PEDIDOS.
    rt_lista[] = ztt_pedidos
  endmethod.

  method CREAR_PEDIDOS.
    "Algo de código para un LOOP a la tabla ZTT_PEDIDOS y crear las entregas 
  endmethod.

ENDCLASS.

No hace falta que toda la lógica de la aplicación este en una misma clase. La ventaja de usar clases globales es la reutiliziación del código. Para un proyecto podría crearse toda una librería de clases que encapsulasen la lógica de las aplicaciones y así evitar la duplicidad del código. Para crear toda esta estructura de clases y no morir en el intento, podeis utilizar el patron de diseño COMPOSITE para construir objetos complejos a partir de otros más simples y similares entre sí, gracias a la composición recursiva y a una estructura en forma de árbol.

Ahora que tenemos la capa modelo, veamos como quedaría el programa en la transacción SE38:

REPORT ZREPORT_SE38.

DATA go_modelo_datos TYPE REF TO zcl_modelo_datos.
DATA ti_pedidos TYPE ztt_pedidos.

* Algunas definiciones de datos 
* Parametros de la pantalla de seleccion

START-OF-SELECTION.
  go_modelo_datos = NEW #( ).
  go_modelo_datos->leer_pedidos_ventas( ).
  go_modelo_datos->eliminar_bloqueados( ).
  ti_pedido = go_modelo_datos-get_lista_pedidos( ).
  perform display_alv.
END-OF-SELECTION.

FORM display_alv.
* Código para llamar a REUSE_ALV_GRID_DISPLAY
ENDFORM.

FORM user_command USING pi_ucomm rs_selfield.
  IF ( pi_ucomm EQ c_ucomm_save ).
    go_modelo_datos->crear_entregas( ).
  ENDIF.
ENDFORM.

Utilizando el patrón MVC,  lo que hace el programa ZREPORT_SE38 es únicamente funcionar como "mensajero" entre la clase ZCL_MODELO_DATOS y la función REUSE_ALV_GRID_DISPLAY.

En caso de necesitar modificar el ALV y cambiar nombres de columnas, orden de las columnas, añadir pulsadores, etc... podrías crear una nueva clase que llame a REUSE_ALV_GRID_DISPLAY e implementar todos los métodos que necesitáis para modificar y ajustar el ALV. El programa que funcione como controlador llamaría a esta nueva clase en vez de a REUSE_ALV_GRID_DISPLAY.

También, en vez de usar la bapi REUSE_ALV_GRID_DISPLAY, podríamos utilizar la clase CL_SALV_TABLE. Crear una nueva clase que tenga un constructor o un método que configure el ALV y genere el ALV usando la clase CL_SALV_TABLE. De esta manera seguimos manteniendo el principio del patrón MVC de separar la responsabilidad de cada capa.

En resumen, El objetivo del patrón MVC es separar los datos y su procesamiento de su representación visual. Las principales ventajas son que los programas se vuelven mucho mas flexibles y escalables ante nuevos requerimientos o cambios. También nos permite tener un mismo modelo para la obtención de datos que podemos reutilizar en múltiples vistas para la representación de los datos.

Por contra, al utilizar programación orienta a objetos, tendremos que crear nuestras propias clases, aumentando la cantidad de objetos que hay que mantener ( pasamos de 1 programa a como mínimo 1 clase global y un programa )  y la complejidad del sistema.

Fuentes:
Design Patterns in ABAP Objects 2017 Kerem Koseoglu
The MVC design pattern in ABAP for practical use
ABAP Objects Design Patterns – Model View Controller
SDN Wiki - Model View Controller design Pattern