2013-12-09 11 views
18

Po raz pierwszy wdrażam podejście oparte na domenie bardziej oparte na domenie. Postanowiłem wypróbować model Onion Architecture, ponieważ koncentruje się on na domenie, a nie na infrastrukturze/platformach/etc.Architektura cebuli, jednostka pracy i ogólny wzorzec Repozytorium

enter image description here

W celu streszczenie dala od Entity Framework, mam stworzył rodzajowe repozytorium z jednostka pracy realizacji.

IRepository<T> i IUnitOfWork interfejsy:

realizacje
public interface IRepository<T> 
{ 
    void Add(T item); 

    void Remove(T item); 

    IQueryable<T> Query(); 
} 

public interface IUnitOfWork : IDisposable 
{ 
    void SaveChanges(); 
} 

Entity Framework of IRepository<T> i 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(); 
    } 
} 

klienta repozytorium:

public interface ICustomerRepository : IRepository<Customer> 
{ 

} 

public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository 
{ 
    public CustomerRepository(IUnitOfWork unitOfWork): base(unitOfWork) 
    { 
    } 
} 

ASP.NET kontroler MVC pomocą repozytorium

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"); 
    } 
} 

Zależność wtrysk z sobą

container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>(); 
container.RegisterType<ICustomerRepository, CustomerRepository>(); 

rozwiązanie:

enter image description here

problemy?

  • realizacja Repository (kod EF) jest bardzo ogólne. Wszystko znajduje się po stronie klasy EntityFrameworkRepository<T>. Repozytoria konkretnych modeli nie zawierają żadnej z tych logik. To oszczędza mi pisania ton zbędnego kodu, ale ewentualnie poświęcenia elastyczność?

  • Klasy ICustomerRepository i CustomerRepository są w zasadzie puste. Są wyłącznie po to, aby zapewnić abstrakcję. O ile mi wiadomo, pasuje to do wizji architektury cebuli, w której infrastruktura i zależny od platformy kod znajdują się na zewnątrz systemu, ale mając puste klasy i puste interfejsy, czujesz się źle?

  • Aby użyć innej implementacji trwałości (np. Magazyn tabel Azure), należy utworzyć nową klasę CustomerRepository i odziedziczyć AzureTableStorageRepository<T>. Ale może to prowadzić do redundancji kodu (wiele CustomerRepositories)? Jak ten efekt kpi?

  • Inna implementacja (np. Magazyn tabel Azure) ma ograniczenia w zakresie obsługi międzynarodowej, więc klasa AzureTableStorageUnitOfWork nie działałaby w tym kontekście.

Czy są jakieś inne problemy z tym, jak to zrobiłem?

(Brałem większość moich inspiracji this post)

+2

Używasz [wzorca lokalizatora usług] (http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/), biorąc zależność od kontenera IoC. Zamiast tego należy zarejestrować klasę fabryczną, aby wprowadzić ją do kontrolera. Niektóre kontenery IoC mogą wytworzyć jeden z nich dla Ciebie w postaci funkcji 'Func ' zależność – AlexFoxGill

Odpowiedz

22

mogę powiedzieć, że ten kod jest wystarczająco dobry dla pierwszej próbie czasu, ale ma kilka miejsc do poprawy.

Chodźmy przez niektórych z nich

1. Dependency injection (DI) oraz korzystanie z IoC

użyć najprostszej wersji Service Locator pattern -... sam container przykład

Proponuję użyć "wtrysku konstruktora". Możesz znaleźć więcej informacji here (ASP.NET MVC 4 Dependency Injection).

public class CustomerController : Controller 
{ 
    private readonly IUnitOfWork unitOfWork; 
    private readonly ICustomerRepository customerRepository; 

    public CustomerController(
     IUnitOfWork unitOfWork, 
     ICustomerRepository customerRepository) 
    { 
     this.unitOfWork = unitOfWork; 
     this.customerRepository = customerRepository; 
    } 

    public ActionResult List() 
    { 
     return View(customerRepository.Query()); 
    } 

    [HttpPost] 
    public ActionResult Create(Customer customer) 
    { 
     customerRepository.Add(customer); 
     unitOfWork.SaveChanges(); 
     return RedirectToAction("List"); 
    } 
} 

2. jednostki pracy (UOW) zakresu.

Nie mogę znaleźć stylu życia: IUnitOfWork i ICustomerRepository. Nie znam Unity, ale msdn says that TransientLifetimeManager is used by default. Oznacza to, że otrzymasz nową instancję za każdym razem, gdy typ zostanie rozwiązany.

Więc, co następuje próba nie powiedzie się:

[Test] 
public void MyTest() 
{ 
    var target = new UnityContainer(); 
    target.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>(); 
    target.RegisterType<ICustomerRepository, CustomerRepository>(); 

    //act 
    var unitOfWork1 = target.Resolve<IUnitOfWork>(); 
    var unitOfWork2 = target.Resolve<IUnitOfWork>(); 

    // assert 
    // This Assert fails! 
    unitOfWork1.Should().Be(unitOfWork2); 
} 

I oczekuję, że wystąpienie UnitOfWork w kontrolerze różni się od wystąpienia UnitOfWork w swoim repozytorium. Czasami może to powodować błędy. Ale nie jest podświetlony w ASP.NET MVC 4 Dependency Injection jako problem dla Unity.

W stylu Castle WindsorPerWebRequest tryb życia jest używany do udostępniania tego samego wystąpienia typu w ramach pojedynczego żądania http.

Jest to typowe podejście, gdy UnitOfWork jest komponentem PerWebRequest. Niestandardowy ActionFilter może być użyty w celu wywołania Commit() podczas wywoływania metody OnActionExecuted().

Chciałbym również zmienić nazwę metody SaveChanges() i nazywają to po prostu Commit jak to się nazywa w example aw PoEAA.

public interface IUnitOfWork : IDisposable 
{ 
    void Commit(); 
} 

3.1. Zależności od repozytoriów.

Jeśli twoje repozytoria będą "puste", nie ma potrzeby tworzenia dla nich specyficznych interfejsów. Jest możliwe, aby rozwiązać IRepository<Customer> i mieć następujący kod w kontrolerze

public CustomerController(
    IUnitOfWork unitOfWork, 
    IRepository<Customer> customerRepository) 
{ 
    this.unitOfWork = unitOfWork; 
    this.customerRepository = customerRepository; 
} 

Tam jest test, który sprawdza go.

[Test] 
public void MyTest() 
{ 
    var target = new UnityContainer(); 
    target.RegisterType<IRepository<Customer>, CustomerRepository>(); 

    //act 
    var repository = target.Resolve<IRepository<Customer>>(); 

    // assert 
    repository.Should().NotBeNull(); 
    repository.Should().BeOfType<CustomerRepository>(); 
} 

Ale jeśli chcesz mieć repozytoria, które są "warstwa abstrakcji nad warstwą mapowania gdzie kod budowa zapytanie jest skoncentrowany. (PoEAA, Repository)

repozytorium pośredniczy między domeną i odwzorowywanie danych warstw działa jak zbiór obiektów domeny w pamięci. Obiekty klienta konstruktywnie skonstruuj specyfikacje zapytań i przesyłaj je do repozytorium dla satysfakcji.

3.2. Dziedziczenie w EntityFrameworkRepository.

W tym przypadku chciałbym stworzyć prosty IRepository

public interface IRepository 
{ 
    void Add(object item); 

    void Remove(object item); 

    IQueryable<T> Query<T>() where T : class; 
} 

i jej realizacji, który wie, jak pracować z infrastrukturą EntityFramework i może być łatwo zastąpiona przez inną (np AzureTableStorageRepository).

public class EntityFrameworkRepository : IRepository 
{ 
    public readonly EntityFrameworkUnitOfWork unitOfWork; 

    public EntityFrameworkRepository(IUnitOfWork unitOfWork) 
    { 
     var entityFrameworkUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork; 

     if (entityFrameworkUnitOfWork == null) 
     { 
      throw new ArgumentOutOfRangeException("Must be of type EntityFrameworkUnitOfWork"); 
     } 

     this.unitOfWork = entityFrameworkUnitOfWork; 
    } 

    public void Add(object item) 
    { 
     unitOfWork.GetDbSet(item.GetType()).Add(item); 
    } 

    public void Remove(object item) 
    { 
     unitOfWork.GetDbSet(item.GetType()).Remove(item); 
    } 

    public IQueryable<T> Query<T>() where T : class 
    { 
     return unitOfWork.GetDbSet<T>(); 
    } 
} 

public interface IUnitOfWork : IDisposable 
{ 
    void Commit(); 
} 

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>(); 
    } 

    internal DbSet GetDbSet(Type type) 
    { 
     return context.Set(type); 
    } 

    public void Commit() 
    { 
     context.SaveChanges(); 
    } 

    public void Dispose() 
    { 
     context.Dispose(); 
    } 
} 

A teraz CustomerRepository może być pełnomocnikiem i do niego się odwoływać.

public interface IRepository<T> where T : class 
{ 
    void Add(T item); 

    void Remove(T item); 
} 

public abstract class RepositoryBase<T> : IRepository<T> where T : class 
{ 
    protected readonly IRepository Repository; 

    protected RepositoryBase(IRepository repository) 
    { 
     Repository = repository; 
    } 

    public void Add(T item) 
    { 
     Repository.Add(item); 
    } 

    public void Remove(T item) 
    { 
     Repository.Remove(item); 
    } 
} 

public interface ICustomerRepository : IRepository<Customer> 
{ 
    IList<Customer> All(); 

    IList<Customer> FindByCriteria(Func<Customer, bool> criteria); 
} 

public class CustomerRepository : RepositoryBase<Customer>, ICustomerRepository 
{ 
    public CustomerRepository(IRepository repository) 
     : base(repository) 
    { } 

    public IList<Customer> All() 
    { 
     return Repository.Query<Customer>().ToList(); 
    } 

    public IList<Customer> FindByCriteria(Func<Customer, bool> criteria) 
    { 
     return Repository.Query<Customer>().Where(criteria).ToList(); 
    } 
} 
+0

Dziękuję za wspaniałą odpowiedź. (1) Zrobione (2) Walczyłem z tym problemem. Teraz używając PerResolveLifetimeManager (3.1) używam teraz specyficznych interfejsów dla konkretnych metod pobierania (jak GetCustomerByName). Zdecydowałem się usunąć metodę IQueryable Query() z repozytoriów, ponieważ wyciekało szczegóły EF do kodu klienta. (3.2) Przyjrzę się temu bliżej. – davenewza

2

Jedyną wadą widzę, że jesteś bardzo zależy od narzędzia MKOl, więc upewnij się, że realizacja jest stałe. Jednak nie jest to wyjątkowe w przypadku projektów cebuli. Użyłem cebula na liczbę projektów i nie napotkasz żadnych prawdziwych „pułapek”.

+0

Czy IoC nie jest kluczowe dla tego, jak działa Onion? Mógłbym zadeklarować konkretne klasy w moim kodzie wywołującym, ale wtedy znacznie zwiększę sprzężenie i zmniejszę testowalność? Dzięki za odpowiedź :) – davenewza

+1

Tak, dlatego mówię, że jego implementacja musi być solidna. Musisz użyć DI i IOC, aby cebula działała. – Maess

0

Widzę kilka poważnych problemów w kodzie.

Pierwszym problemem jest relacje między repozytoriami i UU.

var unitOfWork = container.Resolve<IUnitOfWork>(); 
    var customerRepository = container.Resolve<ICustomerRepository>(); 

Oto domniemana zależność. Repozytorium nie będzie działać samo bez UU! Nie wszystkie repozytoria muszą być połączone z UoW. Na przykład co z procedurami przechowywanymi? Przechowujesz procedurę i ukrywasz ją za repozytorium. W wywołaniu procedury składowanej używana jest osobna transakcja! Przynajmniej nie we wszystkich przypadkach. Więc jeśli rozwiążę jedyne repozytorium i doda element, to nie zadziała. Co więcej, ten kod nie zadziała, jeśli ustawię Transient life license, ponieważ repozytorium będzie miało inną instancję UoW. Więc mamy ścisłe, niejawne sprzężenie.

Drugi problem polega na utworzeniu ścisłego sprzężenia między silnikiem kontenera DI i wykorzystaniem go jako lokalizatora serwisowego! Lokalizator usług nie jest dobrym podejściem do wdrażania IoC i agregacji. W niektórych przypadkach jest to anty wzorzec. Pojemnik DI powinien być użyty

+0

Przepraszamy .. wpisano szybko i opublikowano z błędami. Kontenery DI powinny być stosowane na najwyższym poziomie.Musisz wdrożyć fabrykę kontrolera i zaimplementować wtrysk zależności za pomocą contructor. W ten sposób usuniesz nadmiarową zależność od kontenera i otrzymasz jawne zależności, które są ustawione w ctor. –

Powiązane problemy