Category

X++

Category

Primero de todo… AVISO: antes de usar esto en un entorno de producción pensadlo bien. Y luego volvedlo a pensar. Y si finalmente decides usarlo, hacedlo con cuidado y cariño.

Por qué este aviso? Bueno, a pesar de que los documentos aseguran que la afectación en el rendimiento del sistema en general es mínima hay que andar con cuidado. Es un ERP. Uno en el que no tenemos acceso al entorno de producción (a no ser que estéis On-Prem) para analizar si hay impacto. Además probablemente Microsoft ya está usandolo para recoger datos de los entornos y mostrarlos en LCS, y desconozco si puede haber interferencias. Un montón de no-lo-ses.

Lo usaría en producción? . Puede ser muy útil en algunos casos.

Y dicho esto, de qué voy a escribir que necesita un aviso? Como dice el título, sobre usar Azure Application Insights en Microsoft Dynamics 365 for Finance and Operations. Este post es consecuencia de uno de los «Has visto esto? Sí, deberíamos probarlo!» entre Juanan (aquí en Twitter, seguidle!) y yo. Y el esto esta vez era este post de Lane Swenka en AX Developer Connection. Así que nada original por aquí 🙂

Azure Application Insights

I spy
Made by Cazapelusas

¿Y qué es Application Insights? Como dice la documentación:

Application Insights is an extensible Application Performance Management (APM) service for web developers on multiple platforms. Use it to monitor your blah web application. It will blah blah detect blaaah anomalies. It blah powerful blahblah tools to bleh blah blih and blah blah blaaaah. It’s blaaaaaaaah.

Mmmm… ved este vídeo mejor:

Hay tanta miseria y tristeza en los primeros 30 segundos…

Monitoreo. Eso hace y para eso es. «Eh, pero LCS ya hace eso!«. Vale, monitoreo extra! A todo el mundo le gusta lo extra, como la pizza, excepto si es piña, claro.

Haciendo que funcione

El primer paso será crear un recurso para Application Insights en nuestra suscripción de Azure. Sobre el precio: los 5 primeros gigas por mes son gratuitos, y los datos se guardan durante 90 días. Más información aquí.

Después necesitamos el código. Me voy a ahorrar los detalles en esta parte porque está perfectamente explicado en el link que he puesto antes (este). Básicamente tienes que crear una DLL para manejar los eventos y mandar la información a AAI y usar esa DLL desde MSDyn365FO. En nuestra versión hemos añadido un método extra para trazas llamado trackTrace. Después solo hay que referenciar la DLL en 365 y ya lo podemos usar.

Qué podemos medir?

Ahora viene la parte interesante (espero). Visitas de páginas, capturar errores (o todos los infologs), ejecuciones de lotes, cambios de valor de campos, y cualquier cosa que podamos extender y desde ahí llamar a nuestra API.

Por ejemplo, podemos extender la clase FormDataUtil del motor de formularios. Esta clase tiene varios métodos que se llaman desde los formularios en acciones de los datasources, como validaciones de writes, deletes, campos, etc… Y también esto:

modifiedField in FormDataUtils

Este método se ejecuta cada vez que se modifica un campo en un formulario. Lo vamos a extender para registrar qué campo se ha modificado, el valor anterior y el nuevo. Así:

Extending modifiedField
Prometo que siempre uso etiquetas!

Y como la llamada a Application Insights también guarda el usuario que ha hecho el cambio de valor, tenemos un nuevo log de la base de datos! Incluso mejor, tenemos un nuevo registro de la base de datos que no afecta al rendimiento porque no se generan datos extra en MSDyn365FO. La única pega es que solo se llamará desde formularios, pero puede ser suficiente para monitorear el uso de formularios y los “yo no he tocado ningún parámetro!” 🙂

Esto es lo que vemos en el explorador de métricas de Azure Application Insights:

Azure Application Insights Custom Event
Qué quieres decir con que he tocado eso!?

Sí, fuiste tú usuario Admin! Uy si soy yo…

Custom events

Todas las métricas de los eventos se muestra  en Azure, y los datos se pueden mostrar en Power BI.

Repito, planead bien lo que queréis monitorear antes de usar esto y testeadlo. Luego testeadlo otra vez, sobretodo en entornos SAT con bases de datos Azure SQL. Tienen un rendimiento distinto a un SQL Server normal y hay que estar seguro.

A disfrutar de los datos!

Una de las opciones para realizar integraciones con MSDyn365FO es usar las data entities con servicios REST a través de OData. Para poder usar las entidades con servicios REST solo tenemos que comprobar que tengan la propiedad IsPublic a Yes:

Entidad Clientes V3

Si no la tienen, y es una entity estándar, tocará duplicarla porque no es posible editar la propiedad en una extensión.

Si realizamos una integración con un sistema externo a través de OData para insertar datos en el ERP, nos podemos encontrar con el problema que al generar el registro sea obligatorio el ID de ese registro, como ocurre con los clientes. Si vemos la propiedad Mandatory del campo CustomerAccount está en Auto, con lo que hereda el valor de la propiedad en la tabla CustTable donde esta a Yes.

En este caso si intentamos crear un cliente sin número de cuenta el servicio va a fallar como podemos ver en esta captura de pantalla de Postman:

Postman fail :(

El error es claro, la cuenta de cliente no puede estar vacía.

Esto no sucede así con la entidad de proveedores. «Pero si la cuenta de proveedor también es obligatoria en la VendTable!» dirá alguno. Sí, lo es, pero en la entidad no lo es:

Vendors V2

Para ver cómo lo soluciona el estándar vamos a ver el initValue de la entidad de proveedores:

Generando valores de secuencias numericas desde servicios REST y OData 1

Tiene truco!

El método skipNumberSequenceCheck es uno de los métodos de datos de la clase Common, y es de la familia de los skipDataMethods, skipDataSourceValidateWrite, skipAosValidation, etc… El método siempre nos devolverá un false a no ser que le digamos antes lo contrario, pasándole el parámetro true en las llamadas en el código.

El método enableNumberSequenceControlForField de la clase NumberSeqRecordFieldHandler lo que hace es inicializar el valor del campo que le pasamos por parámetro con el next de la secuencia numérica que le digamos. En este caso rellena la cuenta de proveedor con la secuencia que este configurada en los parámetros de proveedores para la cuenta de proveedor (lógico).

Reproduciendo el estándar, vamos a crear una clase de extensión para la data entity y extenderemos el método initValue:

Extensión de código de la entity Customers V3

Con esto podemos volver a probar a generar un nuevo cliente en Postman, borrando el parámetro de cuenta de cliente del cuerpo del mensaje, y…

Cliente creado por servicio REST y OData

Premio! Tenemos un nuevo cliente! Creado desde un sistema externo, usando la secuencia numérica del ERP.

Esto no tiene ningún misterio, sobretodo porque reproduce el comportamiento de las partes del estándar que ya lo hacen. Algo que deberíamos intentar hacer siempre, seguir el estándar. Siempre que podamos claro :), porque aunque se intente que los clientes se adapten al estándar lo máximo posible, todos sabemos que siempre, siempre, siempre habrá que hacer alguna customización (menos mal, que somos devs).

Aquí puedes leer mi guía completa sobre Dynamics 365 for Finance and Operations y Azure DevOps.

En la primera parte de este post vimos a importancia de Azure DevOps y como configurarlo para MSDyn365FO.

Quiero empezar esta segunda parte con una pequeña pataleta. Como decía en la primera parte, los que llevamos años trabajando con AX nos habíamos acostumbrado a no usar un control de versiones. MSDyn365FO nos ha llevado a un terreno sin explorar, con lo que no es raro que cada equipo haya decidido trabajar de una forma u otra según las experiencias que se hayan ido encontrando. Evidentemente, hay un componente del interés de los miembros de estos equipos para investigar un poco por su cuenta sobre la gestión de código, ramas y metodologías. Muchas veces a base de experimentación y prueba-error, y con las prisas de algunos proyectos esto sale mal, o muy mal. Y aquí he echado de menos un poco de guidance por parte de Microsoft (que igual la hay y me lo he perdido).

A pesar de esta quejita el camino y el aprendizaje ha sido, y creo que lo que viene también será, bastante divertido 😉

Estrategias de branching

Vaya por delante que no soy, ni mucho menos, un experto en gestión del código ni Azure DevOps. Todo lo que viene a continuación es fruto, como comentaba antes, de la experiencia (y las meteduras de pata) de casi 3 años trabajando con MSDyn365Ops. En este artículo de la documentación sobre estrategias de branching hay más información sobre branching y links a artículos del equipo de DevOps. Y en la Biblioteca de herramientas y guías de los DevOps Rangers hay incluso muchísimo más!

La verdad es que me encantaría una sesión de FastTrack sobre esto y, creo que no la hay. EDIT: parece que no lo vi y sí que existe una sesión de FastTrack sobre esto que se llama Developer ALM. Gracias a Dag Calafell (twitter) por la información!

Como vimos en la primera parte cuando desplegamos la máquina de Build se crea la carpeta de Main. Lo normal es que en un proyecto de implantación se desarrolle sobre Main hasta el momento del arranque, y que justo antes del go live se cree una branch (rama) de desarrollo. El árbol de código quedaría así:

Ramas despues de branch

En ese momento los mapeos de las máquinas de desarrollo deben cambiarse para que apunten a esta nueva rama de dev. Esto permitirá seguir desarrollando mejoras o corrigiendo errores y decidir el momento en el que se van a mover a producción haciendo un merge a Main.

Esta estrategia es bastante sencilla y que no provoca muchos quebraderos de cabeza. En mi anterior trabajo en cliente final decidimos usar 3 ramas por peculiaridades de la empresa. Main, Dev y Test con merges de Dev a Test y de Test a Main. Un dolor de cabeza al final, gestionar las 3 ramas, con upgrades de versiones, decenas de changesets pendientes y un partner ISV que no ayudaba mucho era bastante divertido.  ¿Pero, y lo que aprendí? Buf.

En cualquier caso un consejo: intentad evitar que se queden changesets pendientes de mergear durante mucho tiempo. La cantidad de conflictos que aparecen y hay que resolver a mano es directamente proporcional a lo viejo que sea el changeset.

Llegado a este punto no puedo hacer suficiente hincapié en lo de normal de más arriba. Como digo, esto lo escribo basado en mis experiencias. Está claro que no es lo mismo trabajar en un partner de implantación que en un ISV. Un ISV tiene la necesidad de mantener diferentes versiones de su producto y no va a usar una rama Main y otra Dev sinó que puede tener una (o varias) por versión a mantener para dar soporte a todos los clientes (aunque desde la 8.1 y el fin del overlayering ya no es necesario). Para más «ideas» el artículo que he enlazado al principio es perfecto para empezar.

Builds

En la primera parte y en otro post (Builds que no responden en Azure DevOps) expliqué un poco acerca de las builds. Ya vimos que la definición de build que se genera por defecto al desplegar el servidor es como la de la imagen inferior:

Pasos de la definición de build por defecto

Esta build tiene todos los pasos con los que se ha creado activos. Podemos desactivar (o borrar) los pasos que no necesitemos. Por ejemplo, los 3 de testeo si no tenemos tests creados, o la sincronización y despliegue de informes.

Podemos crear nuevas definiciones de build a partir de esta o de 0 (pero es más sencillo y rápido duplicar esta y modificarla) para que se apliquen a otras ramas u otros motivos.

Con la versión 8.1 de MSDyn365FO han desaparecido los hotfixes de código X++, todos son binarios. Esto lo que implica es que en la carpeta Metadata de nuestras ramas ya no van a aparecer los modelos del estándar, solo los nuestros. Hasta la versión 8.0 era muy útil tener una definición de build únicamente para nuestros modelos y otra con todos los modelos. Con esto lo que se consigue es tener un DP en mucho menos tiempo que generándolo para todos los modelos. Si se aplica algún hotfix hay que generar el DP de la rama con todos los modelos, pero si sólo hay código propio no hace falta generar un paquete con todos los modelos.

Y hasta aquí la información desactualizada. A estas alturas todos los proyectos deberían estar en 8.1 o listos para estarlo, que en abril llega One Version!

Otra opción que es bastante útil es que, por ejemplo, podemos crear una nueva definición que lo único que haga es compilar una rama:

Definicion build continua

Esta build no hace nada más, solo compila. Así a priori no parece muy útil pero si activamos la opción de integración continua:

DevOps continuous integration

Después de cada check-in de cada desarrollador se lanzará una build que compilará todo el código y fallará si hay algún error. ¿Claro que no debería fallar no? Porque todos compilamos los proyectos antes de hacer el check-in, ¿verdad?

tysonjaja

Pues por eso y porque las prisas son malas y a veces tenemos que vivir con ellas, esta build puede ser bastante útil. Sobretodo cuando lo tengamos configurado para la rama Main y nos «chive» los errores que pueden aparecer después de un merge con conflictos. Y cuando hay que pasar algo urgente a producción y tenemos poco margen nos interesa poder generar el paquete lo antes posible. Usando esta estrategia conseguimos que generar un DP con nuestros paquetes tardara 9 minutos en vez de 1h15m generando todo.

Igual alguien con más conocimiento de esto piensa, pero eso no lo puedes hacer con…

Gated check-ins

Con este tipo de check-in el código se compila ANTES de que el check-in se haga efectivo. Si falla la build el changeset no se hace efectivo hasta que se corrijan los errores y se vuelva a hacer el check-in.

A priori esta opción parece ideal para los check-in de merges de una rama de desarrollo a Main. Los problemas que me he encontrado con esta opción son varios:

  • Si haces múltiples merge y check-ins de un mismo desarrollo y el primero falla no se mergea, pero si el segundo compila correctamente sí.
  • Problemas con las notificaciones de errores y código pendiente al fallar el check-in
  • En merges con más de un check-in se encolan muchas builds (y por defecto solo tenemos un build agent disponible…)

Seguro que esto tiene solución, pero no he sabido encontrarla. Y de todas formas la opción de integración contínua que comentaba antes nos ha funcionado perfectamente para validar que la rama compila sin errores. Como digo todo esto ha sido fruto de la investigación del equipo y prueba-error.

Conclusiones

Supongo que la mayor conclusión es que con MSDyn365FO hay que usar Azure DevOps. Es obligatorio, no hay otra opción. Los que no lo estéis haciendo, si es que alguien no lo hace, hacedlo. Ya. Revisad vuestra forma de trabajo y olvidemos de una vez por todas AX, MSDyn365FO a nivel técnico es otro producto.

La verdad es que, a los desarrolladores, MSDyn365FO nos ha acercado un poco más a lo que es un proyecto de desarrollo de software clásico como puede ser uno de .NET o Java. Pero no del todo. Un proyecto de ERP tiene muchas peculiaridades, y creo que no partir de zero en el desarrollo del producto, tener una base que nos «obliga» un poco a seguir una línea, nos limita en algunos aspectos y en el uso de ciertas técnicas y metodologías.

Y hasta aquí estos dos posts sobre Azure DevOps. Espero que le sean de ayuda a alguien. Y si alguien con más experiencia, o mejores ideas, quiere recomendar algo, ¡los comentarios están abiertos!

ariste.info