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.
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.
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.
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.
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 os podéis descargar el antiguo PDF del Microsoft Dynamics AX 2009 development best practices white paper aquí) 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 owrite 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.
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:
class AASAppCheckerDemo
{
public void complexMethod()
{
int a, b, c, d, e, f, g, h, i;
if (a)
{
if (d)
{
if (d)
{
}
else if (e)
{
}
else if(f)
{
}
}
else if (e)
{
if (d)
{
}
else if (e)
{
}
else if(f)
{
}
}
else if(f)
{
if (d)
{
}
else if (e)
{
}
else if(f)
{
}
}
}
else if (b)
{
if (d)
{
if (d)
{
}
else if (e)
{
}
else if(f)
{
}
}
else if (e)
{
if (d)
{
}
else if (e)
{
}
else if(f)
{
}
}
else if(f)
{
if (d)
{
}
else if (e)
{
}
else if(f)
{
}
}
}
else if(c)
{
if (d)
{
if (d)
{
}
else if (e)
{
}
else if(f)
{
}
}
else if (e)
{
if (d)
{
}
else if (e)
{
}
else if(f)
{
}
}
else if(f)
{
if (d)
{
}
else if (e)
{
}
else if(f)
{
}
}
}
}
}
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.
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.
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.
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.
He estado en la preview privada desde hace casi un año con un cliente. 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.
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.
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:
SELECT TOP 1 T1.PAYMTERMID,T1.LINEDISC,T1.TAXWITHHOLDGROUP_TH,T1.PARTYCOUNTRY,T1.ACCOUNTNUM,T1.ACCOUNTSTATEMENT,
T1.AFFILIATED_RU,T1.AGENCYLOCATIONCODE,T1.BANKACCOUNT,T1.BANKCENTRALBANKPURPOSECODE,T1.BANKCENTRALBANKPURPOSETEXT
,T1.BANKCUSTPAYMIDTABLE,T1.BIRTHCOUNTYCODE_IT,T1.BIRTHPLACE_IT,T1.BLOCKED,T1.CASHDISC,T1.CASHDISCBASEDAYS,
T1.CCMNUM_BR,T1.CLEARINGPERIOD,T1.CNAE_BR,T1.CNPJCPFNUM_BR,T1.COMMERCIALREGISTER,T1.COMMERCIALREGISTERINSETNUMBER,
T1.COMMERCIALREGISTERSECTION,T1.COMMISSIONGROUP,T1.COMPANYCHAINID,T1.COMPANYIDSIRET,T1.COMPANYNAFCODE,T1.COMPANYTYPE_MX,
T1.CONSDAY_JP,T1.CONTACTPERSONID,T1.CREDITCARDADDRESSVERIFICATION,T1.CREDITCARDADDRESSVERIFICATIONLEVEL,T1.CREDITCARDADDRESSVERIFICATIONVOID,
T1.CREDITCARDCVC,T1.CREDITMAX,T1.CREDITRATING,T1.CURP_MX,T1.CURRENCY,T1.CUSTCLASSIFICATIONID,T1.CUSTEXCLUDECOLLECTIONFEE,
T1.CUSTEXCLUDEINTERESTCHARGES,T1.CUSTFINALUSER_BR,T1.CUSTGROUP,T1.CUSTITEMGROUPID,T1.CUSTTRADINGPARTNERCODE,T1.CUSTWHTCONTRIBUTIONTYPE_BR,
T1.DEFAULTDIMENSION,T1.DEFAULTDIRECTDEBITMANDATE,T1.DEFAULTINVENTSTATUSID,T1.DESTINATIONCODEID,T1.DLVMODE,T1.DLVREASON,T1.DLVTERM,
T1.EINVOICE,T1.EINVOICEATTACHMENT,T1.EINVOICEEANNUM,T1.ENDDISC,T1.ENTRYCERTIFICATEREQUIRED_W,T1.EXPORTSALES_PL,T1.EXPRESSBILLOFLADING,
T1.FACTORINGACCOUNT,T1.FEDERALCOMMENTS,T1.FEDNONFEDINDICATOR,T1.FINECODE_BR,T1.FISCALCODE,T1.FISCALDOCTYPE_PL,T1.FORECASTDMPINCLUDE,
T1.FOREIGNRESIDENT_RU,T1.FREIGHTZONE,T1.GENERATEINCOMINGFISCALDOCUMENT_BR,T1.GIROTYPE,T1.GIROTYPEACCOUNTSTATEMENT,T1.GIROTYPECOLLECTIONLETTER,
T1.GIROTYPEFREETEXTINVOICE,T1.GIROTYPEINTERESTNOTE,T1.GIROTYPEPROJINVOICE,T1.ICMSCONTRIBUTOR_BR,T1.IDENTIFICATIONNUMBER,T1.IENUM_BR,
T1.INCLTAX,T1.INSSCEI_BR,T1.INTBANK_LV,T1.INTERCOMPANYALLOWINDIRECTCREATION,T1.INTERCOMPANYAUTOCREATEORDERS,T1.INTERCOMPANYDIRECTDELIVERY,
T1.INTERESTCODE_BR,T1.INVENTLOCATION,T1.INVENTPROFILEID_RU,T1.INVENTPROFILETYPE_RU,T1.INVENTSITEID,T1.INVOICEACCOUNT,T1.INVOICEADDRESS,
T1.INVOICEPOSTINGTYPE_RU,T1.IRS1099CINDICATOR,T1.ISRESIDENT_LV,T1.ISSUEOWNENTRYCERTIFICATE_W,T1.ISSUERCOUNTRY_HU,T1.LINEOFBUSINESSID,
T1.LVPAYMTRANSCODES,T1.MAINCONTACTWORKER,T1.MANDATORYCREDITLIMIT,T1.MANDATORYVATDATE_PL,T1.MARKUPGROUP,T1.MCRMERGEDPARENT,
T1.MCRMERGEDROOT,T1.MULTILINEDISC,T1.NIT_BR,T1.NUMBERSEQUENCEGROUP,T1.ONETIMECUSTOMER,T1.ORDERENTRYDEADLINEGROUPID,
T1.ORGID,T1.OURACCOUNTNUM,T1.PACKAGEDEPOSITEXCEMPT_PL,T1.PACKMATERIALFEELICENSENUM,T1.PARTY,T1.PARTYSTATE,T1.PASSPORTNO_HU,T1.PAYMDAYID,
T1.PAYMENTREFERENCE_EE,T1.PAYMIDTYPE,T1.PAYMMODE,T1.PAYMSCHED,T1.PAYMSPEC,T1.PDSCUSTREBATEGROUPID,T1.PDSFREIGHTACCRUED,
T1.PDSREBATETMAGROUP,T1.PRICEGROUP,T1.RESIDENCEFOREIGNCOUNTRYREGIONID_IT,T1.RFC_MX,T1.SALESCALENDARID,T1.SALESDISTRICTID,
T1.SALESGROUP,T1.SALESPOOLID,T1.SEGMENTID,T1.SERVICECODEONDLVADDRESS_BR,T1.STATEINSCRIPTION_MX,T1.STATISTICSGROUP,T1.SUBSEGMENTID,
T1.SUFRAMA_BR,T1.SUFRAMANUMBER_BR,T1.SUFRAMAPISCOFINS_BR,T1.SUPPITEMGROUPID,T1.TAXGROUP,T1.TAXLICENSENUM,T1.TAXPERIODPAYMENTCODE_PL,
T1.TAXWITHHOLDCALCULATE_IN,T1.TAXWITHHOLDCALCULATE_TH,T1.UNITEDVATINVOICE_LT,T1.USECASHDISC,T1.USEPURCHREQUEST,T1.VATNUM,
T1.VENDACCOUNT,T1.WEBSALESORDERDISPLAY,T1.AUTHORITYOFFICE_IT,T1.EINVOICEREGISTER_IT,T1.FOREIGNERID_BR,T1.PRESENCETYPE_BR,
T1.TAXGSTRELIEFGROUPHEADING_MY,T1.FOREIGNTAXREGISTRATION_MX,T1.CUSTWRITEOFFREFRECID,T1.ISEXTERNALLYMAINTAINED,T1.SATPAYMMETHOD_MX,
T1.SATPURPOSE_MX,T1.CFDIENABLED_MX,T1.FOREIGNTRADE_MX,T1.WORKFLOWSTATE,T1.USEORIGINALDOCUMENTASFACTURE_RU,T1.COLLECTIONLETTERCODE,
T1.BLOCKFLOORLIMITUSEINCHANNEL,T1.AXZMODEL182LEGALNATURE,T1.AXZCRMGUID,T1.MODIFIEDDATETIME,T1.MODIFIEDBY,T1.CREATEDDATETIME,T1.RECVERSION,
T1.PARTITION,T1.RECID,T1.MEMO
FROM CUSTTABLE T1
WHERE (((PARTITION=5637144576) AND (DATAAREAID=N'usmf')) AND (ACCOUNTNUM=N'0001'))
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 operacionesset-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:
UPDATE CUSTTABLE
SET MEMO = 'Special customer'
WHERE (((PARTITION=5637144576) AND (DATAAREAID=N'usmf')) AND (CUSTGROUP=N'10'))
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_FROM
UPDATE_RECORDSET
INSERT_RECORDSET
ARRAY_INSERT
Use … to override
Non-SQL tables
Yes
Yes
Yes
Yes
Not applicable
Delete actions
Yes
No
No
No
skipDeleteActions
Database log enabled
Yes
Yes
Yes
No
skipDatabaseLog
Overridden method
Yes
Yes
Yes
Yes
skipDataMethods
Alerts set up for table
Yes
Yes
Yes
No
skipEvents
ValidTimeStateFieldType property not equal to None on a table
Yes
Yes
Yes
Yes
Not 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?
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.
¡ATENCIÓN! ESTE POST ESTÁ TOTALMENTE DESFASADO Y VISUAL STUDIO 2019 ES EL EDITOR QUE VIENE POR DEFECTO EN DYNAMICS 365 FINANCE AND OPERATIONS DESDE LA VERSIÓN 10.0.21 Y EL VHD DESDE EL RELEASE DE OCTUBRE DE 2021.
¿Harto de desarrollar en Visual Studio 2015? ¿Te sientes abandonado en el pasado? No te preocupes, ¡es posible usar Visual Studio 2017/2019 para desarrollo de Microsoft Dynamics 365 for Finance & Operations!
¿Cuáles son sus ventajas?
¡Absolutamente ninguna! Visual Studio se quedará sin responder sea cual sea la versión que uses porque la extensión de las developer tools está un poco pasada de moda y es la que en realidad causa los cuelgues.
Sí se añade la posibilidad de usar Live Share, que para sesiones de pantalla compartida es mil veces mejor que Teams. Ey, y que estaremos usando la última versión de VS!
¿Cuesta mucho?
No, tiene cero misterio. Lo primero que hay que hacer es descargar Visual Studio 2019 Professional (o Enterprise pero para D365 no sirve de mucho más) e instalarlo:
Seleccionamos la opción de desarrollo de escritorio .NET y le damos al instalar. Cuando termine abrimos VS y nos conectamos con nuestra cuenta.
El siguiente paso es instalar la extensión de las developer tools de Dynamics. Vamos a la unidad K y en la carpeta DeployablePackages encontraremos unos ZIP que si abrimos veremos que tienen una carpeta DevToolsService/Scripts con la extensión de VS:
Otra alternativa es, por ejemplo, descargar el paquete de un Platform Update que también tiene las dev tools y puede que más actualizadas.
Instalamos la extensión y ya nos aparece la opción de VS2019:
Una vez instalado abrimos VS como administrador y…
Y también…
¡Que no cunda el pánico! La extensión está hecha para VS2015 y usarla en una versión más nueva da algunos avisos, pero solo eso, tenemos las herramientas listas y las podemos usar:
Como decía al principio la extensión de las dev tools es lo que a veces ralentiza o bloquea VS, y Visual Studio 2019 así lo notifica:
Pese a los avisos se puede trabajar con Visual Studio 2019 sin ningún problema. Yo llevo haciéndolo una semana y no he encontrado ningún problema que me haga volver a la 2015.
Actualización: parece que el editor de reports no funciona y si abrimos un diseño se muestra su XML en vez del diseñador. Gracias a David Murray por avisarme!
Preview de las nuevas dev tools
En octubre de 2019 se va a publicar la versión de preview de las herramientas de desarrollo tal y como se vio en el MBAS de Atlanta. Esperemos ver qué novedades trae tanto en versión de VS como mejora del rendimiento de la extensión.
Si alguna vez necesitas consumir un servicio SOAP desde Dynamics 365 for Finance and Operations, lo primero que tienes que hacer es pedir a los responsables de ese servicio que creen una versión REST. Si eso no es posible, este post es para ti.
Voy a usar este web service que encontré por ahí en http://www.dneonline.com/calculator.asmx para el ejemplo, es una calculadora con las 4 operaciones básicas de suma, resta, división y multiplicación.
Consumir un servicio SOAP en .NET
Empecemos por lo más básico. ¿Cómo consumimos un servicio SOAP en Visual Studio? Muy fácil. Sólo hay que añadir una referencia de servicio en tu proyecto:
Apuntar al servicio web de tu elección:
Esto va a crear la referencia en Visual Studio:
Una vez hecho esto podemos crear una instancia del cliente del servicio y llamar a sus métodos:
3 + 6 = 9, parece que funciona.
Consumir un servicio SOAP en Dynamics 365 for Finance and Operations
Para consumir un servicio web en FnO crea un nuevo proyecto, haz click derecho en referencias y añade la referencia de servicio:
Emmm… no, no se puede, no hay referencias de servicio.
Consumir un servicio SOAP en Dynamics 365 for Finance and Operations (espero…)
El problema es que en las máquinas de desarrollo de 365 no podemos añadir las referencias de servicio en Visual Studio.
¿Qué dice la documentación sobre esto? Bueno, igual que en AX2012 necesitamos crear una clase en .NET que consumira el servicio web y a través de la que accederemos a él desde 365. Muy bien!
Ya lo tenemos. Una referencia a nuestra librería y una runnable class que hará el trabajo:
A ejecutarlo:
¿Qué?
An exception of type ‘System.InvalidOperationException’ occurred in System.ServiceModel.dll but was not handled in user code
Additional information: Could not find default endpoint element that references contract ‘AASSOAPCalculatorService.CalculatorSoap’ in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element.
¿Contrato? ¿Qué contrato? No sé nada de un contrato. Nadie me ha dicho nada de un contrato! ¿Qué dice la Wikipedia del SOAP?
Soap is the term for a salt of a fatty acid or for a variety of cleansing and lubricating products produced from such a substance.
Creo que no es ese el SOAP que buscaba… (en inglés era más gracioso 🙁 )
SOAP provides the Messaging Protocol layer of a web services protocol stack for web services. It is an XML-based protocol consisting of three parts:
an envelope, which defines the message structure and how to process it
a set of encoding rules for expressing instances of application-defined datatypes
a convention for representing procedure calls and responses
El sobre es el contrato. Un contrato de datos es un acuerdo entre un servicio y un cliente que descibe qué datos se van a intercambiar. Ese contrato.
Consumir un servicio SOAP en Dynamics 365 for Finance and Operations (esta es la buena)
Si vemos el proyecto que hemos creado para consumir el servicio hay un archivo que se llama app.config:
En este archivo tenemos el endpoint que usa la DLL. Este valor es fijo, y en caso de que tengamos un endpoint de pruebas y uno de producción tendríamos que crear una librería para cada uno de ellos. También vemos el contrato que usa, AASSOAPCalculatorService.CalculatorSoap. Como #MSDyn365FO es un ERP basado en web podríamos solucionar esto añadiendo el nodo de system.serviceModel en el web.config del servidor, no? (app.config para apps de escritorui, web.config para apps web). Sí, pero sólo podríamos hacerlo en desarrollo porque en producción no tenemos acceso al servidor, y en cuanto los entornos Tier2+ se migren a self-service será imposible ahí también.
¿Qué hacemos entonces? Fácil, ChannelFactory<T> al rescate.! ChannelFactory<T> nos permite crear una instancia de la factory para nuestro contrato del servicio y después crear un canal entre el cliente y el servicio. El cliente sería nuestra clase en D365and y el servicio el endpoint (obviamente).
Hacemos lo siguiente:
El objeto BasicHttpBinding puede ser un BasicHttpsBinding si el servicio web corre sobre HTTPS. El objeto EndpointAddress es la URL del servicio. Instanciamos un contrato del servicio de nuestra clase con el binding y el endpoint y creamos el canal. Ahora podemos llamar al servicio web y sus métodos y…
Funciona! Y lo que es mejor, si hay distintos endpoints con este método podemos parametrizarlos y solo necesitamos una DLL para todos los endpoints!
Pero de verdad, evitad los servicios SOAP, tiremos todos de REST.
He sido programador de X++ casi 10 años, eso es el 100% de mi vida profesional, sin contar becas. Durante estos 10 años he visto el producto evolucionar, y, en mi opinión, los últimos tres años con #MSDyn365FO han sido los mejores de lejos como he dicho otras veces.
El cambio de un IDE tipo bloc de notas como era MorphX a Visual Studio, poder usar Azure DevOps y las tasks de subida a LCS y despliegue me hacen sentir más como un desarrollador. Y esto es solo el principio del viaje, ahora estamos empezando con el testing automatizado, con RSAT y el ATL, ahora sí que sí vamos a hacer testeo!
Y cómo podemos mejorar como desarrolladores de X++?
Lleva tiempo
Como aprender cualquier cosa. No sabes nada el primer día, aprendes sobre la marcha a base de trabajar y hacer cosas, y con el tiempo te das cuenta de que el ERP es enorme y sólo conoces una parte pequeña. Pasará más tiempo y seguirás conociendo sólo una parte de Operations.
Ama tu trabajo
Esto puede ser jodido a veces… lo que hagas hazlo con pasión. Busca una empresa que te haga crecer, intenta divertirte en el trabajo. Será difícil, sobretodo en pleno arranque, pero incluso entonces habrá momento para las risas. El resto es más fácil si te lo tomas así.
Conocimiento funcional
Obviamente los desarrolladores necesitamos saber como funcionan los procesos desde el punto de vista funcional. En caso de duda pregunta a un compañero funcional, no pierdas el tiempo buscando por el código intentando entender la funcionalidad. Después de la explicación funcional ver el código estará más claro, seguro.
Siempre he pensado que programar en X++ es bastante fácil, lo difícil es conocer todos los procesos.
Aprende otros lenguajes
Sal de X++. Trabajar (o probar) otro lenguaje de programación puede ayudar a quitarnos algunos vicios de trabajar con AX.
Los programadores suelen conocer más de un lenguaje de programación, de otros trabajo o proyectos personales. C# es una buena opción porque podemos usar librerías de .NET en X++ o podemos crear las nuestras. Aprende la sintaxis (fácil), prueba los foreach (me encantaría tenerlo en X++), LINQ, etc.
Hace un tiempo pensaba que, en algún momento del futuro, susituirían X++ por .NET/C#, así que aprenderlo era una buena idea. Viendo las últimas novedades de X++ como el SysDA o ATL tengo algunas dudas a medio plazo. Además la capa de acceso a datos de X++ es maravillosa.
Explora Azure
Incluyendo DevOps. Por suerte no usarlo es imposible. Pero no lo uses solo como herramienta de control de código, es muuuuuucho más que eso.
Explora Azure, hay muchos recursos y la solucón a un problema puede estar ahí. Azure functions, Logic apps, Azure SQL, Service bus (combinado con Business events por ejemplo). AX ya no está solo, 365 viene con un montón de amigos en la nube.
Power Platform
Después del último MBAS, ha quedado clarísimo que Microsoft está invirtiendo mucho en la PowerPlatform. Flow, PowerApps, AI Builder… Todos estos productos se pueden integrar con MSDyn365FO.
Podemos sustituir un espacio móvil con una PowerApp que seguro que será más flexible, Flow para mandar correos que se lancen por un Business Event o una operación CRUD.
Aprende algo sobre CRM y CDS, seguro que en algun proyecto va a tocar integrarlo con FnO.
Comparte y enseña
Para mi enseñar es terriblemente difícil, soy un profesor horroroso, las cosas en mi cabeza están muy claras pero se pierden por el camino a la boca. Me cuesta a veces materializar esas ideas en palabras. Escribir me ayuda a ordenar las ideas, porque puedo escribir, borrar, escribir y volver a borrar una y otra vez 🙂
Comparte tu conocimiento, da formación interna con tus compañeros, hazte speaker. Nunca pensé en nada de esto hasta que me dijeron si quería dar una charla en el Dynamics 365 Saturday lo primero que pensé fue «¿Yo? ¿Qué puedo contar que le interese a alguien?». Al final lo único que necesitas es escoger o inventarte un tema del que sepas algo (o nada) y expandir los conocimientos, o tener ideas tontas y llevarlas a cabo!
Y nada más. Esto son sólo algunas ideas, hay muchas cosas que se pueden hacer para mejorar, pero creo que lo más importante es tener paciencia. Y el tiempo. Tiempo y paciencia.