Zwiebel Architektur, die Einheit der Arbeit und ein generic Repository pattern
Dies ist das erste mal, dass ich bin Implementierung eines domain-driven-design-Ansatz. Ich habe beschlossen, zu versuchen die Zwiebel Architektur, wie es konzentriert sich auf die domain statt auf Infrastruktur/Plattformen/etc.
Um abstrakte Weg vom Entity Framework, habe ich eine generic repository mit einem Arbeitseinheit Umsetzung.
Den IRepository<T>
und IUnitOfWork
Schnittstellen:
public interface IRepository<T>
{
void Add(T item);
void Remove(T item);
IQueryable<T> Query();
}
public interface IUnitOfWork : IDisposable
{
void SaveChanges();
}
Entity Framework-Implementierungen von IRepository<T>
und IUnitOfWork
:
public class EntityFrameworkRepository<T> : IRepository<T> where T : class
{
private readonly DbSet<T> dbSet;
public EntityFrameworkRepository(IUnitOfWork unitOfWork)
{
var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork;
if (entityFrameworkUnitOfWork == null)
{
throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork");
}
dbSet = entityFrameworkUnitOfWork.GetDbSet<T>();
}
public void Add(T item)
{
dbSet.Add(item);
}
public void Remove(T item)
{
dbSet.Remove(item);
}
public IQueryable<T> Query()
{
return dbSet;
}
}
public class EntityFrameworkUnitOfWork : IUnitOfWork
{
private readonly DbContext context;
public EntityFrameworkUnitOfWork()
{
this.context = new CustomerContext();;
}
internal DbSet<T> GetDbSet<T>()
where T : class
{
return context.Set<T>();
}
public void SaveChanges()
{
context.SaveChanges();
}
public void Dispose()
{
context.Dispose();
}
}
Den Kunden repository:
public interface ICustomerRepository : IRepository<Customer>
{
}
public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository
{
public CustomerRepository(IUnitOfWork unitOfWork): base(unitOfWork)
{
}
}
ASP.NET MVC-controller arbeiten mit dem repository:
public class CustomerController : Controller
{
UnityContainer container = new UnityContainer();
public ActionResult List()
{
var unitOfWork = container.Resolve<IUnitOfWork>();
var customerRepository = container.Resolve<ICustomerRepository>();
return View(customerRepository.Query());
}
[HttpPost]
public ActionResult Create(Customer customer)
{
var unitOfWork = container.Resolve<IUnitOfWork>();
var customerRepository = container.Resolve<ICustomerRepository>();;
customerRepository.Add(customer);
unitOfWork.SaveChanges();
return RedirectToAction("List");
}
}
Dependency injection with unity:
container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
container.RegisterType<ICustomerRepository, CustomerRepository>();
Lösung:
PROBLEME?
-
Repository-Implementierung (EF-code) ist sehr allgemein gehalten. Alles sitzt in der Seite, die
EntityFrameworkRepository<T>
Klasse. Konkrete Modell-repositories nicht enthalten, diese Logik. Das spart mir aus schreiben von Tonnen redundanter code, sondern möglicherweise Opfer Flexibilität? -
Den
ICustomerRepository
undCustomerRepository
Klassen sind im Grunde leer. Sie sind rein da, um Abstraktion. Soweit ich das verstanden habe, passt sich dieser mit der vision, die Zwiebel Architektur, Infrastruktur-und Plattform-abhängigen code sitzt auf der Außenseite von Ihrem system, aber mit leere Klassen und leere Schnittstellen fühlt sich falsch an? -
Verwenden eine unterschiedliche Persistenz-Implementierung (sagen, Azure Table Storage), dann eine neue
CustomerRepository
Klasse erstellt werden müssen, und die Erben würde eineAzureTableStorageRepository<T>
. Aber dies könnte führen zu redundanter code (mehrere CustomerRepositories)? Wie würde dieser Effekt Spott? -
Andere Implementierung (z.B. Azure Table Storage) hat Beschränkungen der transnationalen Unterstützung, damit die eine AzureTableStorageUnitOfWork Klasse würde nicht funktionieren, in diesem Zusammenhang.
Gibt es andere Probleme mit der Art, wie ich dies getan habe?
(Ich teilgenommen habe, die meisten meiner inspiration aus dieser Beitrag)
- Sie sind über die service locator anti-pattern, indem Sie eine Abhängigkeit von der IoC-container. Stattdessen sollten Sie sich eine factory-Klasse zu injizieren in Ihre controller. Einige IoC-Container kann produzieren diese für Sie in form eines
Func<T>
Abhängigkeit
Du musst angemeldet sein, um einen Kommentar abzugeben.
Kann ich sagen, dass dieser code ist gut genug für die erste Zeit versucht, aber es hat einige Plätze zu verbessern.
Let ' s gehen durch einige von Ihnen.
1. Dependency injection (DI) und die Nutzung des IoC.
Verwenden Sie die einfachste version Service-Locator-Muster -
container
Instanz selbst.Ich schlage vor, Sie verwenden 'constructor injection'. Weitere Informationen finden Sie hier (ASP.NET MVC 4 Dependency Injection).
2. Unit of Work (UoW) Umfang.
Kann ich nicht finden lebensstil
IUnitOfWork
undICustomerRepository
. Ich bin nicht vertraut mit Unity aber msdn sagt, dass TransientLifetimeManager wird standardmäßig verwendet. Es bedeutet, dass Sie erhalten eine neue Instanz jedes mal, wenn Sie das Problem beheben geben.Also, folgender test fehl:
Und ich erwarte, dass die Instanz von
UnitOfWork
im controller unterscheidet sich von der Instanz desUnitOfWork
im repository. Manchmal kann es sein, führte zu Fehlern. Aber es wird nicht hervorgehoben, in der ASP.NET MVC 4 Dependency Injection als ein Problem für die Einheit.In Schloss Windsor PerWebRequest lebensstil wird verwendet, um teilen sich die gleiche Instanz des Typs innerhalb der einzelnen http-Anforderung.
Es ist gemeinsame Ansatz, wenn
UnitOfWork
ist ein PerWebRequest Komponente. BenutzerdefinierteActionFilter
können genutzt werden, um Sie aufrufenCommit()
beim Aufruf vonOnActionExecuted()
Methode.Ich würde auch umbenennen, die
SaveChanges()
- Methode, und rufen Sie es einfachCommit
wie es genannt wird, in der Beispiel und in der PoEAA.3.1. Abhängigkeiten von repositories.
Wenn Ihr repositories gehen, um die 'leere' ist es nicht erforderlich, um spezifische Schnittstellen für Sie. Es ist möglich, Sie zu lösen
IRepository<Customer>
und habe den folgenden code in Ihr controllerEs ist ein test, der prüft es.
Aber wenn Sie möchte haben Repositorys, die 'Ebene der Abstraktion über die mapping-Schicht, wo die Abfrage Baugesetzbuch konzentriert ist.' (PoEAA, Repository)
3.2. Vererbung auf EntityFrameworkRepository.
In diesem Fall würde ich erstellen Sie eine einfache
IRepository
und deren Umsetzung, der weiß, wie die Arbeit mit Entity Framework-Infrastruktur und kann leicht durch ein anderes ersetzt (z.B.
AzureTableStorageRepository
).Und jetzt
CustomerRepository
kann ein proxy und beziehen sich auf Sie.Die einzige con, die ich sehe, ist, dass Sie sehr abhängig von Ihren IOC-tool, so stellen Sie sicher, Ihre Umsetzung ist solide. Dies ist jedoch nicht eindeutig Zwiebel-designs. Ich habe verwendet die Zwiebel bei vielen Projekten und nicht in eine echte "Fallstricke".
Ich sehe paar ernsthafte Probleme im code.
Das 1. problem ist relashionship zwischen Repositorys und UoW.
Hier ist die implizite Abhängigkeit. Repository wird nicht funktionieren, selbst ohne UoW! Nicht alle repositories angeschlossen werden muss, mit UoW. Zum Beispiel was über gespeicherte Prozeduren? Sie haben die gespeicherte Prozedur, und Sie verstecken es hinter repository. Gespeicherte Prozedur-Aufruf verwendet separate Transaktion!!! Zumindest nicht in allen Fällen. Also wenn ich das beheben das nur repository und hinzufügen, dann wird es nicht funktionieren. Außerdem ist dieser code wird nicht funktionieren, wenn ich Vorübergehende Lebens-Lizenz, weil repository haben einen UoW-Instanz. So haben wir engen implizite Kopplung.
Dem 2. problem, das Sie erstellen, die enge Kopplung zwischen den DI-container Motor und verwenden Sie es als service locator! Service locator ist kein guter Ansatz zu implementieren IoC und aggregation. In einigen Fällen ist es anti-pattern. DI-container sollte verwendet werden,