Automatización del ALM de desarrollo en Microsoft Dynamics 365 for Finance and Operations

Ya he escrito unos cuantos posts sobre la ALM (Application Lifecycle Management) de desarrollo  para Dynamics 365 for Finance and Operations in the past:

La posibilidad de hacer CI/CD real es una de mis cosas favoritas de MSDyn365FO, pasar del «¿Qué control de código?» a «O usas control de código o muere» ha sido una maravilla. Nunca me cansaré de decirlo.

Además el post acaba con un extra bonus!

¡Más automatización!

Ya he explicado antes como automatizar las builds, crear builds de CI y crear las releases en Azure DevOps, así que de lo que quiero hablar en este post es de añadir un poco más de automatización.

Builds

En la definición de build vamos a la pestaña «Triggers» y activamos las builds programadas:

Esto va a lanzar una build automaticamente los días que quieras y a la hora que le digas. En el ejemplo se lanza todos los días entre semana a las 16.30h. Pero… ¿todos los días? No. El check «Only schedule builds if the source or pipeline has changed» hace que sólo se lance la build si ha habido cambios en el código, así que si un día no hay changesets nuevos no se lanza la build.

Releases

Builds listas, a ver qué podemos hacer con las releases:

La release de la imagen de arriba es la que se lanza automáticamente después de la build que he creado en el primer paso. Para esta release he añadido lo siguiente:

El disparador de CD está activo, lo que hace que cada vez que una build termina se ejecuta automáticamente. Sin necesidad de definir una programación horaria, pero también se podría hacer.

Como véis, la pantalla de la programación es exactamente igual que en las builds, incluso el check de cambios está ahí. Se pueden usar cualquiera de estas dos formas, CD o release programada, depende de las necesidades del proyecto o equipo.

Con estos dos pequeños pasos tenemos la estrategia de CI y CD completamente automatizada, y la actualización del entorno de UAT cada noche para tener los cambios hechos durante el día listos para testear, sin humanos de por medio!

Pero a mi me gusta un poco de toque humano

Si no te gusta no saber cuando se actualiza un entorno… bueno eso es IMPOSIBLE porque LCS te Spamea para que no te pierdas ni un despliegue. Pero si lo que no quieres es ser completamente reemplazado por robots puedes añadir aprobaciones a tu flujo de release:

Haciendo clic en el icono izquierdo de relámpago + persona puedes configurar los aprobadores, una persona o grupo (cosa que es muy práctica), y el tipo de aprobación (única o múltiple) y timeout. También recibirás un correo con un enlace al formulario de aprobación:

¡E incluso puedes posponer el despliegue! ¡Todo es fabuloso!

Extra bonus!

Un pequeño consejo. Imaginemos que tenemos esta release:

Esto actualizaría 3 entornos, pero también haría 3 subidas del Deployable Package a LCS. ¿No estaría bien poder hacer una única subida y que los despliegues se hicieran usando ese archivo? Sí, pero no podemos pasar el valor de la variable de salida del stage de subida a los demas stages 🙁 Eso desgraciadamente es así. Pero podemos hacer algo con un poco de ayuda de nuestro amigo Powershell!

Actualizar una variable en uan release

Lo que necesitamos es crear una variable en la definición de release y poner su ámbito como «Release»:

Luego, por cada stage que tengamos, tenemos que activar este check en el agent job:

Luego explicaré por qué. Ahora sólo tenemos que actualizar esta variable una vez subido el DP a LCS. Añadimos un Powershell de tipo inline después del paso de la subida y hacemos esto:

Necesitamos cambiar lo siguiente:

  • Línea 2: $assetId= «$(GoldenUpload.FileAssetId)». Cambiar $(GoldenUpload.FileAssetId) por tu variable de salida.
  • Línea 6: $ReleaseVariableName = ‘axzfileid’. Cambiar axzfileid por tu variable de Release.

Y listos. Este script usa la API REST de Azure DevOps para actualizar el valor de la variable con el file id, y hemos activado el check del token de OAuth para no tener que autenticarnos con ningún usuario. Esto no es idea mía, lo he hecho gracias a este post del blog de Stefan Stranger’s.

Ahora en los stages de despliegue recuperamos el valor de la variable así:

No os olvidéis los ( ) o no funcionará!!!

Y con este pequeño cambio tenemos una release como esta:

Con una única subida del DP a LCS y múltiples despliegues usando el archivo subida en el primer stage. Y con aprobaciones, y retrasos y correos y todo!

Ahora la mala noticia

La mala noticia es que, de momento, en los entornos de tipo self-service no se puede automatizar el despliegue. Y obviamente en los entornos de producción tampoco se puede automatizar nada, esa parte sigue siendo 100% manual.

 

Recuento de inventario con AI Builder

Este pasado fin de semana he participado en mi tercer 365 Saturday, esta vez en Barcelona, como speaker. Como podéis ver en el título mi charla ha tratado sobre la creación de diarios de recuento usando la IA y la Power Platform.

El evento ha sido (como siempre) genial, pero mi sesión me ha dejado con un sabor agridulce porque no he podido mostrar la funcionalidad completa de la app por culpa de problemas técnicos estúpidos (que eran estúpidos pero mi culpa) que solucioné en dos minutos después de la sesión.

Yo arreglando el problema DESPUÉS de la sesión

Pero bueno, gracias a todos los que asistieron a mi charla y lo siento por la cagada. Gracias también a la organización, así como a los demás speakers t al equipo de Axazure.

Contando con IA

¿De qué trataba mi sesión? Nada original. Si habéis visto la keynote de inauguración del pasado MBAS hubo una parte sobre un distribuidor de Pepsi que estaba usando AI Builder para escanear sus expositores y analizar cómo evolucionaban las ventas (más o menos). Mi PowerApp usa AI Builder para contar objetos (luego veréis cuáles) y con eso crea un diario de recuento en Dynamics 365 for Como-se-llame-pero-el-ERP.

Al final mi intención principal con esto es demostrar que podemos usar toda la Power Platform con MSDyn365FO, no solo Power BI, y que nos puede ayudar en los proyectos. Porque los que trabajamos con AX a veces estamos como:

Esto lo encontré en Twitter y añadí los logos, pero no recuerdo a quién se lo robé 🙁

AI Builder

AI Builder es una herramienta para la Power Platform que añade funcionalidad a PowerApps y Flow. Y es muy muy muy fácil de configurar y usar.

Ahora mismo AI Builder está formado por 4 modelos distintos:

  • Predicción: responde preguntas binarias como «¿Renovará mi cliente la suscripción?» o «¿Qué cliente no pagará a tiempo?».
  • Clasificación de texto: extracción de datos de texto. Devuelve un % como respuesta, 95% Bueno, 76% rápido, etc.
  • Procesamiento de formularios: extracción de datos en pares clave-valor. Por ejemplo, extraer datos de documentos (tiene que ser siempre el mismo documento).
  • Detección de objetos: detecta objetos en imágenes. Este es el modelo que usé yo.

De estos cuatro modelos solo el de predicción esta en GA, los otros siguen en preview. Además hay 5 modelos pre-entrenados disponibles:

Si quieres saber más sobre AI Builder hay unhands-on-lab con todos los recursos necesarios para crear tu App usando cualquiera de los 4 modelos.

Y si necesitas un entorno de PowerApps apúntate al PowerApps Community Plan para obtener un entorno gratis en el que podrás usar PowerApps y FLow (y el CDS). Si no te has apuntado todavía, ¡ya tardas!

IA 101

Para explicar cómo funciona esto primero tengo que explicar un poco de conceptos básicos de AI y ML. Pero muy básicos, del palo tan básicos que pueda salir yo delante de gente y explicarlo. Si quieres ver esto mejor explicado mira este video de Channel 9 sobre modelos, es de donde aprendí todo.

En el desarrollo clásico cuando solucionamos un problema lo que hacemos es pasar datos y una función que hemos creado por un proceso y obtenemos un resultado como salida. El equivalente a esto en machine learning es que introducimos datos y las soluciones a un problema y lo que obtenemos es una función que soluciona el problema al que responden las soluciones que hemos introducido. Esta función es tu modelo de ML.

¿Qué más tenemos que saber acerca de lo modelos? Bueno, básicamente que la cantidad de datos con la que alimentamos a nuestro modelo es directamente proporcional a la calidad de las respuestas/soluciones que obtendremos. AI Builder nos pide un mínimo de 15 imágenes para el modelo de detección de objetos. Con 15 imágenes lo que tendremos es un modelo mierdoso, que detectará lo que queremos, pero que también detectará casi como como lo que estamos intentando identificar, porque la muestra es demasiado pequeña.

La PatatApp

¿Por qué este nombre? Pues es que cuento patatas con la app. ¿Y por qué patatas? Porque me encantan, son muy versátiles (puedes hacer tortilla, freírlas, vodka, etc.) y porque contar palés es muy aburrido.

Lo que mi PowerApp hace es detectar las patatas, luego puedo elegir entre usar un diario existente o crear uno nuevo, selecciono un artículo con sus dimensiones de inventario y, finalmente, creo una línea en el diario en AX. He hecho un vídeo muy corto mostrándolo.

Fácil, ¿verdad? Detecto 3 patatas usando AI Builder, selecciono la empresa, creo un diario nuevo y completo el artículo y dimensiones. Finalmente se crea la línea y la tenemos en MSDyn365FO.

Ni brujería ni magia por ningún lado. (ODIO lo de «magia» cuando hablamos de desarrollo o lo que sea, porque parece que se ha hecho sin esfuerzo. Fin de la pataleta.) Para crear la cabecera y la línea del diario estoy usando dos Flows que obtienen los datos de la PowerApp y los crean en Dynamics 365:

¿Véis? No hay magia, sólo un Flow.

Mi compañero Hugo de Jesús me dijo de usarl la función Patch en el data source pero: 1) La app estaba terminada 2) Me lo dijo la semana antes. Pero seguramente hubiera funcionado bien también.

Como véis es una app muy sencilla, la primera versión la tuve lista en cuatro horas, con AI Builder y los Flows es muy rápido.

Modelo mierder vs Modelo no-tan-mierder

Quiero terminar con hechos reales y datos importantes. ¿Recordáis el mínimo de imágenes que pide AI Builder? 15 nada más. Esto es lo que pasa cuando tu modelo está formado por 20 imágenes de solitarias patatas:

Si tu modelo apesta seguirá detectando las patatas en una imagen, pero prácticamente todo será, literalmente, una patata.

Después entrené una segunda versión del modelo, con 40 imágenes de patatas con gatos, personas, otras verduras, etc. El resultado es mucho mejor y sigue detectando patatas:

Quiero agradecer a cazapelusas por hacerme todas las imágenes de las patatitas y por el rediseño de la PowerApp, deberíasi ver la V1. Por favor, adoptad un diseñador gráfico, hará vuestra vida más bonita.

Ninguna patata fue dañada en la creación de esta PowerApp.

Configurar la exportación de Entity Store a Azure Data Lake

Empezar este post es fácil, porque muchos nos podemos preguntar:

¿Qué es un Data Lake?

Pescando en un Data Lake. Cortesía de cazapelusas.

Un Data Lake no es un producto de Azure, es un concepto que hace referencia a un lugar donde se guardan datos, sin importar si son estructurados o no. Su único propósito es guardar los datos sin procesar para que estén listos para ser consumidos por otros sistemas. Es como un lago que recibe y almacena el agua de sus afluentes, solo que con datos en vez de agua.

En Azure el Data Lake es un blob que guarda los datos. Y estos datos pueden venir de Microsoft Dynamics 365 for Finance o Supply Chain Management (voy a acabar loco con los cambios de nombre de Axapta 7) o de otros orígenes.

Actualmente, y desde el PU23, #MSDyn365FO (#MSDyn365F ? o #MSDyn365SCM ?) soporta oficialmente la exportación del Entity Store a un Azure Data Lake storage Gen1, pero la compatibilidad con el Data Lake Storage Gen2 está en preview privada con los Data Feeds, que nos permitirán exportar entidades y tablas (SÍ!) en casi tiempo real. Si queréis saber más echadle un ojo al grupo Data Management, Data Entities, OData and Integrations de Yammer del Insider Program (y si no estáis apuntados, deberíais).

Comparación vs. BYOD

Lo primero de lo que nos vamos a dar cuenta es el precio. El almacenamiento es más barato que una base de datos, incluso si es una sola base de datos en una instancia SaaS en Azure SQL. Por ejemplo, un Blob de 1GB en Azure cuesta 18.22€ al mes.

Y la DB más simple, una Azure SQL Gen 4 con 1 vCore cuesta 160,53€ al mes. Casi 10 veces más.

¿Y el redimiento? Esto que voy a decir es una conclusión de la observación, no un test real de rendimiento, pero los datos se transfieren muy rápido. Y es rápido porque en un Data Lake los datos se mandan sin procesar, no hay transformación de los datos hasta que se consumen (ETL para una DB, ELT para un Data Lake) así que se pierde menos tiempo hasta que los datos llegan a su destino. Esto no tiene un impacto real con conjuntos de datos pequeños, pero sí con los grandes.

Configuración

El proceso para exportar el Entity Store al Data Lake es bastante sencillo y está bien documentado (pero no actualizado del todo) en los docs. Lo explicaré paso a paso.

Crear una cuenta de almacenamiento en Azure

En Azure vamos a las cuentas de almacenamiento y creamos una nueva como en la imagen inferior:

Nos aseguramos de deshabilitar el almacenamiento Gen2:

Y ya podemos crear la cuenta. Cuando esté lista vamos a Access Keys y copiamos la cadena de conexión:

Azure Key Vault

El siguiente paso es crear el Key VAult. Para este paso debemos seleccionar la misma región que la de nuestra instancia de Dynamics 365:

Cuando el Key Vaul esté listo vamos al recurso y creamos un nuevo secreto. Pegaremos la cadena de conexión de la cuenta de almacenamiento y pulsamos crear:

Crear un registro de App de AAD

Le damos un nombre a la App, seleccionamos los tipos de cuenta soportadas que necesitemos y rellenamos la URL con la dirección de nuestra instancia de #MSDyn365FO:

La registramos y ahora tenemos que añadir la API de Azure Key Vault para la app como en la imagen:

Seleccionamos la API y añadimos el permiso delegado de user_impersonation:

No os olvidéis de dar permisos con el botón de encima (debe hacerlo un admin de Azure). Ahora vamos a los secretos, creamos uno y copiamos el valor que nos da. Cuando se cierre esta pestaña no podremos ver el valor del secreto, así que aseguráos de haberlo copiado!

Configurar el Key Vault

Volvemos al Key Vault que hemos creado antes y vamos a Access policies. Creamos una nueva:

Necesitamos seleccionar Get y List para permisos de clave y secreto:

Pulsamos select principal y aquí añadimos la App de AAD que hemos creado en el tercer paso:

Añádela y no te olvides de darle a guardar en la página de las políticas de acceso!!

Configurar MSDyn365F… y O o SCM o como sea que se llame este mes

Vamos a Administración del sistema -> Configurar -> Parámetros del sistema y ahí a la pestaña Conexiones de datos. Aquí tenemos 4 campos que hay que rellenar. El Application ID es el Application ID de la App de AAD (obvio) y el Application Secret  el secreto de esa App. Esta parte está bastante clara.

El DNS name es la URL del Key Vault, y el Secret name es el nombre del secreto que hemos creado en el Key Vault con la cadena de conexión.

Una vez está todo configurado podemos pulsar los botones de prueba y, si habéis seguido todos los pasos, deberían saliros estos mensajes:

Si cualquiera de las validaciones falla simplemente borraría todos los recursos y empezaría de cero.
Ahora, los dos checkbox que tenemos al lado de los campos:
  • Enable Data Lake integration: activa el push de datos del entity store a la cuenta de almacenamiento que hemos creado al principio y que es el principal propósito de este post.
  • Trickle update Data Lake: hace actualizaciones después de que cambien los datos (Trickle Feed).

Configurar Entity Store

Para acabar, vamos al Entity Store (en Administración del sistema-> Configurar -> Entity Store) y activamos el refresco de las entidades que queremos que hidraten el Data Lake (me encanta, parece que es el término correcto para referirse a mandar datos al Data Lake):

Y listo, nuestros datos se están mandando a un Blob de Azure:

Las entidades se guardan cada una en una carpeta, y dentro de cada carpeta hay otra carpeta por cada medida de esa entidad, y dentro un CSV con los datos.

Ahora podemos consumir estos datos desde Power BI con el conector de blob, o alimentar a Azure Data Factory o lo que quieras, porque ese es el propósito del Data Lake.

Desplegar manualmente los paquetes de Retail para Microsoft Dynamics for Finance and Operations

Primer post sobre Microsoft Dynamics 365 for Finance and Operations Retail! Espero que lleguen más.

Como sabréis, uno de los contratiempos del refresco de datos desde producción en LCS es que hay algunos datos que no se copian. Esto es una medida de seguridad para evitar, entre otros, que no se manden correos o se ejecuten los lotes por accidente después de restaurar la DB.

Recordad que es una buena idea tener un script/query de SQL que cambie todos los endpoints, contraseñas, activar usuarios, etc. que se pueda ejecutar después del refresco de datos, igual que teníamos en AX2009/2012. Un F5 en SSMS y el entorno estará listo para usar y tendremos la DB lista para exportar a las máquinas de desarrollo.

Otra de las cosas que no se copian después del restore son los archivos de las cuentas de almacenamiento de Azure: ficheros XLSX del ER, archivos de la DocuValue y los instaladores self-service de Retail.

Paquetes de Retail

Los paquetes de Retail son los ejecutables que se usan para instalar el MPOS en el… bueno, en el terminal de venta (POS). Estos archivos están guardados en una cuenta de almacenamiento de Azure blob storage que es específico de cada entorno, así que cuando se refresca la DB no están los paquetes de autoservicio en el entorno de destino:

La solución oficial de Microsoft es aplicar un paquete binario que recreará los archivos EXE en la cuenta de almacenamiento de la máquina virtual donde se ejecute el paquete desplegable.Y como sabemos ya todos esto requiere de un tiempo, y aunque se puede ejecutar fuera de horario también lo podemos arreglar en menos de 10 minutos.

El workaround

Ahhh el «workaround»… una palabra tan bonita y que tiene tantos significados… Truquillo, ñapa, arreglo… Esta vez encima tiene una restricción: sólo va a funcionar en VMs de desarrollo y Tier 2+ normales, no se puede hacer en los entornos de tipo self-service porque no tenemos acceso la máquina del AOS.

Lo que tenemos que hacer es entrar a una de las máquinas del AOS por RDP e ir al disco de servicio (normalmente la K en desarrollo y G en los Tier 2+). Debería haber una carpeta llamada DeployablePackages si has aplicado alguno, sino hay que seguir con el método oficial. Si no existe la carpeta seguramente funcione también usando los archivos que hay en la unidad de instalación, pero no lo he probado.

Ordenamos los archivos por fecha de modificación (nuevos primero) y dentro del primero debería haber otra carpeta llamada RetailSelfService:

Y dentro de esa carpeta verás 3 carpetas más, Packages, Scripts y ServiceModel. Dentro de la carpeta Packages están los archivos EXE y en el Scripts están los scripts (un aplauso por favor), abrimos este último y vamos a la carpeta Upgrade, ahí encontraremos un script de PowerShell que se llama UpdateRetailSelfService. Tenemos que ejecutar este script en PowerShell como administrador. Va a tardar entre 3 y 5 minutos y cuando termine los paquetes estarán subidos a la cuenta de almacenamiento y ya aparecerán en el formulario de parámetros de Retail.

¡Eso a mi no me funciona!

Hay un caso es el que los instaladores no se cargan: si no tienes ninguna configuración hecha para Retail. ¿Por qué? El script de PowerShell comprueba lo siguiente:

  • Que existan datos de canal en la Channel DB
  • Que existan datos de canal en el AOS
  • Que haya transacciones en el AOS
  • Que haya datos de transacciones en la Channel DB
  • Que existan extensiones de la Channel DB

Si no se cumple ninguna de estas condiciones el script se salta la creación de los instaladores en el blob. ¡Pero podemos hacer algo! Sí, como configurar un canal por ejemplo. Pero ¿y si no tienes ganas?

Recuerdas el script UpdateRetailSelfService de antes? Sólo tienes que editarlo y comentar las siguientes líneas:

Esto hará que el script se salte la validación y se desplieguen los instaladores.

Es bastante guarro, ¿verdad? Sí. Hace honor a workaround.

¿Qué hay de los entornos self-service?

Estoy convencido de que esto también se puede conseguir modificando un Deployable Package (uno de los de las actualizaciones mensuales), dejando solo Retail en el archivo DefaultTopologyData.xml, e incluso editar el script si hace falta. Pero no lo he probado. ¿Algún voluntario?

Parsea XML y JSON en MSDyn365FO fácilmente

Hace un tiempo tuve que crear un interfasado entre MSDyn365FO yun sistema externo que devolvía los datos en XML. Decidí usar las clases XML de X++ (XmlDocument, XmlNodeList, XmlElement, etc…) para parsear el XML y acceder a los datos. Estas clases son horrorosas. Sacas el trabajo pero de una forma fea fea. Hay un método mejor para parsear XML o JSON en MSDyn365FO.

.NET al rescate

Hay una funcionalidad en Visual Studio que nos ayudará con esto, pero no está disponible para los proyectos de tipo Unified Oprations. Para usarlo sólo hay que abrir Visual Stuio y crear un proyecto de .NET. Ahora copias un ejemplo de XML que quieras parsear, vas al menú Edit, Paste Special, Paste XML As Classes:

Y con esto tendremos un contrato de datos con todos los elementos necesarios para acceder a todos los nodos del XML usando notación con puntos para accede a los datos! Por ejemplo, para este XML de ejemplo obtendremos este código:

Podemos crear esto en una Class Library de .NET y consumirla desde Finance and Operations. Este es el método más rápido para usar todas las clases y miembros de estas. Seguramente se pueda implementar como clases de Dynamics 365 FnO, pero habrís que crear tantas clases como tipos distintos de nodos haya en el XML. Y el propósito original de esto era parsear el XML más rápido. Yo me quedaría con la librería de .NET.

Todos estos pasos también son válidos para un archivo JSON, copias el JSON, paste special y tenemos todas las clases para acceder a los datos.

Usándolo en Dynamics

Una vez tenemos la librería o las clases creadas en Microsoft Dynamics 365 (no hagáis esto anda) añadimos la referencia al proyecto y (siguiendo el ejemplo) hacemos lo siguiente:

Declaramos una variable del mismo tipo que el nodo raíz (el principal), catalog en el ejemplo. Creamos un nuevo XmlSerializer usando nuestro tipo y creamos un TextReader a partir del XML como cadena. Finalmente necesitamos deserializar el XML y asignar el resultado al catalog y…

Como se ve, podemos acceder a los datos usando notación de puntos y las clases que se crearon al usar la función del pegado especial.

Con la ayuda de herramientas que no son específicas de la programación en X++ podemos conseguir esto, y, definitivamente, es más rápido que tener que parsear el XML usando las clases Xml de Dynamics.

Gestión de características: crear una característica personalizada

La gestión de características (Feature management) lleva disponible en Microsoft Dynamics 365 for Finance and Operations desde hace un tiempo. Antes de eso las características se activaban con el flighting, ejecutando una consulta en SQL en las máquinas de desarrollo y UAT (y en prod lo hacía el equipo de DSE).

Ahora tenemos una área de trabajo (workspace) muy bonito que muestra todas las características disponibles, y el flighting sigue por aquí también. La principal diferencia es que el flighting se usa para activar características a clientes en concreto, como una preview de una feature.

En cada nuevo PU aparecen nuevas funcionalidades a MSDyn365FO, y en el PU30, publicado recientemente bajo el programa PEAP (Preview Early Access Program), se ha añadido más funcionalidad a la Gestión de características, esta vez es la Feature class, una nueva propiedad para los Menu Items y los Menus:

Esto todavía no está disponible, y si intentas añadir una Feature Class a un menu item aparecerá un warning y no habrá ninguna funcionalidad.

Si leemos la documentación veremos que crear características personalizadas todavía no está disponible, pero si buscamos las clases de características en los metadatos y probamos un poco…

Creando una característica personalizada

Vamos a usar la clase TaxSetupValidationFeature como ejemplo. Esta clase implementa la interfaz IFeatureMetadata, y todas las clases de características usan el patrón Singleton para obtener la instancia! (Es emocionante porque es la primera vez que lo veo en MSDyn365FO).

Los métodos que se implementan incluyen el nombre y descripción, el modelo y algo de configuración. Simplemente copiamos todos los métodos y la variable, lo pegamos en la clase que hemos creado y cambiamos lo que haga falta.

 

Ahora compilamos la solución y vamos al workspace de gestión de características, pulsamos en comprobar actualizaciones y nuestra característica debería estar en la lista:

Vamos a usar la nueva característica (de una forma un poco tonta). Creamos una extensión de un formulario y en su método init comprobamos si la característica está activa, si lo está mostramos un mensaje:

Antes de activar la característica comprobamos que no se muestra nada en el formulario:

Todo bien, no hay mensaje.

Volvemos a la gestión de características y la activamos.

Volvemos al formulario extendido (la CustTable en mi ejemplo) y…

Y ahi tenemos el mensaje!

Las características personalizadas están funcionando en el PU30, por menos en máquinas de desarrollo, y quizá en entornos sandbox Tier 2+. Pero no probéis esto en producción hasta que esté disponible oficialmente (aunque tampoco es posible al ser un PEAP)

Esto sólo es una pequeña prueba de las clases que están disponibles ya, veremos nuevas funcionalidades en el PU31 cuando la Feature Class funcione, y como leí en Twitter:

Configurar las nuevas tareas de Azure DevOps para generar el paquete y versiones de modelos

Durante la pasada noche (que por lo menos era noche para mí :P) se han publicado las nuevas tareas de Azure DevOps para desplegar los paquetes, actualizar versiones de modelos y añadir licencias a los DPs:

Se ha publicado tambien un anuncio en los blogs de Community con más detalles acerca de la configuración. Vamos a ver las nuevas tareas y cómo configurarlas.

Tarea Update Model Version

Esta es sencillita, simplemente hay que añadirla a tu definición de build debajo de la tarea actual, deshabilitas la original y listo. Si tienes algún filtro, excluyendo modelos por ej., necesitaras crear el filtro en el campo Descriptor Search Pattern usando la sintaxis de patrones de Azure DevOps.

Tarea Create Deployable Package

Esta tarea va a sustituir la Generate packages actual. Para configurarla correctamente necesitamos hacer un par de cambios a los valores que trae por defecto:

X++ Tools Path

Esto es el directorio físico de tu VM de build donde está la carpeta bin. La carpeta AosService normalmente está en la unidad K en las VMs desplegadas en la suscripción del cliente. Probablemente esto cambie cuando pasemos a un modelo sin VMs para hacer las builds.

Edito!: la ruta a la unidad se puede cambiar por $(ServiceDrive), quedando una ruta como $(ServiceDrive)\AOSService\PackagesLocalDirectory\bin.

Location of the X++ binaries to package

La tarea viene con este campo rellenado con $(Build.BinariesDirectory) por defecto, pero esto no nos ha funcionado para nuestras builds, quizás esa variable no esta en el archivo proj. Sólo hay que cambiarlo por $(Agent.BuildDirectory)\Bin y el DP se generará sin problemas.

Filename and path for the deployable package

La ruta en la imagen debería cambiarse por $(Build.ArtifactStagingDirectory)\Packages\AXDeployableRuntime_$(Build.BuildNumber).zip. Se puede dejar sin la parte de Packages pero entonces habra que cambiar el campo Path to Publish de la tarea Publish Artifact: Package de la definición.

Tarea Add Licenses to Deployable Package

Esta tarea añade las licencias a un Deployable Package que ya existe. Recuerda que la ruta del DP tiene que ser la misma que hayas configurado en la tarea Create Deployable Package.

¡Y ya esta todo listo! Un pasito más cerca de deshacernos de las VM de build.

Si necesitas ayuda para configurar Configurar Release en Azure DevOps puedes leer este post que escribí.

 

Application Checker: mejores practicas de programación?

Si no has trabajado en un ISV hay muchas posibilidades de que nunca te hayas preocupado de las Best Practices de Dynamics (BP), o quizá sí. Yo no he trabajado para un ISV pero cuando empecé a trabajar con AX me dieron el documento de BP de desarrollo y he intentado seguir la mayoría cuando programo.

Pero las BPs se podían ignorar y no seguirlas sin problema. Esto es por lo que Microsoft va a publicar…

Application Checker

Application Checker es una herraminta que pretende cambiar esto. Va a forzar algunas reglas que nuestro código tendrá que cumplir, de otra forma no va a compilar (y quizá ni se despliegue en los entornos).

Tuvimos un pequeño avance en el pasado MBAS, en la sesión “X++ programming with quality” de Dave Froslie y Peter Villadsen. Desafortunadamente la sesión no se grabó.

App checker usa BaseX, una herramienta de análisis XML, y alimenta a Socratex, que usará Microsoft para auditar la calidad del código. No sé si Socratex será publicado y no recuerdo si se dijo nada durante la charla.

El conjunto de reglas se puede encontrar en elproyecto de GitHub de Application Checker y todavía es WIP. Creo que hay muuuuchas cosas a decidir antes de que esto sea GA, y algunas reglas me preocupan y asustan 😛

Tipos de reglas

Hay distintos tipos de reglas, algunas provocarán errores y otras warnings. Por ejemplo:

ExtensionsWithoutPrefix.xq: esta regla provocará un error evitando que tu código compile. Comprueba que una clase de extensión que acabe en _Extension y el atributo ExtensionOf. En caso de que así sea debe tener un prefijo. Por ej.: si extendemos la clase CustPostInvoice no se puede llamar CustPostInvoice_Extension, necesita un prefijo como CustPostInvoiceAAS_Extension.

SelectForUpdateAbsent.xq: esta regla lanzará un warning. Si hay una cláusula forUpdate en un select y no se llama a doUpdate, update, delete, doDelete o write posteriormente nos lo hará saber.

A día de hoy hay 21 reglas en el proyecto de GitHub. Se puede contribuir al proyecto, o puedes crear tus propias reglas sin mandarlas al proyecto y forzarlas en las máquinas de desarrollo con añadirla a la carpeta local de reglas. Yo crearía una que hiciera que después de un if/while/for/switch sea obligatorio dejar el espacio en blanco, pero eso sólo es cosa de mi TOC al escribir/leer código.

Pruébalo en tu código

Podemos usar Application Checker en nuestras VMs de desarrollo desde, creo, el PU26. Sólo tenemos que instalar JRE y BaseX en la máquina y seleccionar el check al hacer una full build.

Algunos ejemplos

ComplexityIndentationCombined.xq

Esta consulta comprueba la (agarráos a la silla)This query checks the (wait for it…) complejidad ciclomática de los métodos. Lo intentaré explicar… la complejidad ciclomática es una métrica de calidad de software, y es el número de caminos independientes que puede seguir el código. Dependiendo del número de ifs, whiles, switches, etc… el código puede tener distintas salidas por distintos caminos, es es lo que calcula la complejidad.

Tomando este ejemplo, que es muy sencillo para demostrarlo, vemos la gran cantidad de caminos que podría seguir:

En App checker el error aparece cuando la complejidad es más de 30. He usado el analizador de complejidad Lizard para calcular la complejidad del método y es de 49.

La regla también comprueba la profundidad de indentación, fallando si es mayor de 2. Al final el propósito de estas reglas es que troceemos métodos largos en partes más pequeñas, lo que también ayudará a que nuestro código sea más extensible, como hizo Microsoft con las clases Data Provider de los informes.

BalancedTtsStatement.xq

Con esta regla tengo sentimientos encontrados. La regla comprueba que los ttsbegin y ttscommit de un método estén dentro del mismo ámbito. Así que esto ya no será posible:

Imagina que has desarrollado una integración con una aplicación externa que introduce datos en una tabla intermedia y que luego se procesan esos datos de forma secuencial. No quieres que si algo falla haya un throw para que el proceso siga con el siguiente registro, así que llamas a ttsabort, guardas el error y continúas. Si esto no es posible… cómo deberemos hacerlo? Crear un lote que genere una tarea por cada línea a procesar?

Además, los modelos del estándar están llenos de ttscommit dentro de ifs.

RecursiveMethods.xq

Esta regla bloqueará el uso de recursividad en métodos estáticos. No tengo muy claro por qué. Application checker debería ser una forma de promover buenas prácticas, no prohibir algunos patrones. Si alguien mete un método recursivo en producción y no se llega nunca a la condición de salida… hola testeo?

Algunas reflexiones finales

¿Va a forzar esto a programar mejor? Lo dudo mucho, pero seguramente no sea el propósito de App Checker. Durante milenios los humanos hemos encontrado la forma de saltarnos las reglas, leyes y todo tipo de restricciones, y esto no será una excepción.

¿Nos ayudará? ¡Joder sí! Pero la mejor forma de asegurarnos de la calidad del código es promoviendo las buenas prácticas en nuestros equipos, a través de formación interna o revisiones de código. E incluso así si a alguien le importa el código limpio un pepino seguirá escribiendo código terrible. Puede que funcione pero no será bonito.

Finalmente, algunas reglas no me convencen. Como lo de evitar la recursividad o el tema de los tts. Tendremos que esperar a ver qué reglas se lanzan finalmente y cómo se va a implantar Application checker en el ALM de MSDyn365FO, bloqueando (o no) los despliegues de código o si se va a incluir en el proceso de build.

Entornos self-service: el futuro que ya está aquí

Ahora mismo Microsoft Dynamics 365 for Finance and Operations tiene una arquitectura de tipo monolítico, aunque está en la nube de Azure lo que tenemos en realidad es una (o varias en el caso de los entornos Tier 2+) máquina virtual que ejecuta todo: el AOS/IIS, Azure SQL Server, el servicio de lotes, MR, etc. Igual que teníamos en AX 2009/2012.

Esto va a cambiar en los próximos meses con los entornos self-service (o autogestionados). Vamos a pasar de una arquitectura monolítica a una de microservicios que ejecutarán todos los componentes con la ayuda de Azure Service Fabric. MSDyn365FO estará finalmente en un modelo SAAS real.

Antes de empezar quiero dejar claro que todos estos cambios solo afectan a los entornos Tier 2+ gestionados por Microsoft: entornos de tipo sandbox y producción. El entorno de build (hasta que desaparezca) y los entornos hospedados en la nube de la suscripción del partner o cliente seguirán siendo máquinas virtuales.

¿Qué cambia?

Despliegues más rápidos

Cuando despliegues un nuevo entorno se va a iniciar el despliegue en el momento, sin esperar a que lo haga Microsoft (es self-service). Además, gracias a la nueva arquitectura de microservicios, estará disponible en unos 30 minutos en comparación con las 6-8 horas que hay que esperar ahora. La primera vez parece…

Estimación de suscripción

Seguimos necesitando rellenar la estimación de la suscripción por un tema de licencias y para que MS estime la capacidad que debe asignar al entorno de producción. Los entornos self-service se pueden escalar más rápido (poner o quitar un AOS por ej.).

No hay acceso por RDP

El acceso al escritorio de la VM se ha quitado porque… bueno, supongo que es porque no hay una VM ya. Todas las operaciones que necesitaran que accedieramos al RDP se podrán hacer desde LCS.

No hay acceso a SQL Server

Exacto, que no haya acceso por RDP quiere decir que tampoco podemos acceder por RDP a la máquina de SQL. Seguimos teniendo acceso a la DB en Azure SQL, sólo tenemos que solicitarlo desde LCS y se activa en unos segundos:

Además hay que poner la IP desde la que se vaya a conectar a SQL en la withelist desde el botón Maintain – Enable access de LCS para poder conectar al Azure SQL Server. El acceso a la DB y la regla en el firewall estarán activos durante 8 horas.

Como siempre, no tenemos acceso a la DB de producción.

Un deployable package para gobernarlos a todos

Si has intentado desplegar un deployable package (DP) que no tenga todos los paquetes o modelos que tiene el entorno (básicamente creando el DP desde Visual Studio) habrás visto que aparece un aviso sobre la diferencia entre entorno y DP.

Con los entornos self-service tienes que incluir todos los modelos/paquetes Y!!! ISVs en un único DP.

Actualizar producción

Primero de todo, podemos lanzar la actualización de producción sin las 5 horas de preaviso que necesitamos ahora. Seguimos pudiendo programar el despliegue pero también lo podemos iniciar en el momento.

Después, la forma en la que se actualiza el entorno de producción ha cambiado un poco de lo que hacemos ahora. Con los nuevos entornos lo que haremos será actualizar un entorno de sandbox como hacemos ahora, y una vez esté hecho, seleccionaremos el entorno de sandbox para promocionarlo a producción. Esto supongo que es otro beneficio de los cambios de arquitectura.

En el futuro el tiempo de mantenimiento también se reducirá a cero para las actualizaciones de servicio siempre que estemos en la última actualización. Esto no será posible para DPs con código nuestro.

¿Cómo consigo estos entornos?

Por el momento esto sólo está disponible para algunos nuevos clientes. Los clientes actuales serán migrados en los próximos meses, Microsoft los contactará para programar una ventana de mantenimiento para aplicar los cambios.

Para más información podéis ver la sesión Microsoft Dynamics 365 for Finance and Operations: Strategic Lifecycle Services Investments del MBAS del pasado junio.

Nuestra experiencia

Llevamos en la preview privada desde hace casi un año con uno de los clientes de Axazure. El cliente ya está con producción en live con un entorno self-service y ha ido todo bien.

Pero los inicios fueron un poco duros. Muchas de las funcionalidades aun no estbaan disponibles en ese momento, como el refresco de DB o… el despliegue de DPs. Sí, teníamos que solicitar a MS cada vez que queríamos subir cambios a un entorno. ¡No podíamos ni poner los entornos en mantenimiento! En los primeros meses de 2019 se añadió un montón de funcionalidad a LCS, y en junio finalmente pudimos hacer los despliegues en producción nosotros mismos. La ayuda que hemos tendido por parte del equipo de producto de Microsoft ha sido clave y nos han desbloqueado algunos problemas que estaban deteniendo el progreso del proyecto.

Operaciones «set-based» lentas?

En Microsoft Dynamics 365 for Finance and Operations podemos ejecutar operaciones CRUD en el código de dos formas, operaciones set-based (que podríamos traducir como basadas en conjuntos más o menos) y registro a registro.

La recomendación de Microsoft es usar siempre las operaciones set-based, si es posible, como se puede ver en la sesión Implementation Best Practices for Dynamics 365: Performance best practices for a successful Dynamics 365 Finance and Operations implementation del pasado Business Applications Summit de junio.

¿Por qué?

Set-based Vs. Registro a registro

Cuando ejecutamos una query en MSDyn365FO usando la capa de acceso a datos lo que ocurre es que nuestra consulta termina convertida en un comando SQL. Podemos ver las diferencias usando el método getSQLStatement de la clase xRecord con un generateonly en la query (y un forceliterals para mostrar los valores de los parámetros) para obtener la consulta de SQL. Por ejemplo si ejecutamos el siguiente código:

Obtenemos esta consulta de SQL:

 

Podemos ver que se seleccionan todos los campos y que los filtros en el where incluyen la cuenta que hemos filtrado (además del DataAreaId y Partition).

Cuando se ejecuta un while select en MSDyn365FO, lo que ocurre en SQL Server es que se ejecuta una consulta por cada vuelta del while. Lo mismo ocurre si se ejecuta un update o delete dentro del bucle. Esto se conoce como operación registro a registro.

Imaginad que queréis actualizar la nota de todos los clientes que sean del grupo de clientes 10. Podríamos hacerlo con un while select de esta manera:

Esto ejecutaría tantas consultas en SQL como clientes del grupo 10 existan, una por cada vuelta del bucle. O podríamos usar operaciones set-based:

Esto ejecutará la actualización de todos los clientes del grupo 10 en una única consulta de SQL Server en vez de una por cliente:

Existen tres operaciones set-based en MSDyn365FO, update_recordset para actualizar, insert_recordset para crear registros y delete_from para borrarlos de forma masiva. Además de poder hacer inserciones masivas con las listas de tipo RecordSortedList y RecordInsertList.

Ejecutar estos métodos en vez de while selects debería ser por lo general más rápido porque se lanza una sola consulta SQL. Pero…

¿Por qué son lentas mis operaciones set-based?

Existen algunos escenarios documentados en los que las operaciones set-based se convierten en operaciones registro a registro como podemos ver en la siguiente tabla:

DELETE_FROMUPDATE_RECORDSETINSERT_RECORDSETARRAY_INSERTUse … to override
Non-SQL tablesYesYesYesYesNot applicable
Delete actionsYesNoNoNoskipDeleteActions
Database log enabledYesYesYesNoskipDatabaseLog
Overridden methodYesYesYesYesskipDataMethods
Alerts set up for tableYesYesYesNoskipEvents
ValidTimeStateFieldType property not equal to None on a tableYesYesYesYesNot applicable

En el ejemplo, si el método update de la CustTable está sobreescrito (que lo está) la operación del update_recordset se ejecutará como si fuera un while select, actualizando registro a registro.

En el caso del update_recordset esto se puede solucionar llamando al método skipDataMethods antes de ejecutar el update:

Esto hace que no se ejecute el método update (o insert si se hace un insert_recordset), más o menos como si se hiciera un doUpdate en el loop. El resto de los métodos también se pueden saltar con el método correspondiente que aparece en la última columna de la tabla.

Así que, para actualizaciones masivas siempre deberíamos usar operaciones set-based, y activar esto también en las data entities con la propiedad EnableSetBasedSqlOperations.

Y ahora viene otro pero.

¿Debería usar siempre operaciones set-based para actualizaciones masivas?

Bueno, depende de con qué datos estemos trabajando. Hay un post magnífico de Denis Trunin llamado «Blocking in D365FO(and why you shouldn’t always follow MS recommendations)» que explica un ejemplo perfecto de cuándo las operaciones set-based podrían ser perjudiciales.

Como siempre, desarrollar en un ERP es bastante delicado, y escenarios parecidos pueden tener soluciones totalmente diferentes. Simplemente hay que analizar y tomar la decisión.