Alternatif untuk pola repositori untuk merangkum logika ORM?

24

Saya baru saja mengganti ORM dan itu adalah tugas yang relatif menakutkan, karena logika kueri bocor di mana-mana. Jika saya pernah harus mengembangkan aplikasi baru, preferensi pribadi saya akan merangkum semua logika kueri (menggunakan ORM) untuk memastikannya untuk perubahan. Pola repositori cukup sulit untuk dikodekan dan dipelihara sehingga saya bertanya-tanya apakah ada pola lain untuk menyelesaikan masalah?

Saya dapat meramalkan posting tentang tidak menambahkan kompleksitas tambahan sebelum itu benar-benar diperlukan, menjadi lincah dll. Tapi saya hanya tertarik tentang pola yang ada memecahkan masalah yang sama dengan cara yang lebih sederhana.

Pikiran pertama saya adalah memiliki repositori tipe generik, yang saya tambahkan metode yang diperlukan untuk kelas repositori tipe spesifik melalui metode ekstensi, tetapi unit pengujian metode statis sangat menyakitkan. YAITU:

public static class PersonExtensions
{
    public static IEnumerable<Person> GetRetiredPeople(this IRepository<Person> personRep)
    {
        // logic
    }
}
Dante
sumber
5
Apa sebenarnya itu tentang pola Repositori yang menurut Anda merepotkan untuk dipelihara dan dipelihara?
Matthew Flynn
1
Ada alternatif di luar sana. Salah satunya adalah menggunakan pola Obyek Kueri yang meskipun saya belum pernah menggunakan sepertinya pendekatan yang baik. IMHO repositori adalah pola yang baik jika digunakan dengan benar tetapi tidak menjadi segalanya dan mengakhiri semua
dreza

Jawaban:

14

Pertama-tama: Repositori generik harus dianggap sebagai kelas dasar dan bukan implementasi lengkap. Ini akan membantu Anda mendapatkan metode CRUD yang umum. Tetapi Anda masih harus menerapkan metode kueri:

public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
    public UserRepository(DbContext context){}

    public ICollection<Person> FindRetired()
    {
        return context.Persons.Where(p => p.Status == "Retired").ToList();
    }
}

Kedua:

Jangan kembali IQueryable. Ini abstraksi yang bocor. Cobalah untuk mengimplementasikan antarmuka itu sendiri atau menggunakannya tanpa sepengetahuan penyedia db yang mendasarinya. Misalnya, setiap penyedia db memiliki API sendiri untuk memuat entitas dengan penuh semangat. Itu sebabnya ini adalah abstraksi yang bocor.

Alternatif

Sebagai alternatif, Anda dapat menggunakan kueri (misalnya sebagaimana ditentukan oleh pola pemisahan Command / Query). CQS dijelaskan dalam wikipedia.

Anda pada dasarnya membuat kelas permintaan yang Anda panggil:

public class GetMyMessages
{
    public GetMyMessages(IDbConnection connection)
    {
    }


    public Message[] Execute(DateTime minDate)
    {
        //query
    }
}

yang hebat dengan permintaan adalah bahwa Anda dapat menggunakan metode akses data yang berbeda untuk permintaan yang berbeda tanpa mengacaukan kode. Anda bisa misalnya menggunakan layanan web dalam satu, nhibernate di yang lain dan ADO.NET di ketiga.

Jika Anda tertarik pada implementasi .NET, baca artikel saya: http://blog.gauffin.org/2012/10/griffin-decoupled-the-queries/

jgauffin
sumber
Bukankah pola alternatif yang Anda sarankan membuat lebih banyak kekacauan pada proyek yang lebih besar karena apa yang akan menjadi metode dalam Pola Repositori sekarang menjadi seluruh kelas?
Dante
3
Jika definisi Anda tentang memiliki kelas-kelas kecil berantakan, ya. Dalam hal ini Anda tidak boleh mencoba untuk mengikuti prinsip-prinsip SOLID baik karena menerapkannya akan selalu menghasilkan lebih banyak kelas (lebih kecil).
jgauffin
2
Kelas yang lebih kecil lebih baik, tetapi navigasi kelas query yang ada tidak akan lebih menyusahkan, daripada mengatakan, menelusuri intellisense dari repositori (domain) untuk melihat, jika fungsionalitas yang diperlukan terletak di sana dan jika tidak, terapkan.
Dante
4
Struktur proyek menjadi lebih penting. Jika Anda menempatkan semua kueri di namespace seperti YourProject.YourDomainModelName.Queriesitu akan mudah dinavigasi.
jgauffin
Satu hal yang sulit saya lakukan dengan CQS adalah pertanyaan umum. Misalnya, diberikan pengguna, satu kueri mendapatkan semua siswa terkait (nilai yang berbeda, memiliki anak, dll.). Sekarang permintaan lain perlu mendapatkan anak-anak untuk pengguna dan kemudian melakukan beberapa operasi pada mereka - sehingga permintaan 2 dapat memanfaatkan logika umum dalam permintaan 1 tetapi tidak ada cara sederhana untuk melakukan itu. Saya belum dapat menemukan implementasi CQS yang memfasilitasi ini dengan mudah. Repositori banyak membantu dalam hal ini. Bukan penggemar kedua pola tetapi hanya berbagi pendapat saya.
Mrchief
8

Pertama yang saya katakan adalah ORM sudah menjadi abstraksi yang cukup besar atas database Anda. Beberapa ORM menyediakan pengikatan ke semua database relasional yang umum (NHibernate memiliki binding ke MSSQL, Oracle, MySQL, Postgress, dll.) Jadi membangun abstraksi baru di atasnya sepertinya tidak menguntungkan bagi saya. Juga, dengan alasan, bahwa Anda perlu "memisahkan" ORM ini tidak ada artinya.

Jika Anda masih ingin membangun abstraksi ini, saya akan menentang pola Repositori. Itu hebat di zaman SQL murni, tetapi cukup merepotkan dalam menghadapi ORM modern. Terutama karena Anda akhirnya menerapkan kembali sebagian besar fitur ORM, seperti operasi CRUD dan permintaan.

Jika saya membangun abstraksi semacam itu, saya akan menggunakan aturan / pola ini

  • CQRS
  • Gunakan sebanyak mungkin fitur ORM yang ada
  • Meringkas hanya logika dan kueri yang kompleks
  • Cobalah untuk memiliki "plumbing" dari ORM yang tersembunyi di dalam arsitektur

Dalam implementasi konkret, saya akan menggunakan operasi CRUD ORM secara langsung, tanpa pembungkus apa pun. Saya juga akan melakukan query sederhana langsung dalam kode. Tetapi pertanyaan kompleks akan diringkas dalam objek mereka sendiri. Untuk "menyembunyikan" ORM, saya akan mencoba menyuntikkan konteks data secara transparan ke dalam objek layanan / UI dan telah melakukan hal yang sama pada objek kueri.

Hal terakhir yang ingin saya katakan, adalah bahwa banyak orang menggunakan ORM tanpa mengetahui cara menggunakannya dan bagaimana menghasilkan "keuntungan" terbaik darinya. Orang merekomendasikan repositori biasanya dari jenis ini.

Sebagai bacaan yang direkomendasikan, saya akan mengatakan blog Ayende , terutama artikel ini .

Euforia
sumber
+1 "Hal terakhir yang ingin saya katakan, adalah bahwa banyak orang menggunakan ORM tanpa mengetahui cara menggunakannya dan bagaimana menghasilkan" keuntungan "terbaik darinya. Orang merekomendasikan repositori yang biasanya dari jenis ini"
Misters
1

Masalah dengan implementasi bocor adalah bahwa Anda memerlukan banyak kondisi filter yang berbeda.

Anda dapat mempersempit api ini jika Anda menerapkan metode repositori FindByExample dan menggunakannya seperti ini

// find all retired persons
Person filter = new Person {Status=PersonStatus.Retired};
IEnumerable<Person> found = personRepository.FindByExample(filter);


// find all persons who have a dog named "sam"
Person filter = new Person();
filter.AddPet(new Dog{Name="sam"});
IEnumerable<Person> found = personRepository.FindByExample(filter);
k3b
sumber
0

Pertimbangkan untuk membuat ekstensi Anda beroperasi pada IQueryable alih-alih IRepository.

public static class PersonExtensions
{
    public static IQueryable<Person> AreRetired(this IQueryable<Person> people)
    {
        return people.Where(p => p.Status == "Retired");
    }
}

Untuk unit test:

List<Person> people = new List<Person>();
people.Add(new Person() { Name = "Bob", Status = "Retired" });
people.Add(new Person() { Name = "Sam", Status = "Working" });

var retiredPeople = people.AsQueryable().AreRetired();
// assert that Bob is in the list
// assert that Sam is not in the list

Tidak diperlukan cemoohan pembiayaan untuk pengujian. Anda juga dapat menggabungkan metode ekstensi ini untuk membuat kueri yang lebih kompleks sesuai kebutuhan.

Jared S
sumber
5
-1 a) IQueryableadalah ibu yang buruk, atau lebih tepatnya antarmuka ALLAH. Menerapkannya sangat dimiliki dan ada sangat sedikit (jika ada) LINQ lengkap untuk penyedia Sql. b) Metode penyuluhan membuat penyuluhan (salah satu prinsip OOP dasar) tidak mungkin.
jgauffin
0

Saya menulis pola objek permintaan yang cukup rapi untuk NHibernate di sini: https://github.com/shaynevanasperen/NHibernate.Sessions.Operations

Ini bekerja dengan menggunakan antarmuka seperti ini:

public interface IDatabases
{
    ISessionManager SessionManager { get; }

    T Query<T>(IDatabaseQuery<T> query);
    T Query<T>(ICachedDatabaseQuery<T> query);

    void Command(IDatabaseCommand command);
    T Command<T>(IDatabaseCommand<T> command);
}

Diberikan kelas entitas POCO seperti ini:

class Database1Poco
{
    public int Property1 { get; set; }
    public string Property2 { get; set; }
}

Anda dapat membuat objek permintaan seperti ini:

class Database1PocoByProperty1 : DatabaseQuery<Database1Poco>
{
    public override Database1Poco Execute(ISessionManager sessionManager)
    {
        return sessionManager.Session.Query<Database1Poco>().SingleOrDefault(x => x.Property1 == Property1);
    }

    public int Property1 { get; set; }
}

Dan kemudian gunakan seperti ini:

var database1Poco = _databases.Query(new Database1PocoByProperty1 { Property1 = 1 });
Shayne
sumber
1
Meskipun tautan ini dapat menjawab pertanyaan, lebih baik untuk memasukkan bagian-bagian penting dari jawaban di sini dan memberikan tautan untuk referensi. Jawaban hanya tautan dapat menjadi tidak valid jika halaman tertaut berubah.
Dan Pichelman
Saya minta maaf, saya sedang terburu-buru. Jawaban diperbarui untuk menampilkan beberapa kode contoh.
Shayne
1
+1 untuk meningkatkan pos dengan lebih banyak konten.
Songo