Eventos

[.NET] Entendiendo el Patrón de Repositorio

Implementar el patrón de repositorio es una práctica muy común en aquellos proyectos que necesitan trabajar con datos externos, ya sea esto a través de bases de datos, servicios web, archivos u otros.

Este patrón nos ayuda mejorar la mantenibilidad y la capacidad de prueba. Con la correcta separación de intereses, nuestra lógica de negocio ya se puede concentrar en el negocio en sí y no preocuparse en operaciones de bajo nivel, como abrir una conexión a una base de datos o algún socket.

Este patrón nos habla de una capa intermedia entre la lógica de negocio y la fuente de datos, pero hay muchas formas de entenderlo así que exploremos algunos puntos.

1. Reponsabilidad única

En el siguiente programa estamos accediendo a una base de datos para obtener un listado y también para mostrarlo en pantalla, pero todo se encuentra en una sola clase violando el principio de diseño de responsabilidad única.

namespace RepPattern
{
internal class Program
{
private static void Main(string[] args)
{
DatabaseFactory.SetDatabaseProviderFactory(new DatabaseProviderFactory(), false);
RunApplication();
Console.ReadKey();
}
private static async void RunApplication()
{
var entities = await GetEntities();
foreach (var item in entities)
Console.WriteLine("Id: {0}, Names: {1}", item.Id, item.Names);
}
private static Task<List<MyEntity>> GetEntities()
{
var taskCompletionSource = new TaskCompletionSource<List<MyEntity>>();
var database = DatabaseFactory.CreateDatabase();
database.BeginExecuteReader("[dbo].[myTable_sel]", (asyncResult) =>
{
try
{
var entities = new List<MyEntity>();
var db = (Database)asyncResult.AsyncState;
using (var reader = db.EndExecuteReader(asyncResult))
{
var a = reader.GetOrdinal("Id");
var b = reader.GetOrdinal("Names");
var c = reader.GetOrdinal("Address");
var d = reader.GetOrdinal("Email");
var e = reader.GetOrdinal("Code");
var f = reader.GetOrdinal("Lat");
var g = reader.GetOrdinal("Lng");
while (reader.Read())
{
entities.Add(new MyEntity
{
Id = reader.GetInt32(a),
Names = reader.GetString(b),
Address = reader.GetString(c),
Email = reader.GetString(d),
Code = reader.GetString(e),
Lat = reader.GetDouble(f),
Lng = reader.GetDouble(g)
});
}
}
taskCompletionSource.TrySetResult(entities);
}
catch (Exception ex)
{
taskCompletionSource.TrySetException(ex);
}
}, database);
return taskCompletionSource.Task;
}
}
}
view raw I_Program.cs hosted with ❤ by GitHub
Lo ideal sería mover la lógica correspondiente a la conexión a base de datos a una una clase aparte en otro proyecto de librería de clases.

namespace RepPattern.Repository
{
public class MyEntityRepository
{
public Task<List<MyEntity>> GetEntities()
{
var taskCompletionSource = new TaskCompletionSource<List<MyEntity>>();
var database = DatabaseFactory.CreateDatabase();
database.BeginExecuteReader("[dbo].[myTable_sel]", (asyncResult) =>
{
try
{
var entities = new List<MyEntity>();
var db = (Database)asyncResult.AsyncState;
using (var reader = db.EndExecuteReader(asyncResult))
{
var a = reader.GetOrdinal("Id");
var b = reader.GetOrdinal("Names");
var c = reader.GetOrdinal("Address");
var d = reader.GetOrdinal("Email");
var e = reader.GetOrdinal("Code");
var f = reader.GetOrdinal("Lat");
var g = reader.GetOrdinal("Lng");
while (reader.Read())
{
entities.Add(new MyEntity
{
Id = reader.GetInt32(a),
Names = reader.GetString(b),
Address = reader.GetString(c),
Email = reader.GetString(d),
Code = reader.GetString(e),
Lat = reader.GetDouble(f),
Lng = reader.GetDouble(g)
});
}
}
taskCompletionSource.TrySetResult(entities);
}
catch (Exception ex)
{
taskCompletionSource.TrySetException(ex);
}
}, database);
return taskCompletionSource.Task;
}
}
}
Y terminar con la clase principal de la siguiente forma:

namespace RepPattern
{
internal class Program
{
private static void Main(string[] args)
{
DatabaseFactory.SetDatabaseProviderFactory(new DatabaseProviderFactory(), false);
RunApplication();
Console.ReadKey();
}
private static async void RunApplication()
{
var entities = await new MyEntityRepository().GetEntities();
foreach (var item in entities)
Console.WriteLine("Id: {0}, Names: {1}", item.Id, item.Names);
}
}
}
view raw II_Program.cs hosted with ❤ by GitHub

2. Repositorios genéricos

En muchas operaciones con datos, creamos, modificamos, eliminamos o consultamos registros, por ello es usual considerar la creación de una interfaz para un repositorio genérico.

namespace RepPattern.Repository
{
public interface IRepository<T>
{
void Create(T entity);
List<T> ReadAll();
T Find(int id);
void Update(T entity);
void Delete(int id);
}
}
Sin embargo, esta idea nos puede llevar a encontrarnos con situaciones muy parecidas a estas:

using Microsoft.Practices.EnterpriseLibrary.Data;
using System;
using System.Collections.Generic;
namespace RepPattern.Repository
{
public class MonthlyBalanceRepository : IRepository<MonthlyBalance>
{
public void Create(MonthlyBalance entity)
{
throw new NotImplementedException();
}
public void Delete(int id)
{
throw new NotImplementedException();
}
public MonthlyBalance Find(int id)
{
MonthlyBalance result = null;
using (var reader = DatabaseFactory.CreateDatabase()
.ExecuteReader("[sales].[balance_find_bymonth]", id))
{
if (reader.Read())
result = new MonthlyBalance
{
//...
};
}
return result;
}
public List<MonthlyBalance> ReadAll()
{
var result = new List<MonthlyBalance>();
using (var reader = DatabaseFactory.CreateDatabase()
.ExecuteReader("[sales].[balance_list_bymonth]"))
{
while (reader.Read())
result.Add(new MonthlyBalance
{
//...
});
}
return result;
}
public void Update(MonthlyBalance entity)
{
throw new NotImplementedException();
}
}
}
No es que se acabe el mundo, pero hay que tomarlo en consideración ya que podría dificultar el diseño de nuestras aplicaciones y viola un principio de diseño.

3. Segregación de interfaces

Implementar las interfaces adecuadamente nos ayuda a tener mejores resultados. Si creamos una interfaz como esta:

namespace RepPattern.Repository.Base
{
public interface IMyEntityRepository
{
Task<List<MyEntity>> GetEntities();
}
}
Tendremos la capacidad de trabajar con diferentes implementaciones según la ocasión.

namespace RepPattern.Repository
{
public class MyEntityMockRepository : IMyEntityRepository
{
public Task<List<MyEntity>> GetEntities()
{
var json = File.ReadAllText("entities.json");
return Task.FromResult(JsonConvert.DeserializeObject<List<MyEntity>>(json));
}
}
}
namespace RepPattern.Repository
{
public class MyEntitySqlRepository : IMyEntityRepository
{
public Task<List<MyEntity>> GetEntities()
{
var taskCompletionSource = new TaskCompletionSource<List<MyEntity>>();
var database = DatabaseFactory.CreateDatabase();
database.BeginExecuteReader("[dbo].[myTable_sel]", (asyncResult) =>
{
try
{
var entities = new List<MyEntity>();
var db = (Database)asyncResult.AsyncState;
using (var reader = db.EndExecuteReader(asyncResult))
{
var a = reader.GetOrdinal("Id");
var b = reader.GetOrdinal("Names");
var c = reader.GetOrdinal("Address");
var d = reader.GetOrdinal("Email");
var e = reader.GetOrdinal("Code");
var f = reader.GetOrdinal("Lat");
var g = reader.GetOrdinal("Lng");
while (reader.Read())
{
entities.Add(new MyEntity
{
Id = reader.GetInt32(a),
Names = reader.GetString(b),
Address = reader.GetString(c),
Email = reader.GetString(d),
Code = reader.GetString(e),
Lat = reader.GetDouble(f),
Lng = reader.GetDouble(g)
});
}
}
taskCompletionSource.TrySetResult(entities);
}
catch (Exception ex)
{
taskCompletionSource.TrySetException(ex);
}
}, database);
return taskCompletionSource.Task;
}
}
}
Y en nuestro programa principal solo nos preocupariamos de la interface y de como se realizaría la instanciación que necesitamos.

namespace RepPattern
{
internal class Program
{
private static void Main(string[] args)
{
DatabaseFactory.SetDatabaseProviderFactory(new DatabaseProviderFactory(), false);
RunApplication();
Console.ReadKey();
}
private static async void RunApplication()
{
IMyEntityRepository entityRepository = new MyEntityMockRepository();
var entities = await entityRepository.GetEntities();
foreach (var item in entities)
Console.WriteLine("Id: {0}, Names: {1}", item.Id, item.Names);
}
}
}
view raw IV_Program.cs hosted with ❤ by GitHub
Algunos extienden esto usando inyección de dependencias, otros se organizan usando espacios de nombre y otros hacen cosas diferentes. La resolución o complejidad de esto ya depende de los requisitos del proyecto o elección de cada uno.

No hay comentarios.:

Publicar un comentario

Epicalsoft — Superheroic Software Development Blog Designed by Templateism Copyright © 2014

Con tecnología de Blogger.