Servicios de RIA

Modelos empresariales con servicios WCF RIA

Michael D. Brown

Descargar el ejemplo de código

Dos anuncios importantes de PDC09 y Mix10 fueron la disponibilidad de Silverlight 4 beta y RC, respectivamente. Para cuando lea esto, la versión completa para la Web de Silverlight 4 estará disponible para descarga. Junto con amplia compatibilidad con la impresión, incluye compatibilidad con permisos elevados, cámaras web, micrófonos, toast, acceso al portapapeles y más. Con su nuevo conjunto de características, Silverlight 4 se posiciona a la altura de Adobe AIR como marco de UI de varias plataformas.

Aunque todo esto me emociona, lo que me encantaría, como desarrollador principalmente de aplicaciones empresariales, sería una manera simple de introducir mis datos y lógica empresariales en una aplicación Silverlight.

Una inquietud que existe respecto de las aplicaciones de Silverlight de línea de negocio es respecto de los datos. Nada le impide crear su propio servicio de Windows Communication Foundation (WCF) y conectarlo en Silverlight 3, pero esto deja mucho que desear, en especial si se considera la infinidad de maneras en que puede conectarse a datos desde ASP.NET o aplicaciones de escritorio. Mientras las aplicaciones web y de escritorio pueden conectarse directamente con la base de datos a través de NHibernate, Entity Framework (EF) o construcciones de ADO.NET sin procesar, las aplicaciones de Silverlight están separadas de los datos por “la nube”. A esta separación la llamo el abismo de datos.

Atravesar este abismo puede parecer simple al principio. Desde luego, ya se ha hecho hasta cierto punto en varias aplicaciones de Silverlight de amplia variedad de datos existentes. Pero lo que en un principio parece ser una tarea fácil, se vuelve cada vez más complicado a medida que se abordan más inquietudes. ¿Cómo se realiza el seguimiento de cambios a través del cable o se encapsula lógica empresarial en entidades existentes a ambos lados del firewall? ¿Cómo se impide que los detalles de la transmisión se filtren en sus inquietudes empresariales?

Están apareciendo herramientas de terceros para abordar estas inquietudes, pero Microsoft también consideró necesario proporcionar una solución, por lo que introdujo los servicios WCF RIA (anteriormente llamados servicios .NET RIA) o, para mayor brevedad, servicios de RIA (encontrará una introducción completa a los servicios de RIA en “Generación de una aplicación de datos Expense con Silverlight 3” en la edición de mayo de 2009 de MSDN Magazine (msdn.microsoft.com/magazine/dd695920). Lo he estado siguiendo desde la primera vez que me invitaron a la versión beta del programa, ofreciendo sugerencias al equipo de desarrollo y aprendiendo a aprovechar el marco dentro de mis propias aplicaciones.

Una pregunta común en los foros de los servicios de RIA es de qué manera los servicios de RIA se ajustan a la arquitectura de procedimientos recomendados. Siempre me impresionaron las capacidades básicas de formularios sobre datos de los servicios de RIA, pero definitivamente vi la oportunidad de crear una mejor arquitectura para mi aplicación, de manera que las inquietudes sobre el marco no se filtraran a la lógica de mi aplicación.

Presentación de KharaPOS

He desarrollado una aplicación de ejemplo, KharaPOS, para proporcionar un ejemplo tangible de los conceptos que presento en este artículo. Es una aplicación de punto de venta (POS) implementada en Silverlight 4 mediante servicios de RIA, Entity Framework y SQL Server 2008. El objetivo final es permitir que la aplicación se pueda hospedar en la plataforma Windows Azure y SQL Azure, pero existe un pequeño problema con respecto a la compatibilidad de Microsoft .NET Framework 4 (o la falta de ésta) dentro de la plataforma Windows Azure. 

Mientras tanto, KharaPOS ofrece un buen ejemplo del uso de .NET Framework 4 para crear una aplicación real. El proyecto se hospeda mediante CodePlex en KharaPOS.codeplex.com. Puede visitar el sitio para descargar el código, ver documentación y unirse al análisis respecto del desarrollo de la aplicación.

Debo señalar que tomé la mayor parte del diseño y la funcionalidad de la aplicación KharaPOS del libro “Object Models: Strategies, Patterns, and Applications, Second Edition” (Prentice Hall PTR, 1996), de Peter Coad, David North y Mark Mayfield. Me centraré en un solo subsistema de la aplicación, administración de catálogos (consulte la Figura 1).

Figure 1 The Entity Data Model for Catalog ManagementFigura 1 Entity Data Model de administración de catálogos

Modelos empresariales

Existen varios excelentes libros que abordan el diseño de modelos para desarrollo de aplicaciones empresariales. Un libro que yo uso constantemente como referencia es “Patterns of Enterprise Application Architecture” (Addison-Wesley, 2003) de Martin Fowler. Este libro y su sitio web complementario (martinfowler.com/eaaCatalog/) ofrecen un excelente resumen de los modelos de software útiles para desarrollar aplicaciones empresariales.

Varios modelos del catálogo de Fowler abordan la presentación y manipulación de datos y, lo que es interesante, ocupan el mismo espacio que los servicios de RIA. Comprender esto ofrece una imagen más clara de la manera en que los servicios de RIA se pueden adaptar para satisfacer las necesidades de las aplicaciones empresariales más simples hasta las más complejas. Abordaré los siguientes modelos:

  • Formularios y controles
  • Script de transacción
  • Modelo de dominio
  • Nivel de servicio de aplicación

Veamos rápidamente estos modelos. Los primeros tres se refieren a diferentes maneras de abordar la lógica en torno a los datos. A medida que avanza a través de ellos, la lógica pasa de estar dispersa en toda la aplicación y repetida según sea necesario, a estar centralizada y enfocada.

Formularios y controles

El modelo de formularios y controles (o, como yo lo llamo, formularios sobre datos) coloca toda la lógica dentro de la UI. A primera vista, esto parece una mala idea. Sin embargo, desde los puntos de vista del ingreso de datos simple y de maestro/detalle, es el enfoque más sencillo y directo para pasar de la UI a la base de datos. Muchos marcos tienen compatibilidad intrínseca para este modelo (scaffolding de Ruby on Rails, datos dinámicos de ASP.NET y SubSonic son tres de los principales ejemplos), por lo que definitivamente existe tiempo y lugar para lo que algunos llaman anti-modelo. Aunque muchos desarrolladores relegan el enfoque de formularios sobre datos sólo a la creación inicial de prototipo, existen usos definidos para este enfoque en aplicaciones finales.

Cualquiera sea la opinión acerca de su utilidad, la simplicidad o accesibilidad de los formularios sobre datos son innegables. No se llama desarrollo rápido de aplicaciones (RAD) porque sea lento. Los servicios WCF RIA llevan RAD a Silverlight. Al aprovechar Entity Framework, los servicios de RIA y el diseñador de Silverlight, es posible crear un editor simple de formularios sobre datos en una tabla de base de datos en cinco pasos:

  1. Cree una nueva aplicación empresarial de Silverlight.
  2. Agregue un nuevo Entity Data Model (EDM) a la aplicación web creada (mediante el asistente para importar la base de datos).
  3. Agregue un servicio de dominio a la aplicación web (asegúrese de crearla antes, de manera que el EDM se detecte correctamente) que haga referencia al modelo de datos.
  4. Use el panel de orígenes de datos para arrastrar una entidad expuesta por los servicios de RIA a la superficie de una página o control de usuario en la aplicación Silverlight (asegúrese de volver a crearla para que pueda ver el nuevo servicio de dominio).
  5. Agregue un botón y un código subyacente para guardar cambios en el formulario en la base de datos con esta línea simple:
this.categoryDomainDataSource.SubmitChanges();

Ahora cuenta con una cuadrícula de datos simple que se puede usar para editar directamente las filas existentes en la tabla. Al agregarle unos pocos elementos más, se puede crear un formulario que permite agregar nuevas filas a la tabla.

Aunque este modelo se ha demostrado en reiteradas oportunidades, probando la ventaja de RAD con servicios WCF RIA, no está de más mencionarlo aquí, ya que ofrece una línea base para el desarrollo con el marco. De la misma forma, según se mencionó, éste es un modelo válido dentro de las aplicaciones basadas en servicios de RIA.

Recomendación Al igual que con los datos dinámicos de ASP.NET, el modelo de formularios sobre datos se debe usar para la administración simple de UI (como el editor de categoría de productos de KharaPOS), en que la lógica es simple y sencilla: agregar, eliminar y editar filas dentro de una tabla de búsqueda. Pero los servicios de Silverlight y RIA escalan a aplicaciones mucho más complejas, como veremos a continuación.

Puerta de enlace de datos de tablas El enfoque estándar listo para usar sobre las aplicaciones de servicios de RIA que acabo de abordar también se puede considerar como una implementación del modelo de puerta de enlace de datos de tablas, según se presenta en las páginas 144 a 151 del libro de Fowler. A través de dos niveles de direccionamiento indirecto (asignación de EF a través de la base de datos seguida por asignación de servicio de dominio a través de EF), creé una puerta de enlace simple hacia las tablas de base de datos mediante las operaciones básicas de crear, leer, actualizar y eliminar (CRUD), lo que arroja objetos de transferencia de datos (DTO) fuertemente tipados.

Técnicamente, esto no califica como puerta de enlace de datos de tablas propiamente tal, debido a sus niveles dobles de direccionamiento indirecto. Pero si se mira entrecerrando los ojos, se parece mucho al modelo de puerta de enlace de datos de tablas. Para ser honesto, hubiera sido un avance más lógico abordar la asignación entre los servicios de RIA y el modelo de puerta de enlace de datos de tabla, dado que todos los demás modelos de la lista son modelos de interfaz de datos, pero los formularios sobre datos son mayoritariamente un modelo de UI. Sin embargo, consideré más prudente comenzar con el escenario básico y concentrarme en la UI al volver hacia la base de datos.

Model-View-ViewModel (MVVM) Aunque es simple crear un formulario funcional mediante formularios sobre datos, sigue habiendo algo de fricción. En la Figura 2, el XAML para administración de categoría es un ejemplo de esto.

Figura 2. XAML para administración de categorías

<Controls:TabItem Header="Categories">
  <Controls:TabItem.Resources>
    <DataSource:DomainDataSource
      x:Key="LookupSource"
      AutoLoad="True"
      LoadedData="DomainDataSourceLoaded"
      QueryName="GetCategoriesQuery"
      Width="0">
      <DataSource:DomainDataSource.DomainContext>
        <my:CatalogContext />
      </DataSource:DomainDataSource.DomainContext>
    </DataSource:DomainDataSource>
    <DataSource:DomainDataSource
      x:Name="CategoryDomainDataSource"
      AutoLoad="True"
      LoadedData="DomainDataSourceLoaded"
      QueryName="GetCategoriesQuery"
      Width="0">
      <DataSource:DomainDataSource.DomainContext>
        <my:CatalogContext />
      </DataSource:DomainDataSource.DomainContext>
      <DataSource:DomainDataSource.FilterDescriptors>
        <DataSource:FilterDescriptor 
          PropertyPath="Id" 
          Operator="IsNotEqualTo" Value="3"/>
      </DataSource:DomainDataSource.FilterDescriptors>
    </DataSource:DomainDataSource>
  </Controls:TabItem.Resources>
  <Grid>
    <DataControls:DataGrid
      AutoGenerateColumns="False" 
      ItemsSource="{Binding Path=Data,
        Source={StaticResource CategoryDomainDataSource}}" 
      x:Name="CategoryDataGrid">
      <DataControls:DataGrid.Columns>
        <DataControls:DataGridTextColumn 
          Binding="{Binding Name}" Header="Name" Width="100" />
        <DataControls:DataGridTemplateColumn 
          Header="Parent Category" Width="125">
            <DataControls:DataGridTemplateColumn.CellEditingTemplate>
              <DataTemplate>
                <ComboBox 
                  IsSynchronizedWithCurrentItem="False" 
                  ItemsSource="{Binding Source=
                    {StaticResource LookupSource}, Path=Data}"  
                  SelectedValue="{Binding ParentId}" 
                  SelectedValuePath="Id" 
                  DisplayMemberPath="Name"/>
              </DataTemplate>
            </DataControls:DataGridTemplateColumn.CellEditingTemplate>
            <DataControls:DataGridTemplateColumn.CellTemplate>
              <DataTemplate>
                <TextBlock Text="{Binding Path=Parent.Name}"/>
              </DataTemplate>
            </DataControls:DataGridTemplateColumn.CellTemplate>
          </DataControls:DataGridTemplateColumn>
          <DataControls:DataGridTextColumn
            Binding="{Binding ShortDescription}"
            Header="Short Description" Width="150" />
          <DataControls:DataGridTextColumn 
            Binding="{Binding LongDescription}" 
            Header="Long Description" Width="*" />
        </DataControls:DataGrid.Columns>
    </DataControls:DataGrid>
  </Grid>
</Controls:TabItem>

La columna de la categoría primaria en la cuadrícula de datos es un cuadro combinado que usa una lista de categorías existentes, de manera que los usuarios puedan seleccionar la categoría primaria por su nombre en lugar de memorizar el id. de la categoría. Desgraciadamente, Silverlight no tolera que se cargue dos veces el mismo objeto dentro del árbol visual. Por lo tanto, tuve que declarar dos orígenes de datos de dominio: uno para la cuadrícula y otro para el cuadro combinado de búsqueda. De la misma forma, el código subyacente para administrar categorías es más bien intrincado (consulte la Figura 3).

Figura 3 Código subyacente para administrar categorías

private void DomainDataSourceLoaded(object sender, LoadedDataEventArgs e)
{
  if (e.HasError)
  {
    MessageBox.Show(e.Error.ToString(), "Load Error", MessageBoxButton.OK);
    e.MarkErrorAsHandled();
  }
}

private void SaveButtonClick(object sender, RoutedEventArgs e)
{
  CategoryDomainDataSource.SubmitChanges();
}

private void CancelButtonClick(object sender, RoutedEventArgs e)
{
  CategoryDomainDataSource.Load();
}

void ReloadChanges(object sender, SubmittedChangesEventArgs e)
{
  CategoryDomainDataSource.Load();
}

No voy a entregar un tutorial completo acerca de MVVM aquí; para ello, consulte el artículo “Aplicaciones de WPF dentro con el patrón de diseño Model-View--ViewModel” en la edición de febrero de 2009 (msdn.microsoft.com/magazine/dd419663), donde encontrará un excelente tratado sobre el tema. La Figura 4 muestra una forma de aprovechar MVVM dentro de una aplicación de servicios de RIA.

Figura 4 Administración de categorías a través de View Model

public CategoryManagementViewModel()
{
  _dataContext = new CatalogContext();
  LoadCategories();
}

private void LoadCategories()
{
  IsLoading = true;
  var loadOperation= _dataContext.Load(_dataContext.
    GetCategoriesQuery());
  loadOperation.Completed += FinishedLoading;
}

protected bool IsLoading
{
  get { return _IsLoading; }
  set
  {
    _IsLoading = value;
    NotifyPropertyChanged("IsLoading");
  }
}

private void NotifyPropertyChanged(string propertyName)
{
  if (PropertyChanged!=null)
    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

void FinishedLoading(object sender, EventArgs e)
{
  IsLoading = false;
  AvailableCategories=
    new ObservableCollection<Category>(_dataContext.Categories);
}

public ObservableCollection<Category>AvailableCategories
{
  get
  {
    return _AvailableCategories;
  }
  set
  {
    _AvailableCategories = value;
    NotifyPropertyChanged("AvailableCategories");
  }
}

Como puede ver, ViewModel es responsable de iniciar el contexto de dominio y de informar a la UI cuando ocurre una carga, junto con controlar las solicitudes que vienen de la UI para crear nuevas categorías, guardar cambios en categorías existentes y volver a cargar los datos desde el dominio de servicio. Esto deja una clara separación entre la UI y la lógica que la impulsa. Puede parecer que el modelo MVVM necesita más trabajo, pero su belleza se revela la primera vez que se debe cambiar la lógica para introducir datos en la UI. De la misma forma, el traslado del proceso de carga de categorías a ViewModel permite limpiar la vista significativamente (tanto de XAML como de código subyacente).

Recomendación Use MVVM para prevenir que lógica de UI compleja abarrote la UI o, aun peor, el modelo de objeto empresarial.

Script de transacción

A medida que se comienza a agregar lógica en la aplicación, el modelo de formularios sobre datos se hace incómodo. Dado que la lógica respecto de lo que se puede hacer con los datos está incrustada en la UI (o en ViewModel, si se ha dado ese paso), estará dispersa por toda la aplicación. Otro efecto secundario de descentralizar la lógica es que los desarrolladores pueden no estar conscientes de que ya existe funcionalidad específica en la aplicación, lo que puede provocar duplicación. Esto crea pesadillas cuando cambia la lógica, dado que es necesario actualizarla en todas las ubicaciones (suponiendo que todas las ubicaciones que implementen la lógica se hayan catalogado correctamente).

El modelo de transacción de script (págs. 110 a 115 de libro de Fowler) ofrece algo de alivio. Permite separar la lógica empresarial que administra los datos de la UI.

Según lo define Fowler, el script de transacción “organiza la lógica empresarial mediante procedimientos en los que cada procedimiento controla una sola solicitud de la presentación”. Los scripts transacción son mucho más que simples operaciones CRUD. De hecho, se asientan frente a la puerta de enlace de datos de tablas para manejar las operaciones CRUD. Si se lleva al extremo, un script de transacción por separado manejaría cada recuperación y envío a la base de datos. Sin embargo, ya que somos personas lógicas, sabemos que hay un lugar y un momento para todo.

Un script de transacción es útil cuando la aplicación debe coordinar una interacción entre dos entidades, como al crear una asociación entre dos instancias de diferentes clases de entidades. Por ejemplo, en el sistema de administración de catálogo, yo expreso que un producto está disponible para que una unidad de negocio pida su inventario creando una entrada de catálogo. La entrada identifica al producto, la unidad de negocio, el SKU del producto y el tiempo durante el cual se puede solicitar interna y externamente. Para simplificar la creación de unidades de catálogo, creé un método en el servicio de dominio (consulte el siguiente fragmento de código) que proporciona un script de transacción para modificar la disponibilidad del producto de una unidad de negocio sin que la UI deba manipular directamente entradas de catálogo.

De hecho, las entradas de catálogo ni siquiera están expuestas en el servicio de dominio, según se muestra a continuación:

public void CatalogProductForBusinessUnit(Product product, int businessUnitId)
{
  var entry = ObjectContext.CreateObject<CatalogEntry>();
  entry.BusinessUnitId = businessUnitId;
  entry.ProductId = product.Id;
  entry.DateAdded = DateTime.Now;
  ObjectContext.CatalogEntries.AddObject(entry);
  ObjectContext.SaveChanges();
}

En lugar de estar expuestas como función en el contexto de dominio del lado cliente, los servicios de RIA generan una función en la entidad en cuestión (en este caso, Product) que, en el momento en que se la llama, coloca una notificación de cambio en el objeto que se interpreta en el lado servidor como una llamada al método en el servicio de dominio.

Fowler recomienda dos enfoques para implementar el script de transacción:

  1. Con comandos que encapsulen las operaciones y se puedan ir comunicando
  2. Con una única clase que contenga una colección de scripts de transacción

En este caso adopto el segundo enfoque, pero nada impide que usted use comandos. El beneficio de no exponer la entrada de catálogo al nivel de UI es que el script de transacción se convierte en el único medio para crear entradas de catálogo. Si usa un modelo de comando, la regla se aplica por convención. Si un desarrollador olvida que existe un comando, volverá al principio con fragmentación y duplicación de lógica.

Otro beneficio de colocar el script de transacción en el servicio de dominio es que la lógica ejecuta el lado servidor (como mencioné anteriormente). Si tiene algoritmos de su propiedad o desea asegurarse de que el usuario no haya manipulado los datos de manera malintencionada, colocar el script de transacción en el servicio de dominio es la manera de hacerlo.

Recomendación Use el script de transacción cuando la lógica empresarial se vuelva demasiado compleja para formularios sobre datos, cuando desee ejecutar la lógica para una operación en el lado servidor en ambos casos.

Lógica empresarial en contraste con lógica de UI Yo hago muchas referencias a la lógica de UI en contraste con la lógica empresarial, y aunque la diferencia pueda parecer sutil al principio, es importante. La lógica de UI es la que se refiere a la presentación, lo que aparece en la pantalla y la manera en que aparece (por ejemplo, los elementos que se usan para rellenar un cuadro combinado). La lógica empresarial, por otro lado, es lo que impulsa a la aplicación misma (por ejemplo, el descuento que se aplica en una compra en línea). Ambas son facetas importantes de una aplicación, y cuando se permite que se mezclen, surge otro modelo. Consulte el artículo “Gran bola de barro” de Brian Foote y Joseph Yoder (laputan.org/mud).

Paso de varias entidades al servicio de dominio De manera predeterminada, sólo se puede pasar una entidad a un método de servicio de dominio personalizado. Por ejemplo, el método

public void CatalogProductForBusinessUnit(Product product, int businessUnitId)

no funciona si se intenta usar esta firma en lugar de un entero:

public void CatalogProductForBusinessUnit(Product product, BusinessUnit bu)

Los servicios de RIA no generarían un proxy de cliente para esta función porque … bueno, existen las reglas. Sólo se puede tener una entidad en un método de servicio personalizado. Esto no debería representar un problema en la mayoría de las situaciones, dado que, si se tiene una entidad, se tiene su clave y es posible recuperarla en el back end.

Sólo digamos, para efectos de la demostración, que recuperar una entidad es una operación costosa (puede que esté al otro lado del servicio web). Es posible indicar al servicio de dominio que se desea conservar una copia de una determinada entidad, según se muestra a continuación:

public void StoreBusinessUnit(BusinessUnit bu)
{
  HttpContext.Current.Session[bu.GetType().FullName+bu.Id] = bu;
}

public void CatalogProductForBusinessUnit(Product product, int businessUnitId)
{
  var currentBu = (BusinessUnit)HttpContext.Current.
    Session[typeof(BusinessUnit).FullName + businessUnitId];
  // Use the retrieved BusinessUnit Here.
}

Dado que el servicio de dominio se ejecuta con ASP.NET, tiene pleno acceso a la sesión de ASP.NET y también a la memoria caché, en caso de que desee eliminar automáticamente el objeto de la memoria después de un determinado tiempo. Actualmente estoy usando esta técnica en un proyecto en el que debo recuperar datos de administración de las relaciones con el cliente (CRM) desde varios servicios web remotos y presentarlos al usuario bajo una UI unificada. Uso un método explícito, ya que vale la pena almacenar en la memoria caché algunos datos y otros no.

Modelo de dominio

A veces, la lógica empresarial se vuelve tan compleja que ni siquiera los scripts de transacción pueden administrarla correctamente. A menudo, esto se presenta como lógica de bifurcación compleja dentro de un script de transacción o varios scripts de transacción para representar matices de la lógica. Otra señal de que una aplicación ha crecido más allá de los scripts de transacción es la necesidad de frecuentes actualizaciones para abordar requisitos empresariales que cambian rápidamente.

Si nota cualquiera de estos síntomas, es hora de considerar un modelo de dominio enriquecido (págs. 116 a 124 del libro de Fowler). Los modelos abordados hasta ahora tienen una cosa en común: las entidades son poco más que DTO; no contienen lógica (algunos consideran que esto es un anti-modelo, denominado modelo de dominio anémico). Uno de los principales beneficios del desarrollo orientado a objetos es la capacidad de encapsular datos y la lógica relacionada con ellos. Un modelo de dominio enriquecido aprovecha dichos beneficios devolviendo la lógica a la entidad a la que pertenece.

Los detalles del diseño de un modelo de dominio están más allá del alcance de este artículo. Consulte el libro “Domain-Driven Design: Tackling Complexity in the Heart of Software” (Addison-Wesley, 2004), de Eric Evans, o el libro de Coad mencionado anteriormente sobre modelos de objeto para encontrar una gran cobertura sobre este tema. Sin embargo, puedo ofrecer un escenario que ayude a ilustrar la manera en que un modelo de dominio puede administrar parte de este esfuerzo.

Algunos clientes de KharaPOS desean ver ventas históricas de ciertas líneas y decidir, mercado por mercado, si las líneas se ampliarán (poniendo a disposición más productos de éstas), se reducirán, se eliminarán por completo o se mantendrán sin cambios por determinada razón.

Ya tengo los datos de ventas en otro subsistema de KharaPOS, y todo lo que necesito está aquí en el sistema de catálogo. Sólo traeré una lista de sólo lectura de las ventas de productos al Entity Data Model, según se muestra en la Figura 5.

Figure 5 Entity Data Model Updated with Sales Data
Figura 5 Entity Data Model actualizado con datos de ventas

Ahora todo lo que debo hacer es agregar la lógica de selección de productos al modelo de dominio. Dado que voy a seleccionar productos para un mercado, colocaré la lógica en la clase BusinessUnit (usaré una clase parcial con una extensión shared.cs o shared.vb para informar a los servicios de RIA que se desea que esta función se transporte al cliente). La Figura 6 muestra el código.

Figura 6 Lógica de dominio para seleccionar productos de una unidad de negocio

public partial class BusinessUnit
{
  public void SelectSeasonalProductsForBusinessUnit(
    DateTime seasonStart, DateTime seasonEnd)
  {
    // Get the total sales for the season
    var totalSales = (from sale in Sales
                     where sale.DateOfSale > seasonStart
                     && sale.DateOfSale < seasonEnd
                     select sale.LineItems.Sum(line => line.Cost)).
                     Sum(total=>total);
    // Get the manufacturers for the business unit
    var manufacturers =
      Catalogs.Select(c =>c.Product.ManuFacturer).
        Distinct(new Equality<ManuFacturer>(i => i.Id));
    // Group the sales by manufacturer
    var salesByManufacturer = 
      (from sale in Sales
      where sale.DateOfSale > seasonStart
      && sale.DateOfSale < seasonEnd
      from lineitem in sale.LineItems
      join manufacturer in manufacturers on
      lineitem.Product.ManufacturerId equals manuFacturer.Id
      select new
      {
        Manfacturer = manuFacturer,
          Amount = lineitem.Cost
      }).GroupBy(i => i.Manfacturer);
    foreach (var group in salesByManufacturer)
    {
      var manufacturer = group.Key;
      var pct = group.Sum(t => t.Amount)/totalSales;
      SelectCatalogItemsBasedOnPercentage(manufacturer, pct);
    }
  }

  private void SelectCatalogItemsBasedOnPercentage(
    ManuFacturer manufacturer, decimal pct)
  {
     // Rest of logic here.
  }
}

Seleccionar automáticamente productos para traspasarlos a otra temporada es tan simple como llamar a la nueva función de BusinessUnit y, a continuación, llamar a la función SubmitChanges en DomainContext. En el futuro, si se encuentra un error en la lógica o es necesario actualizarla, sé exactamente dónde buscar. No sólo he centralizado la lógica, sino que he hecho que el modelo de objeto sea más expresivo de la intención. En la página 246 de su libro “Domain-Driven Design”, Evans explica por qué esto es bueno:

Si un desarrollador debe considerar la implementación de un componente para usarlo, se pierde el valor de la encapsulación. Si una persona distinta del desarrollador original debe inferir el propósito del objeto o de la operación basado en su implementación, el nuevo desarrollador puede inferir un propósito que la operación o clase sólo cumple por casualidad. Si la intención no fue ésa, el código puede funcionar momentáneamente, pero la base conceptual del diseño se habrá dañado, y los dos desarrolladores trabajarán en propósitos distintos.

En otras palabras, al nombrar explícitamente la función según su propósito y al encapsular la lógica (junto con algunos comentarios para dejar claro lo que ocurre), he facilitado que la siguiente persona (aunque esa persona sea yo mismo dentro de cinco meses) determine lo que ocurre incluso antes de que yo pase a la implementación. Unir esta lógica a los datos con los que está relacionada naturalmente aprovecha la naturaleza expresiva de los lenguajes orientados hacia objetos.

Recomendación Use un modelo de dominio cuando la lógica sea compleja y e intrincada e incluya varias entidades a la vez. Incluya la lógica junto con el objeto con el que tiene más afinidad y proporcione un nombre significativo e intencional para la operación.

La diferencia entre modelo de dominio y script de transacción en los servicios de RIA Puede haber notado que tanto para el script de transacción como para el modelo de dominio la llamada se realizó directamente en la entidad. Tenga en cuenta, sin embargo, que la lógica de los dos modelos reside en dos lugares por separado. En el caso del script de transacción, llamar a la función en la entidad sólo sirve para indicar al contexto o servicio de dominio que la función correspondiente se debe llamar en el servicio de dominio la siguiente vez que se llame a submit changes. En el caso del modelo de dominio, la lógica se ejecuta en el lado cliente y, a continuación, se confirma cuando se llama a submit changes.

Los objetos de repositorio y consulta El servicio de dominio implementa naturalmente el modelo de repositorio (consulte Fowler, pág. 322). En la galería de códigos de servicios WCF RIA (code.msdn.microsoft.com/RiaServices), el equipo de servicios de RIA ofrece un excelente ejemplo de la creación de una implementación explícita del modelo a través de DomainContext. Esto permite una mayor capacidad de prueba de la aplicación sin necesidad de visitar realmente el nivel de servicio en la base de datos. En mi blog (azurecoding.net/blogs/brownie) también ofrezco una implementación del modelo objeto de consulta (Fowler, pág. 316) sobre el repositorio, que se diferencia de la ejecución del lado servidor de consulta hasta la enumeración real.

Nivel de servicio de aplicación

Pregunta rápida: ¿qué se hace cuando se desea aprovechar un modelo de dominio enriquecido, pero no se desea exponer su lógica al nivel de UI? En esa situación es útil el modelo de nivel de servicio de aplicación (Fowler, p. 133). Una vez que se tiene el modelo de dominio, este modelo es fácil de implementar sacando la lógica de dominio de shared.cs a una clase parcial distinta y colocando una función en el servicio de dominio que invoca la función en la entidad.

El nivel de servicio de la aplicación actúa como una fachada simplificada sobre el modelo de dominio, exponiendo las operaciones pero no sus detalles. Otro beneficio es que los objetos de dominio podrán tomar dependencias internas sin requerir que clientes de nivel de servicio las tomen también. En algunos casos (consulte el ejemplo de selección de productos estacionales que aparece en la Figura 6), el servicio de dominio hace una llamada simple en el dominio. A veces puede orquestar algunas entidades, pero tenga cuidado: demasiada orquestación lo convierte nuevamente en un script de transacción, y se pierden los beneficios de la encapsulación de la lógica dentro del dominio.

Recomendación Use el nivel de servicio de aplicación para proporcionar una fachada simple sobre el modelo de dominio y eliminar el requisito que el nivel de UI necesita para tomar dependencias que las entidades podrían tomar.

Información adicional: el contexto limitado

En los foros de RIA, los participantes a menudo preguntan “¿cómo separo mi gran base de datos en servicios de dominio, de manera que sea más administrable?”. Una pregunta que viene a continuación es “¿cómo administro entidades que necesitan existir en varios servicios de dominio?”. Al principio, yo pensé que no debería haber necesidad de hacer estas cosas; el servicio de dominio debe actuar como nivel de servicio sobre el modelo de dominio, y un solo servicio de dominio debe servir como fachada sobre todo el dominio.

Durante mi investigación para este artículo, sin embargo, me topé con el modelo de contexto limitado (Evans, p. 336), que había leído anteriormente pero que no había recordado al responder a estas preguntas originalmente. La premisa básica del modelo es que en cualquier proyecto de gran escala participarán varios subdominios. Por ejemplo, en KharaPOS tengo un dominio para catálogos y uno separado para ventas.

El contexto limitado permite que dichos dominios coexistan pacíficamente aunque compartan elementos (como Sale, Business Unit, Product y LineItem, que se encuentran tanto en los dominios de ventas como de catálogo). Se aplican reglas diferentes a las entidades según el dominio que interactúe con ellas (Sale y LineItem son de sólo lectura en el dominio de catálogo). Una regla final es que las operaciones nunca atraviesan contextos. Esto es muy conveniente, dado que Silverlight no admite transacciones entre varios servicios de dominio.

Recomendación Use contextos limitados para dividir un sistema grande en subsistemas lógicos.

La fosa del éxito

En este artículo, hemos visto la manera en que los servicios de RIA admiten a los principales modelos empresariales con muy poco esfuerzo. Pocas veces los marcos son tan accesibles y, a la vez, lo suficientemente flexibles para admitir todo, desde las aplicaciones de ingreso de datos de hoja de cálculo más simples hasta las aplicaciones empresariales más complejas sin requerir mayor esfuerzo para realizar la transacción. Ésta es la “fosa del éxito” que mencionaba Brad Abrams en su publicación en el blog, del mismo nombre (blogs.msdn.com/brada/archive/2003/10/02/50420.aspx).                 

Mike Brown es el presidente y cofundador de KharaSoft Inc. (kharasoft.com), una empresa de tecnología que se especializa en educación, desarrollo de software personalizado y software como servicio. Es un especialista en tecnología consumado, con más de 14 años de experiencia en la industria, receptor muchas veces del premio MVP, cofundador del grupo de usuarios Indy Alt.NET (indyalt.net) y acérrimo hincha de los Bears.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo:Brad Abrams