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.