Modern ORM'ler (EF, nHibernate) için Havuz Kalıbı aşırı doldurulmuşsa, daha iyi bir soyutlama nedir?


12

Yakın zamanda, güçlü bir ORM ile Entity Framework gibi depo modelinin kullanılmasına karşı birçok argümanı okudum, çünkü Birim benzeri işlevsellik ile birlikte depo benzeri işlevleri de içeriyor.

Kalıbı birim testi gibi bir durum için kullanmaya karşı başka bir argüman, daha genel uygulamalar IQueryable'dan yararlanacağı için havuz paterninin sızdıran bir soyutlama olmasıdır.

Havuz desenini kullanmaya karşı argümanlar bana mantıklı geliyor, ancak önerilen soyutlama yöntemleri genellikle daha kafa karıştırıcı ve sorun kadar aşırı dolu görünüyor.

Jimmy Bogards çözümü, soyutlamaları bir kenara bırakmanın yanı sıra kendi mimarisini tanıtmanın bir karışımı gibi görünüyor. https://lostechies.com/jimmybogard/2012/10/08/favor-query-objects-over-repositories/

Havuzların gereksiz bir başka örneği ... ama mimarimi kullan! http://blog.gauffin.org/2012/10/22/griffin-decoupled-the-queries/

Başka bir ... http://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework

Kendisi daha fazla mimar olmayan "aşırı karmaşık" veri havuzu model yaklaşımının açık bir yerine veya alternatifini bulamadım.


4
Özellikle neyi başarmaya çalışıyorsunuz? Soyutlamaların bir amacı olmalı. Bir CRUD uygulaması yazıyorsanız, ORM muhtemelen yeterince soyuttur.
JacquesB

@JacquesB Güçlü bir etki alanı modeli ile nesne ilişkisel empedans sorunları önlemek için çalışıyorum, ama aynı zamanda bir mvc uygulamada benim bakış modelleri uzak soyut.
AnotherDeveloper

Reed Cospey'in burada IQuerable hakkında söyleyecek çok olumlu şeyleri var: stackoverflow.com/questions/1578778/using-iqueryable-with-linq Bu, taşıma katmanında daha iyi olduğu anlamına gelir. Soyutlama kadar, ben EntityType enjekte etmek gerekiyordu ama yine de ortak yöntemleri korumak istedim Genel Depo desen için kullanımı iyi buldum. Öte yandan, kendimi MSDN LINQ forumunda EF'in bir depo deseni olduğunu savunuyorum çünkü hepsi hafızada. Bir proje, yöntem çağrıları olarak çok iyi çalışan sayısız Where cümlelerini kullandı.
John Peters

Ama içine baktığım ama reddettiğim alanlardan biri İfade Ağacı işiydi, bazıları gerçekten hoşuna gitti, ama .... faydalı bulmadan önce çok çalışmalısın.
John Peters

2
kontrolörlerden SQL çağırmaya geri dönmeliyiz
bobek

Yanıtlar:


12

Sanırım depoları ve genel depoları karıştırıyorsunuz.

Temel bir depo sadece veri deponuzun arayüzünü oluşturur ve verileri döndürmek için yöntemler sağlar

IRepository {
   List<Data> GetDataById(string id);
}

Bir IQueryable veya rastgele sorguları iletmenin diğer yolları aracılığıyla veri katmanını kodunuza sızdırmaz ve iyi tanımlanmış test edilebilir ve enjekte edilebilir yöntem yüzeyi sağlar.

Genel Havuz, sorgunuzu bir ORM gibi iletmenize olanak tanır

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
    //or
    IQueryable<T> Get<T>();
}

Temelde sadece başka bir Genel Depo olan bir ORM'nin üstünde bir Genel Depo kullanmanın fazla bir anlamı olmadığını kabul ediyorum.

Cevap ORM'nizi gizlemek için temel Havuz modelini kullanmaktır


1
ORM ORM? Komik olmaya çalışıyordum. ORM'yi neden soyutlamanız gerekiyor?
johnny

1
her şeyi soyutlamanýn nedeni de bu. kodlarını özel sınıflarla kirletmekten kaçınmak için
Ewan

6

Yanlışlıkla bahsettiğiniz argümanların çoğu, sahip olmadığı Havuz kalıbı özelliklerine atfedilir.

Kavramsal olarak, başlangıçta DDD'de tanımlandığı gibi bir Havuz, yalnızca arayabileceğiniz veya ekleyebileceğiniz bir nesne koleksiyonudur. Arkasındaki kalıcılık mekanizması soyutlanır, böylece bir tüketici olarak bellek içi bir koleksiyon olduğu yanılsamasını alırsınız.

Sızdıran soyutlamaları olan bir Depo uygulaması ( IQueryablesörneğin, açığa vurma ) kötü bir Depo uygulamasıdır.

Yalnızca toplama işlemlerinden (örneğin, İş Birimi özellikleri) daha fazlasını ortaya çıkaran bir Depo uygulaması, zayıf bir Depo uygulamasıdır.

Veri erişimi için Havuz'a alternatifler var mı? Evet, ancak sorunuzda gündeme getirdiğiniz sorunlarla ilgili değil.



1

Bana göre, ORM veya diğer DB kalıcılık katmanlarıyla birlikte depolar şu dezavantajlara sahiptir:

  1. İş Birimlerini Kapsayan. UoW'nin programcı tarafından kodlanması gerekir ve nadiren kullanıcının UoW sınırlarını ve muhtemelen taahhüt noktasını tanımlamaksızın sorguları ve modifikasyonları yaptığı arka planda bir tür sihir olarak uygulanabilir. Bazen UoW, her Havuz erişim yönteminde mikro UoW'ye (örn. NHibernate oturumları) indirilerek terk edilir.
  2. Kalıcılık Cehaletini örtbas etmek veya en kötü durumda yok etmek: "Load ()", "Get ()", "Save ()" veya "Update ()" gibi yöntemler, tek tek gönderme gibi sanki tek tek nesne işlemlerini önerir SQL / DML veya dosyalarla çalışıyormuş gibi. Aslında, örneğin, bu yanıltıcı isimlerle NHibernate yöntemleri genellikle bireysel erişim sağlamaz, ancak tembel yük veya ekleme / güncelleme toplu işlemi için enqueue (Kalıcı Cehalet). Bazen, programcılar neden anında DB işlemlerini alamadıklarını ve zorla kalıcılık bilgisizliğini kırmadıklarını merak ediyorlar, böylece performansı öldürüyorlar ve sistemi gerçekten daha da kötüleştirmek için büyük çaba sarf ediyorlar.
  3. Kontrolsüz Büyüme. Basit bir havuz, belirli ihtiyaçlara uyacak şekilde gittikçe daha fazla yöntem biriktirebilir.

Gibi:

public interface ICarsRepository  /* initial */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); // bad, should be for multiple IDs.
    void SaveCar(ICar carToSave); // bad, no individual saves, use UoW commit!
}

public interface ICarsRepository  /* a few years later */
{
    ICar CreateNewCar();
    ICar LoadCar(int id); 
    IList<ICar> GetBlueCars();
    IList<ICar> GetRedYellowGreenCars();
    IList<ICar> GetCarsByColor(Color colorOfCars); // a bit better
    IList<ICar> GetCarsByColor(IEnumerable<Color> colorsOfCars); // better!
    IList<ICar> GetCarsWithPowerBetween(int hpFrom, int hpTo);
    IList<ICar> GetCarsWithPowerKwBetween(int kwFrom, int kwTo);
    IList<ICar> GetCarsBuiltBetween(int yearFrom, int yearTo);
    IList<ICar> GetCarsBuiltBetween(DateTime from, DateTime to); // some also need month and day
    IList<ICar> GetHybridCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetElectricCarsBuiltBetween(DateTime from, DateTime to); 
    IList<ICar> GetCarsFromManufacturer(IManufacturer carManufacturer); 
    bool HasCarMeanwhileBeenChangedBySomebodyElseInDb(ICar car); // persistence ignorance broken
    void SaveCar(ICar carToSave);
}

4. Tanrı'nın tehlikesi nesnesi: tüm modelinizi veya veri erişim katmanınızı kapsayan bir tanrı sınıfı yaratmaya cazip olabilirsiniz. Havuz sınıfı yalnızca Car yöntemlerini değil, tüm varlıklar için yöntemleri de içerecektir.

Benim düşünceme göre, birçok tek amaçlı yöntemin büyük karmaşasından kaçınmak için en azından bazı sorgu fırsatları sunmak daha iyidir. LINQ, kendi sorgu dili veya doğrudan ORM'den alınan bir şey olursa olsun (Tamam, bir tür bağlantı sorunu ...).


4
Depo Kalıbı kullanılmıyorsa tüm bu yöntemler nereye gider?
johnny


1

Havuz arayüzünün amacı, bir unittest (= tek başına test) için veritabanını alay etmekse , en iyi soyutlama alay etmek kolay bir şeydir.

IQueryable sonucuna dayanan bir veri havuzu arayüzünü taklit etmek güçtür.

Birim testi açısından

IRepository {
   List<Data> GetDataById(string id);
}

kolayca alay edilebilir

IGenericRepository<T> {
    List<T> Get<T>(IQuery query);
}

ancak, sahte sorgu parametresinin içeriğini yoksayarsa kolayca alay edilebilir.

IGenericRepository<T> {
    IQueryable<T> Get<T>(some_parameters);
}

kolayca alay edilemez


0

Sorgulama için lambda işlevlerini kullanırsanız, havuz deseninin aşırıya kaçma olduğunu düşünmüyorum. Özellikle ORM'yi soyutlamak zorunda kaldığınızda (bence her zaman yapmalısınız) o zaman deponun kendisinin uygulama ayrıntılarını umursamıyorum.

Örneğin:

using System;
using System.Collections.Generic;

public class Program
{
    public static void Main()
    {
        UserRepository ur = new UserRepository();
        var userWithA = ur.GetBy(u => u.Name.StartsWith("A"));

        Console.WriteLine(userWithA.Name);


        ur.GetAllBy(u => u.Name.StartsWith("M"))
          .ForEach(u => Console.WriteLine(u.Name));


        ur.GetAllBy(u => u.Age > 13)
          .ForEach(u => Console.WriteLine(u.Name));
    }
}

public class UserRepository 
{
    List<User> users = new List<User> { 
        new User{Name="Joe", Age=10},
            new User{Name="Allen", Age=12},
            new User{Name="Martin", Age=14},
            new User{Name="Mary", Age=15},
            new User{Name="Ashton", Age=29}
    };

    public User GetBy(Predicate<User> userPredicate)
    {
        return users.Find(userPredicate);
    }

    public List<User> GetAllBy(Predicate<User> userPredicate)
    {
        return users.FindAll(userPredicate);
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.