En estos días, estuve escribiendo unos ejemplos de AjGenesis
http://www.ajlopez.com/ajgenesis
mi proyecto de código abierto de generación de código, para producir, desde un modelo, aplicaciones tanto en ASP.NET 1.x/2.x, como en JSP, tanto usando SQL Server en el primer caso, como con MySql en Java, y tanto con ADO.NET, como con NHibernate, Hibernate, ya sea con arquitectura de capas, como en capas a la Domain-Driven Design de Evans.
Sigo escribiendo esos ejemplos, pero ya hay algo publicado en
Usaremos la versión del proyecto que está actualmente en desarrollo:
Quisiera en este artículo, comentar cómo funciona y se construye, uno de esos ejemplos. Permítanme primero repasar algo sobre el proyecto. Primero, “the big picture”:
Está escrito en Visual Basic .NET 1.x, y es código abierto, pues viene con una licencia tipo BSD, que permite utilizarlo en cualquier proyecto que quieran. Se puede usar como librería, invocado desde nuestro proyecto, se puede invocar desde la línea de comando, o puede ser utilizado desde el poderoso NAnt (esto último me permitió organizar mejor las tareas que realiza el generador de código).
En la página del proyecto (y en el proyecto mismo) hay varios ejemplos, que generan código desde un modelo, usando plantillas. Los ejemplos generan código PHP, Java, JSP, VB.NET, C#, ASP.NET, y hasta scripts de base de datos y procedimientos almacenados. Quisiera destacar dos puntos:
– El modelo del que parte es totalmente definible por el usuario
– Las tareas y plantillas a aplicar son totalmente programables y controlables
Esto lo diferencia de otros generadores. Podemos construirnos nuestro propio modelo, y sus propias plantillas, para generar los artefactos de texto que prefiera. Otros sistemas parten de la base de datos, y sólo generan un grupo de artefactos de textos predefinidos (por ejemplo, POJOs, plain old java objects, o DAOs, Data Access Objects). Pero con AjGenesis puede generar el artefacto de texto que se nos ocurra.
Para comprender mejor que el modelo se puede construir y consumir como uno lo disponga, ver un artículo anterior:
Generando Código- Hello World con AjGenesis
Ahí se describen los pasos iniciales, y un modelo que es totalmente libre.
Creando una aplicación
Encaremos hoy algo más completo. Necesitamos armar una solución sencilla, con dos tablas, sobre una base de datos SQL Server, con código VB.NET 2.0, con interface web, capa de servicios, capa de datos, entidades y componentes de negocio a la Microsoft. Queremos generar la solución, los proyectos, los scripts de creación de la base, y procedimientos almacenados. Este ejemplo está incluido en los ejemplos AjGenesisExamples3.zip. Primer paso: escribir el modelo.
El proyecto
En un directorio de proyectos de los ejemplos que acompañan este artículo, hay un directorio Projects/AjFirstExample.
En ese directorio está el archivo Project.xml que contiene el modelo.
<Project>
<Name>AjFirstExample</Name>
<Description>First Example using AjGenesis</Description>
<Prefix>AjFE</Prefix>
<Domain>com.ajlopez</Domain>
<CompanyName>ajlopez</CompanyName>
<Model>
<Entities>
<Entity Source=”Entities/Customer.xml”/>
<Entity Source=”Entities/Supplier.xml”/>
</Entities>
</Model>
</Project>
Recordemos: el modelo es libre. Acá definimos, para los templates que vamos a usar, las entidades de nuestro modelo: customers y suppliers.
Las entidades
Para que un archivo XML no resulte terriblemente largo, AjGenesis permite que cualquier nodo del modelo se especifique en un archivo aparte. Es un criterio que he usado para definir cómo se escribe el modelo: el XML resultante no debe herir a la vista, debe ser entendible y abarcable en una lectura.
En el Project.xml, eso aparece en el caso de las entidades, con el atributo Source. Examinemos una entidad, escrita en Entities/Customer.xml:
<Entity>
<Name>Customer</Name>
<Description>Customer Entity</Description>
<SetName>Customers</SetName>
<Descriptor>Customer</Descriptor>
<SetDescriptor>Customers</SetDescriptor>
<SqlTable>customers</SqlTable>
<Properties>
<Property>
<Name>Id</Name>
<Type>Id</Type>
</Property>
<Property>
<Name>Name</Name>
<Type>Text</Type>
<SqlType>varchar(200)</SqlType>
</Property>
<Property>
<Name>Address</Name>
<Type>Text</Type>
<SqlType>text</SqlType>
</Property>
<Property>
<Name>Notes</Name>
<Type>Text</Type>
<SqlType>text</SqlType>
</Property>
</Properties>
</Entity>
Hay atributos de la entidad, como su nombre y descripción, en singular y plural, que sirve para nombrarlas en las páginas resultantes, o dentro del código. Las propiedades son los campos a mantener en cada entidad. Vemos que en este ejemplo, no hay más que datos dentro de una entidad.
Aparte de las entidades, en otro directorio, Technologies, se especifica el modelo dependiente de la tecnología, como VbNet2:
<Technology>
<Programming>
<Dialect>VbNet2</Dialect>
</Programming>
<Database>
<Dialect>MsSql</Dialect>
<Name>AjFirstExample</Name>
<Username>sa</Username>
<Prefix>ajfe_</Prefix>
<Host>(local)</Host>
</Database>
</Technology>
Las plantillas
En el directorio Templates/VbNet2 encontramos
Son las plantillas para generación de código VB.Net 2.0. También encontraremos plantillas para C# 1.x y 2, Vb.NET 1, Java. Hay plantillas para usar Nhibernate, Hibernate, JSP, MySql, y conceptos de Domain-Driven Design. Todo desde el mismo modelo. Tomemos como muestra una plantilla, la que genera la entidad en Visual Basic, EntityVb.tpl:
<#
message “Generating Entity ${Entity.Name}”
include “Templates/VbNet2/VbFunctions.tpl”
include “Templates/VbNet2/Prologue.tpl”
#>
‘
‘ Project ${Project.Name}
‘ ${Project.Description}
‘ Entity ${Entity.Name}
‘ ${Entity.Description}
‘
‘
Public Class ${Entity.Name}
‘ Private Fields
<# for each Property in Entity.Properties
message “Procesando Campo ${Property.Name}”
#>
Private m${Property.Name} as ${VbType(Property)}
<#
end for
#>
‘ Default Constructor
Public Sub New()
End Sub
‘ Public Properties
<#
for each Property in Entity.Properties
message “Procesando Propiedad ${Property.Name}”
#>
Public Property ${Property.Name}() as ${VbType(Property)}
Get
Return m${Property.Name}
End Get
Set(ByVal Value As ${VbType(Property)})
m${Property.Name} = Value
End Set
End Property
<#
end for
#>
End Class
Como antes, se usan estructuras de control, y recorrido de una entidad del modelo. No se maneja el XML. El formato XML es la forma de serialización del modelo. Durante el proceso de la plantilla, el modelo ya está en memoria, accesible desde variables dinámicas.
Los pasos
Ahora tenemos más archivos a generar: desde las páginas ASPX, y su código asociado, los proyectos de fachada de servicio, entidades, acceso a datos, el archivo de solución, y más. Para automatizar esta generación, el ejemplo tiene varios archivos de tareas, en el directorio Tasks, donde se describen los pasos a ejecutar. Hay dos grandes tareas: los pasos a ejecutar independientemente de la tecnología elegida, como completar el modelo, revisarlo, y las dependientes de la tecnología, como generar tal archivo JSP o ASPX, dependiendo de si queremos Java o .NET.
La tarea de completar el modelo está a cargo de Tasks\BuildProject.ajg, que comienza con:
‘
‘ Build Project
‘ Complete the Project Data
‘ Project must be loaded in global variable Project
‘
PrintLine “Completing Project ${Project.Name}”
include “Templates/EntityFunctions.tpl”
include “Templates/Utilities.tpl”
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.SystemName then
Project.SystemName = Project.Name
end if
Acá incluye algunas funciones auxiliares, y luego comienza a completar el modelo que reside en la variable Project. Ejemplo: si falta Project.Title le coloca como título el Project.Name. Prosigue:
for each Entity in Project.Model.Entities
PrintLine “Entity ” + Entity.Name
for each Property in Entity.Properties
PrintLine “Property ” & Property.Name
Property.Description = Property.Name
end if
if not Property.Title then
Property.Title = Property.Description
end if
if Property.Type=”Id” and not Property.SqlType then
Property.SqlType=”int”
end if
if Property.SqlType and not Property.SqlColumn then
Property.SqlColumn = Property.Name
end if
if Property.Type=”Id” and not Entity.IdProperty then
Entity.IdProperty = Property
end if
if Property.Reference then
…
Mas adelante, se ejecutan las tareas de tecnología. Como ejemplo, veamos un fragmento de Tasks\BuildVbNet2.ajg:
<#
include “Templates/Utilities.tpl”
include “Templates/VbNet2/UtilitiesVb.tpl”
message “Creating Directories…”
FileManager.CreateDirectory(Project.BuildDir)
FileManager.CreateDirectory(“${Project.BuildDir}/Sql”)
FileManager.CreateDirectory(“${Project.BuildDir}/Src/${Project.Name}.Entities”)
FileManager.CreateDirectory(“${Project.BuildDir}/Src/${Project.Name}.Entities/My Project”)
FileManager.CreateDirectory(“${Project.BuildDir}/Src/${Project.Name}.Data”)
FileManager.CreateDirectory(“${Project.BuildDir}/Src/${Project.Name}.Data/My Project”)
FileManager.CreateDirectory(“${Project.BuildDir}/Src/${Project.Name}.Services”)
…
En este fragmento, se crean los directorios necesarios para albergar la solución. El nombre del directorio se extrae del modelo desde ${Project.BuildDir}.
Generando la solución
Podríamos lanzar las tareas desde la línea de comando, pero tenemos un build armado para el Nant, uno para cada tecnología. Ejecutamos las tareas build, buildsql, y deploysql de AjFirstExampleVbNet2.build:
En el directorio Build/AjFirstExample/VbNet2/Sql quedan los scripts de creación de la base y procedimientos almacenados. Y en el directorio hermano Src, sorpresa, tenemos la solución ya preparada:
Encontramos varios proyectos armados. Si levantamos la solución en el Visual Studio 2005, aparece todo el código generado:
Con otro archive build AjFirstExampleCSharp2.build, generamos la misma solución en CSharp:
Encontrarán otros proyectos y ejemplos de .build, que usan NHibernate, Hibernate, JSP, y conceptos de DDD.
Reflexiones
Claro, no todo se puede generar automáticamente. Es importante tener siempre presente esto. Pero en el día a día, reconozcamos que tenemos cantidad de texto repetitivo, tareas que bien podemos encargar al propio software.
Recordemos siempre: el modelo es libre. Los ejemplos presentados son solamente ejemplos: podemos general el modelo que queremos, y escribir las plantillas que necesitamos. Es importante escribir las plantillas de forma que el código generado sea similar al que hubiéramos generado nosotros. Si no nos sentimos cómodos con el código generado, si no tiene nuestro estilo, nuestra experiencia, terminamos generando algo que no entendemos.
Otra reflexión: el modelo debe ser independiente de la tecnología. En el ejemplo final, hemos visto cómo, desde el mismo modelo, se puede generar la solución para VB.NET2, y para CSharp. Encontrarán las plantillas para generarla con Nhibernate, con DDD, con Hibernate, con Java y JSP, y podemos escribir la que necesitamos.
El software debe ayudarnos a generar software. Nuestra experiencia cuenta: lo que aprendimos de hacer aplicaciones, podemos volcarlo en esta especie de sistema experto, generador de código. Justamente, en el futuro, espero poder incorporar al proyecto, en las plantillas, más toma de decisiones: así como volcamos nuestra experiencia en escritura de aplicaciones, podemos incorporar nuestro conocimiento acumulado sobre patrones, arquitectura, estilos de programación.
Y al ser de código abierto, AjGenesis permite que lo extendamos, a nuestro gusto y necesidad.
Se aceptan sugerencias, historias de uso. Pueden escribir comentarios a este artículo, o escribirme. Desde ya, muchas gracias por cualquier “feedback”.
Nos leemos!
Angel “Java” Lopez
http://www.ajlopez.com/