Refactoring Golf

Los días 11, 12 y 13 de Octubre se realizó en Buenos Aires (Argentina) la conferencia Agiles2011, es el evento latinoamericano más grande sobre metodologías ágiles, al cual tuve el placer de asistir y dar una ponencia junto con Gustavo Quiroz.

La ponencia se titula “Refactoring Golf”, es un workshop avanzado de Refactoring que está inspirado en la ponencia original realizada en el Software Craftmaship 2010.

Materiales

Les dejo todos los materiales del workshop, están decentemente reutilizables. Pueden utilizarlos libremente, solo déjenme sus comentarios e ideas para poder mejorar el material. Pueden hacer un fork al repositorio para agregarle más cosas pero no se olviden de avisarme para que yo también pueda usarlas =).

Descripción y Reglas.

Repositorio Github: Contiene código fuente, diapositivas y documentos para imprimir.

Ponencia Original.

Diapositivas

Adicionalmente he subido la presentación a Slideshare para que puedan comentarla y compartirla.

Retrospectiva

Esta es una imagen con la retrospectiva realizada al final del workshop.

retrospectiva

Saludos
Angel Núñez Salazar

Refactoring to Design Patterns

En este screencast vamos a ver cómo podemos refactorizar una clase para implementar el patrón State. El código pertenece al caso “Gumball Machine” que se encuentra en el libro Head First Design Patterns.

El código inicial y final que veremos en el screencast lo podemos descargar de https://github.com/snahider/Head-First-Design-Patterns.


Te recomiendo verlo en HD.
Un agradecimiento especial a Israel Antezana (@israelantezana) que me dio esta idea y con el cuál tuve una pequeña sesión de pair programming durante Agiles2011.

Update
Resharper 7 ya viene con el refactoring Extract Class que nos permite mover miembros de una clase a otra que aún no existe.

Saludos
Angel Núñez Salazar

Entity Framework 4.1 y ASP.NET MVC 3

Con la salida de EF 4.1 ha vuelto la discusión sobre el manejo de la Unit of Work en una aplicación web. En este caso vamos a ver algunos pasos utilizando EF 4.1, MVC 3 y StructureMap.

Paso 1: El Dominio

    public abstract class Entity
{
public int Id { get; set; }
}
    public class Post:Entity
{
public String Title { get; set
; }

public String Body { get; set
; }

public List<Comment> Comments { get; set; }

}
    public class Comment:Entity
{
public string Content { get; set
; }

public int PostId { get; set
; }

public Post Post { get; set; }
}

Paso 2: DbContext y la cadena de conexión

Creamos una clase que extienda de DbContext para que a través de ella interactuemos con la base de datos. Por defecto busca una cadena de conexión que tenga el mismo nombre de la clase que lo implementa, en este caso una cadena con el nombre de DatabaseContext. Podemos cambiar el nombre de esta cadena si ingresamos un nuevo parámetro al constructor del DbContext.

    public class DatabaseContext : DbContext
{

public
DatabaseContext() : base("Blog") {}


}
    <add name="Blog" connectionString="xXx" providerName="System.Data.SqlClient" />

Paso 3: Los Repositorios

No me gusta la idea de tener todos los DbSet dentro del DatabaseContext, es por esto que creamos un repositorio base que instancie un DbSet para su correspondiente entidad y también agregamos algunos métodos comunes.

    public class Repository<T> where T : Entity
{
public
Repository()
{
var database = ObjectFactory.GetInstance<DatabaseContext
>();
this
.Set = database.Set<T>();
}

protected readonly DbSet
<T> Set;

public IEnumerable
<T> All()
{
return this
.Set.ToList();
}

public void
Add(T entity)
{
this
.Set.Add(entity);
}

public T Get(int
id)
{
return this
.Set.SingleOrDefault(x=>x.Id==id);
}

public void
Delete(T entity)
{
this.Set.Remove(entity);
}
}

En este caso no estamos inyectando el DatabaseContext por el constructor sino lo obtenemos a través de un Service Locator, esto con la finalidad de no tener que escribir el constructor en todos los repositorios hijos.

    public interface IPostRepository
{
Post Get(int
id);

void Add(Post
post);

void Delete(Post
post);

IEnumerable<Post
> All();

IEnumerable<Comment> Comments(int
idPost);
}

public class PostRepository : Repository<Post>, IPostRepository
{
public IEnumerable<Comment> Comments(int
idPost)
{
return this.Set.SelectMany(p => p.Comments)
.Where(c => c.Post.Id == idPost).ToList();
}
}

Paso 4: Inyección de Dependencias

En ASP.NET MVC 3 se ha formalizado la inyección de dependencias, es por esto que podemos implementar la nueva interfaz IDependencyResolver e indicarle a MVC que utilice esta clase para determinar cuál es la instancia correcta cuando se solicite un determinado tipo.

    public class StructureMapDependencyResolver:IDependencyResolver
{
public StructureMapDependencyResolver(IContainer
container)
{
this
.container = container;
}

private readonly IContainer
container;

public object GetService(Type
serviceType)
{
if
(serviceType.IsAbstract || serviceType.IsInterface)
{
return this
.container.TryGetInstance(serviceType);

}

return this
.container.GetInstance(serviceType);

}

public IEnumerable<object> GetServices(Type
serviceType)
{
return container.GetAllInstances<object>()
.Where(s => s.GetType() == serviceType);
}
}

Ahora configuramos StructureMap y registramos la clase anterior dentro del Global.asax. El punto más importante en cuanto al manejo de la unidad de trabajo es configurar el contenedor para que se tenga una única instancia del DatabaseContext a nivel de todo un request.

    protected void Application_Start()
{
ObjectFactory
.Initialize(x =>
{
x.For<
DatabaseContext
>().HybridHttpOrThreadLocalScoped();
x.For<
IPostRepository>().Use<PostRepository
>();
});

DependencyResolver
.SetResolver(
new StructureMapDependencyResolver(ObjectFactory
.Container));

AreaRegistration
.RegisterAllAreas();
RegisterGlobalFilters(
GlobalFilters
.Filters);
RegisterRoutes(
RouteTable.Routes);
}

Si utilizamos StructureMap necesitamos limpiar todos los objetos que tienen un ciclo de vida a nivel de request, en este caso el DatabaseContext.

    protected void Application_EndRequest(object sender, EventArgs e)
{
ObjectFactory.ReleaseAndDisposeAllHttpScopedObjects();
}

Paso 4: El Filtro de Transacción

Ahora que se comparte el mismo DatabaseContext en todo un request, cuando este finalice, necesitamos enviar a la base de datos todos los cambios que se hayan realizado como si fueran una sola unidad o transacción. Para esto creamos un filtro de acción que guarde los cambios realizados dentro del DatabaseContext.

    public class TransactionAttribute : ActionFilterAttribute
{
public DatabaseContext Context { get; set
; }

public override void OnActionExecuted(ActionExecutedContext
filterContext)
{
if (filterContext.Exception == null
)
{
try
{
Context.SaveChanges();
}
catch (DbUpdateConcurrencyException
)
{
//manejar errores de concurrencia como sea conveniente
}
catch (Exception
)
{
//manejar errores genéricos como sea conveniente
}
}
}
}

Para poder inyectar la instancia correcta del DatabaseContext dentro del filtro, necesitamos extender la clase FilterAttributeFilterProvider.

    public class StructureMapFilterProvider : FilterAttributeFilterProvider
{
private readonly IContainer
container;
public StructureMapFilterProvider(IContainer
container)
{
this
.container = container;
}

public override IEnumerable<Filter
> GetFilters
(
ControllerContext controllerContext, ActionDescriptor
actionDescriptor)
{
var filters = base
.GetFilters(controllerContext, actionDescriptor);

foreach (var filter in
filters)
{
container.BuildUp(filter.Instance);
}

return filters;
}
}

Modificamos el Global.asax para registrar la clase anterior e indicarle a StructureMap que realice inyección a través de propiedades para el DatabaseContext.

    protected void Application_Start()
{
ObjectFactory
.Initialize(x =>
{
x.For<
DatabaseContext
>().HybridHttpOrThreadLocalScoped();
x.For<
IPostRepository>().Use<PostRepository
>();
x.SetAllProperties(p => p.OfType<
DatabaseContext
>());
});

DependencyResolver
.SetResolver(
new StructureMapDependencyResolver(ObjectFactory
.Container));
FilterProviders
.Providers.Add(
new StructureMapFilterProvider(ObjectFactory
.Container));

AreaRegistration
.RegisterAllAreas();
RegisterGlobalFilters(
GlobalFilters
.Filters);
RegisterRoutes(
RouteTable.Routes);
}

Paso 5: Registrando las entidades en el DatabaseContext

Debido a que los DbSets de nuestras entidades no se encuentran dentro del DatabaseContext, este no las reconoce para poder utilizarlas; para registrarlas debemos sobrescribir el método OnModelCreating y por cada entidad llamar al método genérico modelbuilder.Entity<T>.

    public class DatabaseContext : DbContext
{
public DatabaseContext() : base("Blog"
){}

protected override void OnModelCreating(DbModelBuilder
modelBuilder)
{
var method = typeof(DbModelBuilder).GetMethod("Entity"
);

var types = Assembly
.GetExecutingAssembly().GetExportedTypes()
.Where(t =>
typeof(Entity
).IsAssignableFrom(t) &&
!t.IsAbstract && !t.IsInterface)
.ToList();

foreach (var type in
types)
{
var genericMethod = method.MakeGenericMethod(new
[] { type });
genericMethod.Invoke(modelBuilder,
null);
}
}
}

Paso 6: Utilizando lo anterior en los Controllers

Creamos un controller y algunas acciones que utilicen los repositorios y el filtro anteriormente creado.

    public class PostsController : Controller
{
private readonly IPostRepository
postRepository;

public PostsController(IPostRepository
postRepository)
{
this
.postRepository = postRepository;
}

public ActionResult
Index()
{
IEnumerable<Post
> posts = postRepository.All();
return
View(posts);

}

public ActionResult
Create()
{
return
View();
}

[
HttpPost
]
[
Transaction
]
public ActionResult Create(Post
post)
{
postRepository.Add(post);
return RedirectToAction("index"
);
}

}

Último Paso: Creando la Base de datos

Por último podemos crear la base de datos y sus tablas de forma manual o dejar que estas se creen automáticamente cuando se instancie por primera vez el DatabaseContext.

Con esto ya tenemos todo lo necesario para el manejo de la unidad de trabajo a nivel de request.
Hasta el siguiente post.

Saludos
Angel Núñez Salazar

Integración Continua – Resultado Final

En este post veremos cuál es el resultado final al que llegaríamos si realizamos toda la configuración de nuestro proceso de integración continua.


Te recomiendo verlo activando la calidad HD

En la serie de posts sobre integración continua hablaremos con más detalle de todo lo visto en el video. No se lo pierdan.

Saludos.
Angel Núñez Salazar

Integración Continua Series

Acá tenemos la lista de posts de la serie de Integración Continua.

Saludos
Angel Núñez Salazar