Code Generation as a Service

Despues de un tiempo, vuelvo a escribir sobre mi proyecto preferido AjGenesis, un generador de código basado en un modelo de libre definición. En este artículo veremos

- Una nueva forma interactiva de utilizarlo, basado en web

- Ejemplos de su uso para generar C#, VB.Net, Java, PHP

- Todo ya publicado dentro de la versión 0.5 en CodePlex (también está publicado en SourceForge, hay un download de esta versión)

Usando AjGenesis

Cuando comencé hace unos años a trabajar en él, la única forma de ejecutarlo directamente y hacerlo trabajar era mediante línea de comando. En algún momento, adopté al NAnt, y en particular, al NAntGui, como interface de lanzamiento del sistema. Como es un conjunto de .DLLs también puede invocarse desde un programa propio. El bueno de Carlos Marcelo Santos ha publicado posts sobre cómo usar AjGenesis desde el NAnt

Cómo generar código con AjGenesis sirviéndonos de NAnt
Cómo generar código con AjGenesis sirviéndonos de NAnt – Parte II

El año pasado, el bueno de Jonathan Cisneros escribió una interface Windows para el proyecto, muy interesante, que se llama AjGenesis Studio:

AjGenesis Studio: una IDE para AjGenesis
AjGenesis Studio en CodePlex

Como ejercicio de fin de año 2007, escribí un nuevo proyecto dentro de la solución original, y la publiqué en Enero de este año, en la versión 0.5 en CodePlex.

Es un nuevo proyecto web, al que he llamado, siguiendo la inspiración del nombre del proyecto de Jonathan, AjGenesis Web Studio

La idea es que, mediante este proyecto, ahora se puede acceder a la funcionalidad del núcleo de AjGenesis, desde una interfaz web, basada en ASP.NET. Con este “approach”, podemos montar un servidor en nuestra intranet, o en la misma Internet, y crear, editar, subir y bajar modelos definibles por el usuario, así como generar código y luego bajarlos para usar localmente. Lo que podemos llamar, simpáticamente, Code Generation as a Service.

Veamos cómo está armado y cómo se puede usar este proyecto.

El proyecto

Es un proyecto Web, que se llama AjGenesis.WebStudio.

Para lanzarlo, cargar la solución AjGenesis.sln del directorio src (de la versión 0.5 de CodePlex) en el Visual Studio 2005, y hacer Build de la solución completa.

(Si resulta que encuentra errores en el proyecto AjGenesis.WebStudio, probablemente falte una referencia a una .DLL que maneja la compresión de archivos. Agregar como referencia al proyecto el archivo src\Libraries\Ionic.Utils.Zip.dll. Esta librería permite generar archivos .zip, y expandirlos).

En la solución web usé Master Pages, y un Theme asumido. No maneja base de datos, solamente archivos y directorios. Vamos a ver más adelante que tiene incluido un mini sistema de páginas wiki, para poder armar, por ejemplo, páginas de ayuda del sistema definibles por el usuario.

La apariencia gráfica es sencilla, pero siempre pueden cambiar el Theme y la MasterPage para mejorarlo cuando quieran.

Vamos a ver que el sistema permite generar código en el momento, así como bajarse el resultado en un archivo .zip. Siempre pueden usar el sistema AjGenesis clásico, como explico en otros post, pero ahora se puede usar desde cualquier browser, sin necesidad de tener instalado el sistema, .NET, y estando en otro sistema operativo.

Luego de haber armado la solución, botón derecha sobre el archivo \Default.aspx del proyecto AjGenesis.WebStudio, y ahí, elegir View in browser…:

Esto es preferible a lanzar el proyecto en modo de depuración: si Ud. lo lanza de esta forma, ejecutará la parte de generación de código de modo bastante más lento.

Directorio de Trabajo

El sistema utiliza un directorio de trabajo, de estructura similar a los que probaron los ejemplos de AjGenesisExamples3.zip (a bajar desde los ejemplos en CodePlex). El directorio asumido de trabajo es AjGenesis.WebStudioWorkingDirectory:

Algunos proyectos:

Projects: Donde hay un directorio por cada proyecto.
Templates: Conteniendo los archivos de templates a expandir en la generación de código
Build: Acá queda el código generado de cada proyecto y tecnología.

El directorio de trabajo se puede cambiar yendo a la opción de la izquierda Working Directory y ahí eligiendo la opción Change Working Directory:

Generando el Hello World

Como sostengo en mis charlas de generación de código, la prueba ácida de todo sistema que se precie de ayudarnos en la generación de software debería poder modelar el sistema más simple y universal de todos, el conocido Hola Mundo. Hay una explicación de cómo llegar a generar ese sistema de distintas formas usando AjGenesis en

Generando Código: Hello World con AjGenesis

En AjGenesis Web Studio, elegimos la opción del menú de la izquierda Generate. Aparece la página

Hay dos listas desplegables. Veremos de qué forma se llenan más abajo. Si elegimos la primera lista, aparecen opciones como:

Elegimos el HelloWorld. Al ir a la lista de tecnologías, para este proyecto éstas son:

En este proyecto sólo estan Java y Net20. Veremos otros proyectos con otras tecnologías. Elegimos, por ejemplo, Java, y al presionar el botón de Generate, aparece, luego de trabajar unos momentos, un resultado de texto en la página como:

Generating HelloWorld…
Creating Directory C:\ajlopez\ProyectosNet2\AjGenesis
-0.5\src\AjGenesis.WebStudioWorkDirectory/Build/HelloWorld Generating Solution HelloWorld Creating Directories

¿Cómo funcionó esto?

En primer lugar, el sistema maneja un directorio de trabajo, al principio es AjGenesis.WebStudioWorkDirectory, directorio hermano del proyecto web. Ahí, un directorio especial es Projects, que contiene subdirectorios. Cada uno de esos subdirectorios se toma como un proyecto.

El directorio AjGenesis.WebStudioWorkDirectory\Projects\HelloWorld tiene un archivo Project.xml que describe el modelo libre que vayamos a usar, en este caso:

<Project> <Name>HelloWorld</Name> <Description>HelloWorld Example</Description> <GenerateTask>GenerateHelloWorld</GenerateTask> <Message>Hello World</Message> </Project>

Dentro del directorio del proyecto, hay un directorio Technologies. Cada archivo XML que se encuentre ahí se toma como un modelo que describe la tecnología a usar. Nuevamente, como explico en los artículos de AjGenesis ya publicados, este archivo es de libre diseño. El concepto de Project y Technology es circunstancial, pero AjGenesis Web Studio los toma como directorios distinguidos, especiales, que busca explícitamente.

El archivo de tecnología Java.xml tiene

 

<Technology> <Name>Java</Name> </Technology>

 

y el archivo Net20.xml contiene

<Technology> <Name>Net20</Name> </Technology>

Nada especial. Pero ese valor, Name dentro de Technology, determinará qué pasos ejecutar en la generación.

Al presionar el botón de generar, el sistema carga el modelo de proyecto y el de tecnología elegidos en la memoria. Y luego ejecuta el archivo que esté en el directorio Tasks, con nombre GenerateCode.ajg, a menos que haya en el archivo de proyecto un tag GenerateTask, como arriba. En este caso ejecuta Tasks\GenerateHelloWorld.ajg. Veamos su contenido:

 

PrintLine Generating HelloWorld… if not Project.BuildDir then Project.BuildDir = ${WorkingDir}Build/${Project.Name} end if if not Project.Title then Project.Title = Project.Name end if if not Project.Version then Project.Version = 1.0.* end if if not Project.Language then Project.Language = en end if if not Project.SystemName then Project.SystemName = Project.Name end if PrintLine Creating Directory ${Project.BuildDir} FileManager.CreateDirectory(${Project.BuildDir}) Include Tasks\BuildHelloWorld${Technology.Name}.ajg

Este archivo está escrito en el lenguaje de scripting que usa AjGenesis, afectuosamente conocido como AjBasic. Notemos que la variable Project es la representación en memoria del tag <Project> que cargamos del archivo Project.xml, descripto más arriba. En memoria, es como si fuera un objeto, con propiedades. Vemos que no es un objeto estático: podemos agregarle propiedades en ejecución, como Project.Version arriba. Si en un if preguntamos por el valor de Project.Version y éste no existe, no da error, simplemente da “Nothing” y en la evaluación del if ese valor se toma como false (similar a lo que pasa en otros lenguajes, como PHP).

 

Finalmente, la última línea incluye un archivo adicional, dinámicamente. Las constantes string tienen expansión de expresiones: lo que se encuentra entre ${ y } se toma como una expresión a evaluar. En base a el valor de Technology.Name (obtenido de haber cargado en memoria el archivo XML de tecnología mostrado anteriormente), ejecuta BuildHelloWorldJava.ajg o BuildHelloWorldNet20.ajg. El primero contiene los pasos a ejecutar para generar un archivo .java:

 

 

<# PrintLine Generating Solution ${Project.Name} if not Project.Title then Project.Title = Project.Name end if if not Project.Version then Project.Version = 1.0.* end if PrintLine Creating Directories FileManager.CreateDirectory(${Project.BuildDir}) FileManager.CreateDirectory(${Project.BuildDir}\${Technology.Name}) TransformerManager.Transform(Templates\HelloWorld\ClassJava.tpl, ${Project.BuildDir}\${Technology.Name}\HelloWorld.java, Environment) #>
Acá ejecuta código de utilitarios internos del AjGenesis, que permiten crear directorios, y expandir un archivo de templates. El ClassJava.tpl contiene:

 


// // Automatically generated by AjGenesis // http://www.ajlopez.com/ajgenesis // public class HelloWorld { public static void main(String[] args) { System.out.println(${Project.Message}); } }

y termina produciendo en el directorio Build\HelloWorld\Java:

 


// // Automatically generated by AjGenesis // http://www.ajlopez.com/ajgenesis // public class HelloWorld { public static void main(String[] args) { System.out.println(Hello World); } }

 

Si hubiéramos elegido tecnología Net20, se habrían generado más archivos:

Este resultado es más completo. Contiene una solución VS 2005, dos directorios de proyectos, uno en VB.Net y otro en Java, con sus respectivos archivos de código, proyecto y más:

Para ver el contenido del código generado, los archivos y directorios, podemos elegir la opción del menú izquierdo Builds:

Ahí encontramos un directorio por cada proyecto. Vean que podemos hacer un “download” de todo un directorio, en formato .zip. Podemos crear un archivo, crear un directorio, bajar el directorio actual completo, subir y expandir un archivo .zip, veremos más adelante las opciones de ayuda. Para cada archivo, podemos verlo, editarlo, y borrarlo, como en el caso de readme.txt.

Ingresemos al comando View de directorio HelloWorld:

Y podemos seguir explorando directorios, y subiendo, viendo, editando archivos. Vean que podemos mover un directorio, o hacer una copia, para experimentar con variantes.

Generando soluciones C# y VB.NET

Habiendo visitado el ejemplo sencillo, exploremos la generación de código más complejo. Lo que vamos a ver ya lo generaban ejemplos de AjGenesis, leer para más información:

Generando aplicaciones con AjGenesis

Vamos nuevamente a la opción Generate, y esta vez elegimos un proyecto como AjSecondExample. En la lista de tecnologías ahora encontramos más variedad:

Pueden probar ahora generar CSharp2. Esto genera una solución completa dentro de Builds\AjSecondExample\CSharp2. Ahí en un directorio Sql encontrarán el script de generación de la base de datos MS SQL Server. En el directorio Src está la solución, compuesta de varios proyectos:

Los proyectos siguen una arquitectura de capas, que pueden modificar en los templates. Usa una capa de acceso a datos basado en una .dll de mi proyecto AjFramework pero pueden cambiarla por acceso a Enterprise Library o a su propia capa de datos.

La aplicación (luego de crear la base de datos, y modificar algún string de conexión en en web.config del proyecto web) es perfectamente compilable y ejecutable:

La pagina de administracion muestra:

Volviendo a la página de generación,  si ejecutamos para la tecnología VbNet2, conseguimos un directorio similar con código en capas:

Gran parte de lo dependiente de la tecnología está especificado en los archivos dentro de Projects\AjSecondExample\Technologies. Vemos en VbNet2.xml:

<Technology> <Programming> <Dialect>VbNet2</Dialect> </Programming> <Database> <Dialect>MsSql</Dialect> <Name>AjSecondExample</Name> <Username>sa</Username> <Prefix>ajse_</Prefix> <Host>(local)</Host> </Database> </Technology>

y en CSharp2.xml:

<Technology> <Programming> <Dialect>CSharp2</Dialect> </Programming> <Database> <Dialect>MsSql</Dialect> <Name>AjSecondExample</Name> <Username>sa</Username> <Prefix>ajse_</Prefix> <Host>(local)</Host> </Database> </Technology>

Datos que son usados en la generación de código.

Un detalle: en el archivo Tasks\BuildDirectory.ajg encontrarán una nueva variable, inyectada desde el proyecto web:

if not Project.BuildDir then Project.BuildDir = ${WorkingDir}Build/${Project.Name}/${Technology.Programming.Dialect} end if

Es la variable WorkingDir que apunta al directorio de trabajo que hayamos elegido en el sistema web.

Generando sistemas Java y JSP

Para varios proyectos (AjFirstExample, AjSecondExample, AjTest….) podemos generar código para Java con JavaServer Pages. Internamente, genera un archivo build.xml para ser modificado y usado para compilar el código generado con el utilitario Ant (también lo pueden usar desde una entorno de desarrollo como Eclipse o NetBeans).

Si utilizan ese utilitario, el target build arma un archivo de deploy .war. Pueden examinar y modificar el archivo build.xml para que haga deploy directamente en un Tomcat en ejecución.

El código generado consta de archivos .jsp (páginas web en Java) y código a compilar con una capa de servicio, y otra de acceso a datos, con intercambio de simples JavaBeans entre las capas. El script de base de datos, en estos ejemplos, es para MySql.

Si hacen el build y el deploy de la aplicación, además de instalar la base de datos en un servidor MySql, pueden verlo andando en un Tomcat:

Generando sistemas con ideas de Domain-Driven Design, Hibernate, NHibernate

También para varios proyectos (AjFirstExample, AjSecondExample, AjTest…) tenemos tecnologías CSharp2DDDNh, JavaDDDHb, VbNet2DDDNh, y hasta VbNet2Nh. Los DDD implementan algunos conceptos de Domain-Driven Desing, como capa de Application, Domain, e Infraestructura, pero pueden mejorarse. Los Nh generan código de entidades con mapeo de NHibernate. Los Hb generan código de entidades con mapeo de Hibernate.

Un ejemplo de mapeo de NHibernate, generado:

<?xml version=”1.0″ encoding=”utf-8″ ?> <hibernate-mapping xmlns=”urn:nhibernate-mapping-2.0″> <class name=”AjTest.Domain.Employee, AjTest.Domain” table=”employees”> <id name=”Id” column=”Id” type=”Int32″ unsaved-value=”0″> <generator class=”native”/> </id> <property column=”EmployeeCode” type=”String” name=”EmployeeCode” length=”255″/> <property column=”LastName” type=”String” name=”LastName” length=”255″/> <property column=”FirstName” type=”String” name=”FirstName” length=”255″/> <many-to-one name=”Department” column=”IdDepartment” class=”AjTest.Domain.Department, AjTest.Domain” /> <bag name=”Tasks” lazy=”true” inverse=”true” cascade=”all”> <key column=”IdEmployee”/> <one-to-many class=”AjTest.Domain.Task, AjTest.Domain”/> </bag> <bag name=”EmployeeSkills” lazy=”true” inverse=”true” cascade=”all”> <key column=”IdEmployee”/> <one-to-many class=”AjTest.Domain.EmployeeSkill, AjTest.Domain”/> </bag> </class> </hibernate-mapping>

Se compilan y arman igual que los anteriores de .NET y Java. Pueden ver mejores generadores para NHibernate en los artículos

Generando código para NHibernate (Parte 1)
Generando código para NHibernate (Parte 2)
Generando código para NHibernate (Parte 3)

Generando sistemas PHP

Si elegimos tecnología PHP, se generan páginas con includes PHP, y acceso a MySql. El código generado hay que copiarlo a un directorio de un sitio web, ya sea IIS con soporte de PHP o un Apache. Ejemplo de AjTest generado funcionando en IIS con PHP4:

Otros directorios

Dentro del directorio de trabajo, tenemos otros directorios:

SourceCode: donde hay código fuente de utilitarios que escribí y se copia a los proyectos generados.

Libraries: con dlls adicionales que se necesitan, como el AjFramework, y el conector JDBC de MySql.

El más “largo” subdirectorio es el de Templates:

Ya en anteriores posts vimos en más detalle cómo se escribe un archivo de template. Baste como ejemplo para este post, baste el que genera un entidad en CSharp 2:

 

<# rem Entity Generator rem for C Sharp include Templates/CSharp2/CSharpFunctions.tpl message Processing Entity ${Entity.Name} include Templates/CSharp2/Prologue.tpl #> /* * Project ${Project.Name} * ${Project.Description} * Entity ${Entity.Name} * ${Entity.Description} * */ using System; namespace ${Project.SystemName}.Entities { public class ${Entity.Name} { // Private Fields <# for each Property in Entity.Properties message Processing Field ${Property.Name} #> private ${CSharpType(Property)} ${CSharpFieldName(Property)}; <# end for #> // Default Constructor public ${Entity.Name}() { } // Public Properties <# for each Property in Entity.Properties message Processing Property ${Property.Name} #> public ${CSharpType(Property)} ${Property.Name} { get { return ${CSharpFieldName(Property)}; } set { ${CSharpFieldName(Property)} = value; } } <# end for #> } }

 


Páginas de Ayuda


Aproveché en este proyecto web para probar algunas ideas. Hay código para escribir, en una especie de Wiki, una página de ayuda en cada página.



Una vez en una página de ayuda, se puede editar:



Vean que hay una forma de poner enlaces a otras páginas del “wiki”, y para páginas externas. Los datos de una página se representan en memoria en un objeto .NET. Ese objeto se serializa en un archivo XML dentro del directorio Pages de la aplicación. No hay una base de datos: cada página de ayuda es un objeto, y se lee desde un archivo serializado .xml.


Conclusiones


Espero que este sistema ayude a que se entienda mejor el proyecto AjGenesis, y que pueda ser usado por más gente. Siempre podemos generar desde el NAnt, como en los ejemplos que mencioné, o como se describe:


Generando aplicaciones con AjGenesis


Pero siempre insisto en un punto: estos son ejemplos, no es que AjGenesis genere sólo esto. En Uds. está la capacidad de definir el modelo que les convenga, para el o los proyectos de desarrollo que tengan a cargo, en las tecnologías que quieran. Pueden codificar DDD de distinta forma a lo que yo sugiero. Pueden generar mapeos de Hibernate/NHibernate siguiendo otros criterios. Pueden generar PHP5 con Prado, en lugar de PHP4 plano. Pueden escribir procedimientos almacenados para Oracle, en lugar de los que tengo escritos para MS SQL Server. Y pueden mejorar éstos. Pueden generar WinForms en lugar de páginas ASP.NET. Pueden generar las páginas vistas de JavaServer Faces y los beans que las soportan. Pueden generar el código o texto que deseen (podrían, por ejemplo, extender los ejemplos para incluir una versión de test unitarios, o páginas documentación), y yo recomiendo que ese código generado sea similar al que hubieran generado manualmente.


Invito a quienes tengan trabajos hechos que los posteen, o dejen comentarios, o participen de la lista de Generación de Código (en español):


http://groups.google.com/group/codegeneration?hl=es


Futuros pasos


Ahora que vuelvo al ruedo del desarrollo, la idea es:


- Mejorar los templates para abarcar más tecnologías, como soluciones y proyectos para VS 2008, templates para Struts 1/2, mejor uso de Hibernate, NHibernate, generación para ASP.NET MVC


- Escribir sobre esos templates


- Publicar los ejemplos


y como siempre


- Escribir de una vez por todas una documentación más útil (aunque creo que he hecho un trabajo decente describiendo varios de los pasos en los post sobre el proyecto, pero siempre se puede mejorar)


Nos leemos!


Angel “Java” Lopez
http://www.ajlopez.com/

This entry was posted in 2643, 6145. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>