CodeFluent Entities. Nunca el DDD fué tan fácil

Vamos a ver en este post es un producto que he encontrado y parece muy prometedor. CodeFluent Entities es una herramienta de modelado que nos permite generar y mantener actualizadas todas las layers y capas de nuestra aplicación. Asegurando el conjunto de buenas prácticas arquitecturales en nuestros sistemas desde su nacimiento. Esto es, han creado una herramienta para aplicar Domain Driven Development sin que apenas nos demos cuenta.

La he estado probando un tiempo y la verdad me ha dejado sorprendido, pensé en un momento que iba a ser un ORM más, pero luego descubrí el modelador de objetos de negocio, seguido de la generación de servicios RESTful, la sencillez de enlazar el modelo de negocio con las interfaces de usuario ASP.NET Web Forms, WPF y Windows Forms (están trabajando en los conectores para ASP.NET MVC, Silverlight).

Como resumen general esta herramienta trabaja con un montón de conceptos de diseño que son independientes de cualquier plataforma y de la que se habla en la bibliografía de Ingeniería del Software: Entidades, propiedades, reglas, repositorios, etc.

Genera automáticamente el código necesario para trabajar con estos conceptos: esquemas de bases de datos, tablas, vistas, servicios WCF, proxies. Integra una amplia variedad de tecnologías disponibles a día de hoy (SQL Server, SQL Azure, Oracle, ASP.NET, SharePoint, Excel, WPF,…)

Se integra con Visual Studio con una serie de herramientas visuales que nos permite generar nuestro modelo desde cero, importarlo desde otros modelos ya existentes (bases de datos, diagramas uml, o incluso de modelos de Entity Framework)

También permite modelar “Aspectos” para definir reglas y restricciones que se aplicarán después a los conceptos fundamentales de nuestro modelo. Como ejemplo, el aspecto de “localización” que viene incluido y permite la localización de columnas en una base de datos sin ningún esfuerzo.

La herramienta podéis descargarla aquí. Tiene varias versiones y distintos precios, pero lo realmente genial es que la versión Personal es totalmente gratuita y completa, algo que me ha sorprendido y gustado mucho.

Manos a la obra.

Bueno, como el movimiento se demuestra andando vamos a ver un ejemplo de su uso creando una pequeña aplicación.  Así que, una vez instalado, vamos a abrir nuestro amado Visual Studio y a preparar nuestro entorno.

Nota:  El código está en C# pero no se si se puede hacer en VB.

Estructurar la solución.

Primero creamos un proyecto del tipo Class Library y lo llamaremos Sample. Este proyecto contendrá el modelo de negocio de nuestra aplicación.

Ahora vamos a crear un proyecto del tipo SQL Server 2008 DataBase Project y le daremos el nombre de Sample.Persistance que contendrá los scripts que se usarán para generar la capa(layer) de persistencia.

Nota: Hay que descargar e instalar SQL Managements Object (SQL-DMO) en nuestro servidor, para poder usar la característica de actualización de bases de datos para SQL Server. Podéis descargarla aquí (sólo hace falta el componente “Microsoft SQL Server 2005 Backward Compatibility Components).

Ahora es el momento de crear un proyecto de CodeFluent Entites, así que en el menú File/Add/New Project , seleccionamos del árbol de plantillas intaladas la opción CodeFluent Entitesy seleccionamos la plantilla Blank CodeFluent Entities Model:

Nos preguntará en una ventana de diálogo por un namespace por defecto, aseguraos de que ese namespace por defecto es Sample.

Eliminamos el archivo Class1.cs que se generó en la creación del proyecto Class Library. De modo que nuestra solución debe tener el siguiente aspecto:

Ahora que estamos preparados, vamos a modelar nuestra aplicación en el proyecto Sample.Model y configuraremos CodeFluent Entities para que genere el código fuente y los scripts de base de datos en Sample.Persistance y Sample.

Modelando el negocio

Vamos a modelar una aplicación bastante simple que tan sólo contiene tres conceptos: un cliente que puede pedir productos.

En el proyecto Sample.Model  desplegamos el “directorio” Surfaces y hacemos doble clic en Default. Esto abrirá nuestro modelo que está actualmente vacío porque no hemos creado ninguna entidad.

Añadiendo entidades:

Vamos a crear nuestra primera entidad, así que hacemos clic en Add Entity

Nos saldrá un cuadro de diálogo pidiéndonos información sobre la nueva entidad, la llamaremos Customer y la añadiremos al namespace Sample:

Repetimos este paso para añadir otras dos entidades Product y Order. Las añadiremos también al namespace Sample, de manera que nuestro modelo debe ser igual que este:

Añadir propiedades

Una vez que hemos creado las entidades, es la hora de añadir propiedades. Así que si seleccionamos la entidad Customer y hacemos clic en Add Property nos saldrá un diálogo pidiéndonos el nombre y el tipo. A esta primera propiedad le daremos el nombre Id y el tipo le indicamos int:

Repetid el proceso hasta conseguir que nuestras entidades tengan esta pinta:

Customer:Id (int), Name (string),Address (string)

Order:Id (int), Code (string), Date (DateTime)

Product: Id (int), Code (string), Label (string),Price (currency)

También podemos añadir propiedades de tipos complejos (Advanced Type). Añadid una nueva propiedad a Customer, llamadla Orders, y en Type seleccionad Advanced  y buscad el tipo Order.

Haced clic en Cancel. Sólo queríamos ver esta opción, no hacer nada con ella por ahora.

Añadid ahora estas propiedades:

-          A la entidad Customer añadid una propiedad llamada Orders (dejad el tipo como string).

-          A la entidad Order, añadid una propiedad llamada Customer (dejad el tipo como string),

-          En la propiedad Customer de la entidad Order, pulsad la tecla Shitf y haced clic en la propiedad, aparecerá una flecha, haced que la punta de la flecha llegue a la propiedad Orders  de la entidad Customer

-          Aparecerá un diálogo en el que podemos establecer la multiplicidad de la relación.

-          Definidla como que “one Customer  has many Orders”

-          En la entidad Order, añadid una propiedad llamada Products (dejad el tipo como string)

-          Ahora Shif+Clic en la propiedad Products y apuntadla a la entidad Product.

-          En el diálogo de multiplicidad, seleccionad “Products have many Orders” y en el desplegable “Available properties” seleccionad la opción <Unespecified>

El modelo final debe ser tal que así:

Una vez que tenemos nuestra lógica de negocio modelada, es el momento de generar los productores

Generando

Ántes de generar tenemos que generar nuestros productores. Queremos generar las capas (layers) de persistencia y de negocio, así que tan sólo tenemos que añadir dos productores: uno para la persistencia, y otro para el BOM (Business Object Model).

Para añadir un productor, nos vamos al Solution Explorer/Sample.Model/Producers y hacemos clic derecho y seleccionamos “Add New Producer”:

Menu "Add Producer"

Aparecerá la ventana “Add new producer”. Primero añadiremos un productor de persistencia, en nuestro caso elegiremos el de SQL Server. Seleccionamos en el árbol de la izquierda el nodo Persistence Layer Producers” y dentro de él seleccionamos el que se llama “SQL Server”:

Ventana Add New Producer Sql Server

Tenemos que indicar dónde se van a generar los archivos. Para ello editamos la propiedad Target Directory con el botón de “…” y se abrirá una ventana que nos insta a seleccionar un directorio. Seleccionamos el proyecto Sample.Persistence y clicamos OK.

Seleccion del Target directory

Debemos indicar también la cadena de conexión a nuestra instancia de SQL Server. Si no lo indicamos usará la predeterminada que se genera automáticamente de la siguiente forma:

(Application Name=[DefaultNamespace];server=127.0.0.1;database=[DefaultNamespace];Integrated Security=true)

En nuestro ejemplo vamos a usar la instancia de SQLExpress así que configuramos la propiedad Connection String de esta manera:

Configuracion de la cadena de conexión

Ahora hay que volver a añadir un productor más, pero esta vez vamos a añadir un Business Object Model Producer que generará los archivos en el proyecto de Sample. Hay que indicar el Target Directory y tenemos que poner en la propiedad Outpu Name el valor {0}.dll:

Productor de lógica de negocio

Si compilamos el proyecto Sample.Model se generarán los archivos. Así que vamos a compilar.

Y casi por arte de magia se han generado todas las clases y scripts de base de datos de nuestra solución:

Solución generada

¡Hemos generado nuestra primera solución con CodeFluent Entities!

Llegados a este punto, hemos generado la capa (layer) de persistencia (base de datos, tablas, procedimientos almacenados, relaciones, etc.), y un conjunto de clases que nos van a permitir manipular estos objetos de persistencia desde las capas superiores (por ejemplo: desde la interfaz de usuario). Es importante saber que ahora tenemos las bases para cualquier aplicación de negocio: tanto si son un sitio ASP.NET, o cliente rico o smart client, se crearán encima de estas dos capas(layers).

Así que vamos a crear un cliente de Windows Forms por ejemplo.

Cliente de Windows Forms.

Vamos a añadir un nuevo proyecto a nuestra solución del tipo WinForms Application Project y lo vamos a llamar Sample.WinFormsApplication.

Referenciamos el proyecto Sample, y las dlls CodeFluent.Runtime.dll (en el directorio de instalación de CodeFluent Entities) y WindowsBase.dll (instalado en la GAC).

Ahora añadimos un archivo de configuración App.config de manera que este proyecto debe tener un aspecto como este:

Solución WinForms

Antes de empezar a escribir código en nuestra aplicación de Windows Forms, tenemos que configurarla adecuadamente. Primero, vamos a añadir una sección de configuración al App.config que indica a nuestro modelo de negocio (Business Object Model) a qué base de datos se va a conectar. Así que vamos a abrir el App.config y añadamos estos al principio del nodo de configuración:

<section type="CodeFluent.Runtime.CodeFluentConfigurationSectionHandler, CodeFluent.Runtime" name="Sample"></section>

Aseguráos de que tenéis permisos de acceso al servidor de sql.

Establecer la versión del framework.

Visual Studio configura de manera predeterminada a los proyectos para que usen el profile del framework .NET Framework 4 Client Profile. Vamos a cambiarlo al profile .NET Framework 4 para que use las librerías estándar. Así que nos vamos a las propiedades del proyecto Sample.WinFormsApplication (clic derecho en el Solution Explorer/Properties) y en la sección Application buscamos el desplebagle Target Framework y lo configuramos con .NET Framework 4:

Configuracion Target framework

Desarrollando la Interfaz de Usuario.

Sólo para recordarlo por si se nos olvida, la capa de presentación sólo debe contener código de presentación y toda la lógica de negocio debe estar en la capa de negocio. Por tanto, los objetos de negocio se crean para poder ser enlazados (data bound), y ofrecer las funcionalidades necesarias para poder realizar de forma fácil cosas como validaciones, paginación, ordenación, cacheado y esas cosas.

En este post vamos crear una aplicación de Windows Forms en el que los usuarios podrán crear y ver clientes. Veremos que gracias a la capa de negocio, hace falta muy poco código en la capa de presentación, y además el poco código que va  a contener es código de presentación, algo fundamental a la hora de crear sistemas flexibles y mantenibles. Gracias a esta arquitectura se pueden crear interfaces de usuario de manera muy sencilla, ya que sólo cambia la forma en la que se van a presentar los datos y la lógica.

Así que vamos al formulario Form1.cs y vamos a crear la interfaz de usuario. Vamos a añadir:

-          Dos TextBoxes llamados nameTextBox y addressTextBox,

-          Un Botón llamado createButton, con un manejador para el evento Clic.

-          Un ListBox llamado customerListBox con un manejador para el evento SelectedIndexChanged,

-          Un PropertyGrid llamado propertyGrid.

En el método OnCreateButtonClick, cogeremos los strings que haya en los textboxs y crearemos un nuevo usuario en la base de datos. Después, cargaremos todos los clientes disponibles y los enlazaremos al ListBox. Finalmente, en el evento SelectedIndexChanged del listbox, el cliente seleccionado se cargará en el control propertyGrid.

Aquí tenéis el código del codebehind que hace todo eso:

using System;

using System.Windows.Forms;

namespace Sample.WinFormsApplication {

public partial class Form1 : Form

{

public Form1()

{

InitializeComponent();

UpdateListBox();

}

private void UpdateListBox()

{

customerListBox.DisplayMember = "Name";

customerListBox.ValueMember = "Id";

customerListBox.DataSource = CustomerCollection.LoadAll();

}

private void createButton_Click(object sender, EventArgs e)

{

Customer customer = new Customer()

{

Name = nameTextBox.Text,

Address = addressTextBox.Text,

};

customer.Save();

UpdateListBox();

}

private void customerListBox_SelectedIndexChanged(object sender, EventArgs e)

{

propertyGrid.SelectedObject = customerListBox.SelectedItem;

}

}

}

Y el resultado, si no tenéis a un experto en user experience puede ser algo como esto:

Aplicación WinForms

Y ahora con WPF:

Vamos a crear un pequeño cliente con WPF. Así que añadamos un proyecto del tipo WP Application Project  a nuestra solución, y llamemos al proyecto, no sé, Sample.WPFApplication. Referenciemos el proyecto Sample y la dll CodeFluent.Runtime.dll.

Añadamos ahora un archivo app.config y en la sección de configuración añadimos:


<section type="CodeFluent.Runtime.CodeFluentConfigurationSectionHandler, CodeFluent.Runtime" name="Sample"></section>

Que no se nos olvide establecer la versión del framework que va a usar este cliente WPF como .NET Framework 4, en lugar de .NET Framework 4 Client Profile.

El xaml correspondiente que vamos a  usar es:

<Window x:Class="Sample.WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <DataTemplate x:Key="CustomerDataTemplate">
            <StackPanel>
                <TextBlock Text="{Binding Path=Name}" FontWeight="Bold"/>
                <TextBlock Text="{Binding Path=Address}" Margin="5,0,0,0"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <GroupBox Header="Crear cliente">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <StackPanel Grid.Column="0">
                        <Label Content="Nombre:"/>
                        <TextBox Name="_customerNameTextBox" Margin="2,0,2,0"/>
                    </StackPanel>
                    <StackPanel Grid.Column="1">
                        <Label Content="Dirección:"/>
                        <TextBox Name="_customerAddressTextBox" Margin="2,0,2,0"/>
                    </StackPanel>
                    <Button Grid.Column="2" Name="_createCustomerButton"
                           Content="Crear" VerticalAlignment="Bottom"
                           Click="OnCreateCustomerButtonClick"/>
                </Grid>
            </GroupBox>
            <Button Name="_loadCustomerListButton" Content="Cargar clientes" Margin="5,5,5,5"
                   Click="OnLoadCustomerListButtonClick"/>
            <GroupBox Header="Lista de clientes">
                <ListBox Name="_customerListBox" MinWidth="450" MinHeight="100"
                        ItemTemplate="{StaticResource CustomerDataTemplate}"/>
            </GroupBox>
        </StackPanel>
    </Grid>
</Window>

El codebehind es :

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

}

private void OnCreateCustomerButtonClick(object sender, RoutedEventArgs e)

{

Customer customer = new Customer()

{

Name = _customerNameTextBox.Text,

Address = _customerAddressTextBox.Text,

};

customer.Save();

}

private void OnLoadCustomerListButtonClick(object sender, RoutedEventArgs e)

{

_customerListBox.ItemsSource = CustomerCollection.LoadAll();

}

}

De nuevo, si tenéis a un experto en user experience no vendría mal ya que el resultado de esto es:

Aplicación con WPF

Aún hay más, ahora ASP.NET

Como veo que aún queréis más, pues vamos a hacer un cliente en ASP .NET. Así que:

-           añadid un proyecto del tipo ASP.NET Web Application y, en un alarde de originalidad, llamémosle Sample.WebApplication.

-          Referenciemos el proyecto Sample y la dll CodeFluent.Runtime.dll.

-          Añadamos un Web User Control (ASCX), y llamémosle CreateCustomerControl.ascx

Vamos ahora al web.config y añadamos estas líneas al principio del nodo de configuración:

<configSections>

<section name="Sample" type="CodeFluent.Runtime.CodeFluentConfigurationSectionHandler, CodeFluent.Runtime" />

</configSections>
<Sample connectionString="server=.\SQLExpress;database=Sample;Integrated Security=true"/>

Para la interfaz web vamos a crear un user control (CreateCustomerControl.ascx) que va  a permitir a los usuarios dar de alta a clientes en el sistema.

Lo único que vamos a hacer mostrar ese control en la página Default.aspx. Para ello copia y pega este código en el Default.aspx:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Sample.WebApplication._Default" %>

<%@ Register TagPrefix="uc" TagName="CreateCustomerControl" Src="./CreateCustomerControl.ascx"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

<title>Sample</title>

</head>

<body>

<form id="form1" runat="server">

<div>

<uc:CreateCustomerControl ID="CreateCustomerControl1" runat="server"/>

</div>

</form>

</body>

</html><strong></strong>

Ahora, en el CreateCustomerControl, vamos a implementar la interfaz de creación de clientes. Como CodeFluent Entities genera clases de negocio que cumplen con el estándar necesario para crear ObjectDataSource, nuestro CreateCustomerControl puede enlazarse a un ObjectDataSource que ofrezca el acceso a esas clases de negocio. Aquí tenéis el código:


<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="CreateCustomerControl.ascx.cs" Inherits="Sample.WebApplication.CreateCustomerControl" %>

<asp:ObjectDataSource ID="CustomerDataSource" runat="server"

TypeName="Sample.Customer"

DataObjectTypeName="Sample.Customer"

SelectMethod="Load" InsertMethod="Insert" UpdateMethod="Save">

</asp:ObjectDataSource>

<asp:FormView ID="FormView" runat="server"

DefaultMode="Insert" DataSourceID="CustomerDataSource">

<InsertItemTemplate >

<asp:Label ID="NameLabel" runat="server" Text="Name: " /><br />

<asp:TextBox ID="NameTextBox" runat="server" Text='<%#Bind("Name")%>'/><br />

<asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server"

ErrorMessage="This name is required."

ControlToValidate="NameTextBox" ValidationGroup="InsertGroup"/><br />

<br /><asp:Label ID="AddressLabel" runat="server" Text="Address: " /><br />

<asp:TextBox ID="AddressTextBox" runat="server" Text='<%#Bind("Address")%>'/><br />

<asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server"

ErrorMessage="The address is required."

ControlToValidate="AddressTextBox" ValidationGroup="InsertGroup"/><br />

<asp:Button ID="SaveButton" runat="server"

CommandName="Insert" CausesValidation="true"

Text="Create" ValidationGroup="InsertGroup" />

</InsertItemTemplate>

</asp:FormView>

Ya no queda más que ejectuar y verlo:

Aplicación ASP.NET

Una vez creado, el cliente, podemos ir a la base de datos y ver que se ha creado.

Resumen

Hemos creado una arquitectura N-Layer en un abrir y cerrar de ojos con tres clientes: uno de Windows Forms, uno de WPF y otro para ASP.NET.

Hemos visto que CodeFluent Entities es una herramienta bastante prometedora con todo lo que ofrece. Cierto es que este es un escenario sencillo, pero no hay más que navegar por la documentación en la que nos me he basado para este post (que podéis encontrar aquí )y la verdad, a cada nodo que se expande, más interesante me parece. Os recomendaría que le deis un vistazo y podáis ver por vosotros mismos todo lo que ofrecen.

Lo único que se echa en falta es alguna integración con alguna herramienta de testing. Sí, ya sé que diréis que eso debe ser algo innato en cada línea de código que escribimos, pero quizás algún diálogo o marca que nos pregunte: “Eh, ¿quieres ejecutar Pex y generar test?” o, “¿No hay ningún proyecto de test, quieres generar alguno?”  Algo así no estaría tampoco nada mal.