Archivos de la categoría diseño

Aggregates and repositories

This an informal post about this message in the DDD  list.
I really know that this should be answered by a DDD guru but, seeing that no one showed up, I’ll try to fill the space.
UPDATE Nov/19: Added section titles, service implementation to answer Tom’s comment, changed wrong method name from GetActorsNotAssociatedWithPictures to GetActorsNotAssociatedWithMovies (thanks Tom).
Specifications
Let’s say we have this domain: movies and actors acting in that movies.
The goal of this article is to outline how to design this simple domain, to define aggregates (and identify aggregate roots) and the corresponding repositories.
The domain model specifications:
  • Many actors can act in a movie
  • An actor could act in many movies
  • I should be able to remove movies (actors shouldn’t be removed)
  • I should be able to remove and actor (provided it is not associated with any movie)
Let me detail aside the query specifications:
  • I want to retrieve all the movies in what an actor participated
  • I want to retrieve all the movies older that 10 years
Model design
Now let me design the model. We should have at least two objects, Movie and Actor, so let’s start with that classes:
NOTE: code is in C# without IDE assistance and incomplete, for example, trivial accessors omitted.
public class Movie {
private string name;
private int yearPresented;
}
public class Actor {
private string name;
private int yearBorn;
}
OK, pretty simple. Now I’ll try to connect actors and movies. Let’s look for a classic in IMBD, Gone with the wind. If you scroll a page you can see the actors on that movie, they are the Cast, we could build an object Cast but I’ll choose a simple path, more on that later.
Let’s see, for example, for the Scarlett O’Hara character, the actress was Vivien Leigh.
OK, it’s enough of IMDB, let’s go to the model but let me clarify, it’s very important to make the best effort to find the formal (and ubiquitous) language, that’s why I gone to IMDB.
So, seems like a good first step is to build the Character object.
public class Character {
private Actor actor;
private string name;
}
Now is time to define aggregates, I see two:
  • The Actor aggregate including just an Actor instance as aggregate root.
  • The Movie aggregate including a Movie instance as an aggregate root and one or more Character instances. Each Character instance references one Actor aggregate (kind of foreign key in RDMS parlance).
Let me do another round adding some code to my classes (I’m showing code in the  Fowler‘s way):
Mental note: if Martin changed that old picture in his home page I think it’s time to change mine, it’s 10 years old.
public class Movie {

private IList cast = new List();
public Character AddCharacter(Actor actor, string characterName) {
Character newCharacter =
new Character(this, actor, string characterName);
cast.Add(newCharacter):
return newCharacter;
}

}
And the Character class
public class Character {

internal Character(Movie movie, Actor actor, string name) {
this.movie = movie;
this.actor = actor;
this.name = name;
}
private Movie movie;

}
Repositories design
Now I have this domain model pretty much I like it, let me outline the repositories interfaces (one for each aggregate). As you will see, I use the “one method per query” approach, I could use the more concise GetByCriteria approach but it tends to force me to leak the query API to the model.
public interface MovieRepository {
Movie GetByName(string name);
(1) IList GetByActor(Actor actor);
(2) IList GetByCharacterName(string characterName);
(3) IList GetAgedMoreThan(int years);
(4) void Delete(Movie movie)
}
public interface ActorRepository {
Actor GetByName(string name);
(5) IList GetActorsNotAssociatedWithMovies();
(6) void Delete(Actor actor)
}
(1) Should retrieve movies doing a simple join to compare the actor. Pretty easy to do with a good ORM.
(2) and (3) This is a easy queries as well.
(4) Should delete the Movie instances and all the Character instances associated. Actor instances remain untouched.
(5) Not a difficult query, after obtaining the Actors list we could use (6) to delete un referenced actors.
Services design (some of them)
Tom is curious on how to implement the “I should be able to remove and actor (provided it is not associated with any movie)” specification.
I would like to put this into a more real specification like that: “I should be able to purge old pictures and unreferenced actors”. For this spec I would build a maintenance domain service like that:
public interface CatalogMaintenanceService {
public void DeleteDataOlderThan(int yearsOld) {
IList oldMovies = moviesRepository.GetAgedMoreThan(yearsOld);
foreach (Movie movie in oldMovies)
moviesRepository.Delete(movie);

IList orphanedActors =
actorsRepository.GetActorsNotAssociatedWithPictures();
foreach (Actor actor in orphanedActors)
actorsRepository.Delete(actor);
}
}
This service call should be wrapped by a unit of work. The implementation depends on the persistence layer and application platform, for example, in a Web application with NHibernate, it’s usually implemented with a HttpModule.
Please, feel free to ask any question, I would try to answer according my possibilities.
See you

Refactoring State/Strategy

Este post nació a partir de una consulta en la lista de Patrones del Grupo de Usuarios Microsoft.

En mi opinión y partiendo de la poca información que puede intercambiarse en las listas sobre el problema concreto, una de las posibilidades es el uso combinado de los patrones State/Strategy (un refactoring definido por Fowler en su libro, un esquema puede verse aquí).

Brevemente, Leandro necesitaba calcular un impuesto sobre una transacción y el algoritmo de cálculo depende del tipo de cliente y de algun dato adicional (supongamos que entendí correctamente el problema).

La condición para aplicar State es que tenga un caso en el cual el comportamiento del cliente (en este caso, el tipo de liquidador de impuesto) dependa del estado (de un estado o situacion) que el cliente tenga en ese momento. Otra de las condiciones es tener un switch que reemplazar.

Bueno, ahi va algo de codigo (escrito directamente en el blog, sería raro que compile!!!)…

// La clase Cliente
public class Cliente {
   // Este es el estado en cuestion, en principio puede ser privado
   private TipoCliente tipoCliente;

   // Estamos suponiendo, tambien, que cuando se crea el cliente
   // queda ya configurado el tipo, aunque nada impide que
   // este estado cambie durante la vida del cliente.
   public Cliente( TipoCliente tipoCliente ) {
      this.tipoCliente = tipoCliente;
   }
}

// Ahora definimos el tipo, una clase abstracta
public class TipoCliente {
}

// Un tipo de cliente concreto
public class TipoClienteExtranjero : TipoCliente {
}

// Otro tipo de cliente concreto
public class TipoClienteEmpresa : TipoCliente {
}

Bien, hasta aca tenemos la estructura basica armada, ahora veamos como resolvemos el tema de obtener el tipo de calculador de impuesto.

// Primero definimos la interfaz para el calculador
public interface ICalculadorImpuesto {
   // Este metodo deberia calcular el impuesto sobre
   // la transaccion y devolver un objeto que defina
   // el impuesto aplicado (no defini estas dos clases)
   Impuesto CalcularImpuesto( Transaccion trx );
}

// Ahora definimos un calculador facil, para empresas extranjeras
public class CalculadorImpuestoExento : ICalculadorImpuesto {
   public Impuesto CalcularImpuesto( Transaccion trx ) {
      // Aca, de paso aplicamos el patron SpecialCase,
      // algo parecido al DateTime.MinValue
      return Impuesto.Zero;
   }
}

Esta bien, hagamos uno un poco mas complejo

// El que calcula el impuesto a los movimiendos de debito
// y credito para ctas ctes...
public class CalculadorImpuestoLey25413CtaCte : ICalculadorImpuesto {
   public Impuesto CalcularImpuesto( Transaccion trx ) {
      return new Impuesto( trx.Monto * 0.012 );
   }
}

// y el impuesto a los movimiendos de debito y credito para
// cajas de ahorro
public class CalculadorImpuestoLey25413CA : ICalculadorImpuesto {
   public Impuesto CalcularImpuesto( Transaccion trx ) {
      return new Impuesto( trx.Monto * 0.006 );
   }
}

Ahora apliquemos esto en nuestra clase Cliente

// La clase Cliente
public class Cliente {
   ...
   // Este metodo delega en el metodo del tipo de cliente
   public ICalculadorImpuesto ObtenerCalculadorImpuesto() {
      return tipoCliente.ObtenerCalculadorImpuesto( this );
   }
   ...
}

Falta ahora modificar las clases de tipos de cliente

// En la clase abstracta
public class TipoCliente {
   public abstract ICalculadorImpuesto ObtenerCalculadorImpuesto( Cliente cliente );
}

// Un tipo de cliente concreto
public class TipoClienteExtranjero : TipoCliente {
   public override ICalculadorImpuesto ObtenerCalculadorImpuesto( Cliente cliente ) {
      // Aqui devuelvo el claculador que necesito
      // Para extranjeros, exento, por ejemplo
      return new CalculadorImpuestoExento();
   }
}

// Otro tipo de cliente concreto
public class TipoClienteEmpresa : TipoCliente {
   public override ICalculadorImpuesto ObtenerCalculadorImpuesto( Cliente cliente ) {
   // Aqui devuelvo el claculador que necesito
   // Aqui vemos el caso en que el State consulta al
   // objeto que lo contiene para decidir.
   // ( implementacion sencilla! )
   if ( cliente.TipoCuenta == "CtaCte" )
      return new CalculadorImpuestoLey25413CtaCte();
   else
      return new CalculadorImpuestoLey25413CA();
   }
}

Por ultimo, veamos como se usaria esto en el ejemplo que se me ocurre, por ejemplo, en metodo que tiene que calcular el impuesto de la transaccion:

public void RegistraTransaccion( Cliente cliente, Transaccion trx ) {
   ICalculadorImpuesto ci = cliente.ObtenerCalculadorImpuesto();
   Impuesto imp = ci.CalcularImpuesto( trx );
   RegistrarTansaccionEImpuesto( trx, imp );
}

Bueno, espero que esto ayude. Aqui el patron State esta en la familia de clases TipoCliente y el Strategy en el calculador de impuestos.

TDD y Arquitectura en Rosario

Luego de tres años, el último viernes y sábado estuve en Rosario compartiendo buenos momentos con colegas de esa ciudad.

Lo mejor (y el verdadero motivo del viaje) fue la cena y el almuerzo (pizza y asado respectivamente).

El viernes los aburrí un poco con una sesión sobre TDD. Si alguien esta interesado puede acceder a la grabación aquí.

También pueden descargar el archivo WMV desde este link (click secundario y guardar destino – el tamaño aproximado es 80MB).

El sábado (mientras haciamos tiempo antes de ir a la parrilla), hablamos de MonoRail.

Muchas gracias a mis colegas rosarinos. Nos veremos pronto.