Strategi untuk menghindari SQL di Pengontrol Anda ... atau berapa banyak metode yang harus saya miliki dalam Model saya?


17

Jadi situasi yang sering saya temui adalah situasi di mana model saya mulai:

  • Tumbuh menjadi monster dengan berton-ton metode

ATAU

  • Memungkinkan Anda mengirimkan potongan-potongan SQL kepada mereka, sehingga mereka cukup fleksibel untuk tidak memerlukan jutaan metode berbeda

Misalnya, kita memiliki model "widget". Kami mulai dengan beberapa metode dasar:

  • dapatkan ($ id)
  • masukkan ($ record)
  • perbarui ($ id, $ record)
  • hapus ($ id)
  • getList () // dapatkan daftar Widget

Semua baik-baik saja dan keren, tetapi kemudian kita perlu pelaporan:

  • listCreatedBetween ($ start_date, $ end_date)
  • listPurchasedBetween ($ start_date, $ end_date)
  • listOfPending ()

Dan kemudian pelaporan mulai menjadi rumit:

  • listPendingCreatedBetween ($ start_date, $ end_date)
  • listForCustomer ($ customer_id)
  • listPendingCreatedBetweenForCustomer ($ customer_id, $ start_date, $ end_date)

Anda dapat melihat di mana ini berkembang ... pada akhirnya kami memiliki begitu banyak persyaratan kueri khusus sehingga saya perlu menerapkan berton-ton metode, atau semacam objek "kueri" yang dapat saya sampaikan ke satu -> kueri (kueri) metode $ query) ...

... atau cukup gigit peluru, dan mulailah melakukan sesuatu seperti ini:

  • list = MyModel-> query ("start_date> X AND end_date <Y AND pending = 1 AND customer_id = Z")

Ada daya tarik tertentu untuk hanya memiliki satu metode seperti itu daripada 50 juta metode lain yang lebih spesifik ... tapi kadang-kadang terasa "salah" untuk memasukkan setumpuk apa yang pada dasarnya SQL ke dalam pengontrol.

Apakah ada cara "benar" untuk menangani situasi seperti ini? Apakah dapat diterima untuk memasukkan pertanyaan seperti itu ke dalam metode generik -> kueri ()?

Apakah ada strategi yang lebih baik?


Saya sedang mengalami masalah yang sama sekarang di proyek non-MVC. Pertanyaannya terus muncul, apakah lapisan akses data abstrak keluar dari setiap prosedur yang tersimpan, dan biarkan database lapisan logika bisnis agnostik, atau haruskah lapisan akses data bersifat generik, dengan biaya lapisan bisnis mengetahui sesuatu tentang basis data yang mendasarinya? Mungkin solusi perantara adalah memiliki sesuatu seperti ExecuteSP (string spName, parameter objek params []), kemudian sertakan semua nama SP dalam file konfigurasi untuk dibaca oleh lapisan bisnis. Saya tidak memiliki jawaban yang sangat bagus untuk ini.
Greg Jackson

Jawaban:


10

Pola Arsitektur Aplikasi Enterprise Martin Fowler menjelaskan sejumlah pola ORM terkait, termasuk penggunaan Obyek Kueri, yang merupakan apa yang saya sarankan.

Objek kueri memungkinkan Anda mengikuti prinsip Tanggung Jawab Tunggal, dengan memisahkan logika untuk setiap kueri menjadi objek strategi yang dikelola dan dikelola secara individual. Pengontrol Anda dapat mengatur penggunaannya secara langsung, atau mendelegasikannya ke pengontrol sekunder atau objek bantuan.

Apakah Anda akan memiliki banyak dari mereka? Pasti. Bisakah beberapa dikelompokkan ke dalam pertanyaan umum? Ya lagi

Bisakah Anda menggunakan injeksi ketergantungan untuk membuat objek dari metadata? Itulah yang dilakukan sebagian besar alat ORM.


4

Tidak ada cara yang benar untuk melakukan ini. Banyak orang menggunakan ORM untuk mengabstraksi semua kerumitan. Beberapa ORM yang lebih maju menerjemahkan ekspresi kode menjadi pernyataan SQL yang rumit. ORM memiliki kerugian juga, namun untuk banyak aplikasi manfaatnya lebih besar daripada biaya.

Jika Anda tidak bekerja dengan dataset besar, hal paling sederhana yang harus dilakukan adalah memilih seluruh tabel ke dalam memori dan memfilter dalam kode.

//pseudocode
List<Person> people = Sql.GetList<Person>("select * from people");
List<Person> over21 = people.Where(x => x.Age >= 21);

Untuk aplikasi pelaporan internal, pendekatan ini mungkin baik. Jika dataset sangat besar, Anda akan mulai membutuhkan banyak metode khusus serta indeks yang sesuai di tabel Anda.


1
+1 untuk "Tidak ada cara yang benar untuk melakukan ini"
ozz

1
Sayangnya, pemfilteran di luar kumpulan data tidak benar-benar suatu pilihan bahkan dengan set data terkecil yang bekerja dengan kami - itu terlalu lambat. :-( Senang mendengar bahwa orang lain mengalami masalah yang sama. :-)
Keith Palmer Jr

@KeithPalmer karena penasaran, seberapa besar meja Anda?
dan

Ratusan ribu baris, jika tidak lebih. Terlalu banyak untuk disaring dengan kinerja yang dapat diterima di luar basis data, TERUTAMA dengan arsitektur terdistribusi di mana basis data tidak pada mesin yang sama dengan aplikasi.
Keith Palmer Jr

-1 untuk "Tidak ada cara yang benar untuk melakukan ini". Ada beberapa cara yang benar. Menggandakan jumlah metode ketika Anda menambahkan fitur seperti OP lakukan adalah pendekatan yang tidak dapat diubah, dan alternatif yang disarankan di sini sama-sama tidak dapat diukur, hanya berkaitan dengan ukuran basis data daripada jumlah fitur permintaan. Pendekatan scalable memang ada, lihat jawaban lainnya.
Theodore Murdock

4

Beberapa ORM memungkinkan Anda untuk membangun kueri kompleks mulai dari metode dasar. Contohnya

old_purchases = (Purchase.objects
    .filter(date__lt=date.today(),type=Purchase.PRESENT).
    .excude(status=Purchase.REJECTED)
    .order_by('customer'))

adalah kueri yang benar-benar valid dalam Django ORM .

Idenya adalah bahwa Anda memiliki beberapa pembuat kueri (dalam hal ini Purchase.objects) yang status internnya mewakili informasi tentang kueri. Metode seperti get, filter, exclude, order_byadalah valid dan kembali pembangun query yang baru dengan status update. Objek-objek ini mengimplementasikan antarmuka iterable, sehingga ketika Anda mengulanginya, query dilakukan dan Anda mendapatkan hasil dari query yang dibangun sejauh ini. Meskipun contoh ini diambil dari Django, Anda akan melihat struktur yang sama di banyak ORM lainnya.


Saya tidak melihat keuntungan apa ini dibandingkan old_purchases = Purchases.query ("date> date.today () AND type = Purchase.PRESENT AND status! = Purchase.REJECTED"); Anda tidak mengurangi kerumitan atau mengabstraksikan apa pun hanya dengan membuat SQL AND dan OR menjadi metode AND dan OR - Anda hanya mengubah representasi AND dan OR, bukan?
Keith Palmer Jr

4
Sebenarnya tidak. Anda mengabstraksi SQL, yang memberi Anda banyak keuntungan. Pertama, Anda menghindari injeksi. Kemudian, Anda dapat mengubah database yang mendasarinya tanpa khawatir tentang versi dialek SQL yang sedikit berbeda, karena ORM yang menangani ini untuk Anda. Dalam banyak kasus, Anda juga dapat meletakkan backend NoSQL tanpa memperhatikan. Ketiga, pembuat kueri ini adalah objek yang bisa Anda bagikan seperti yang lainnya. Ini berarti bahwa model Anda dapat membuat setengah kueri (misalnya Anda dapat memiliki beberapa metode untuk kasus yang paling umum) dan kemudian dapat disempurnakan dalam controller untuk menangani ..
Andrea

2
... kebanyakan kasus spesifik. Contoh tipikal adalah menentukan pemesanan standar untuk model di Django. Semua hasil permintaan akan mengikuti pemesanan itu kecuali jika Anda menentukan sebaliknya. Keempat, jika Anda perlu mendenormalisasi data Anda untuk alasan kinerja, Anda hanya perlu mengubah ORM daripada menulis ulang semua pertanyaan Anda.
Andrea

+1 Untuk bahasa kueri dinamis seperti yang disebutkan, dan LINQ.
Evan Plaice

2

Ada pendekatan ketiga.

Contoh spesifik Anda menunjukkan pertumbuhan eksponensial dalam jumlah metode yang diperlukan ketika jumlah fitur yang diperlukan bertambah: kami ingin kemampuan untuk menawarkan kueri tingkat lanjut, menggabungkan setiap fitur permintaan ... jika kami melakukannya dengan menambahkan metode, kami memiliki satu metode untuk permintaan dasar, dua jika kita menambahkan satu fitur opsional, empat jika kita menambahkan dua, delapan jika kita menambahkan tiga, 2 ^ n jika kita menambahkan fitur n.

Itu jelas tidak dapat dipertahankan di luar tiga atau empat fitur, dan ada bau buruk dari banyak kode terkait erat yang hampir disalin-disisipkan antara metode.

Anda bisa menghindarinya dengan menambahkan objek data untuk menyimpan parameter, dan memiliki satu metode yang membangun kueri berdasarkan set parameter yang disediakan (atau tidak disediakan). Dalam hal itu, menambahkan fitur baru seperti rentang tanggal semudah menambahkan setter dan getter untuk rentang tanggal ke objek data Anda, dan kemudian menambahkan sedikit kode tempat kueri parameterisasi dibuat:

if (dataObject.getStartDate() != null) {
    query += " AND (date BETWEEN ? AND ?) "
}

... dan tempat parameter ditambahkan ke kueri:

if (dataObject.getStartDate() != null) {
    preparedStatement.setTime(dataObject.getStartDate());
    preparedStatement.setTime(dataObject.getEndDate());
}

Pendekatan ini memungkinkan untuk pertumbuhan kode linier ketika fitur ditambahkan, tanpa harus mengizinkan permintaan sewenang-wenang dan tidak diparameterisasi.


0

Saya pikir konsensus umum adalah untuk menjaga akses data sebanyak mungkin dalam model Anda di MVC. Salah satu prinsip desain lainnya adalah memindahkan beberapa pertanyaan Anda yang lebih umum (Pertanyaan yang tidak terkait langsung dengan model Anda) ke tingkat yang lebih tinggi dan lebih abstrak di mana Anda dapat mengizinkannya digunakan oleh model lain juga. (Dalam RoR, kami memiliki sesuatu yang disebut framework). Ada juga hal lain yang harus Anda pertimbangkan dan itu adalah pemeliharaan kode Anda. Seiring proyek Anda tumbuh, jika Anda memiliki akses data di pengontrol, akan menjadi semakin sulit untuk melacaknya (Kami saat ini menghadapi masalah ini dalam proyek besar) Model, meskipun berantakan dengan metode memberikan satu titik kontak untuk setiap pengontrol yang mungkin berakhir quering dari tabel. (Ini juga dapat menyebabkan penggunaan kembali kode yang pada gilirannya bermanfaat)


1
Contoh apa yang Anda bicarakan ...?
Keith Palmer Jr

0

Antarmuka lapisan layanan Anda mungkin memiliki banyak metode, tetapi panggilan ke database hanya dapat memiliki satu metode.

Database memiliki 4 operasi besar

  • Memasukkan
  • Memperbarui
  • Menghapus
  • Pertanyaan

Metode opsional lain mungkin untuk menjalankan beberapa operasi database yang tidak termasuk dalam operasi DB dasar. Mari kita sebut Jalankan itu.

Sisipkan dan Pembaruan dapat digabungkan menjadi satu operasi, yang disebut Simpan.

Banyak metode Anda adalah kueri. Jadi, Anda dapat membuat antarmuka umum untuk memenuhi sebagian besar kebutuhan mendesak. Berikut ini contoh antarmuka umum:

 public interface IDALService
    {
        DataTransferObject<T> Save<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Search<T>(DataTransferObject<T> Dto) where T: IPOCO;
        DataTransferObject<T> Delete<T>(DataTransferObject<T> Dto) where T : IPOCO;
        DataTransferObject<T> Execute<T>(DataTransferObject<T> Dto) where T : IPOCO;
    }

Objek transfer data bersifat umum dan akan memiliki semua filter, parameter, penyortiran, dll. Yang terkandung di dalamnya. Lapisan data akan bertanggung jawab untuk mem-parsing dan mengekstraksi ini dan mengatur operasi ke database melalui prosedur yang tersimpan, parameter sql, LINQ dll. Jadi, SQL tidak melewati antar lapisan. Ini biasanya apa yang dilakukan ORM, tetapi Anda dapat memutar sendiri dan memiliki pemetaan sendiri.

Jadi, dalam kasus Anda, Anda memiliki Widget. Widget akan mengimplementasikan antarmuka IPOCO.

Jadi, dalam model lapisan layanan Anda akan melakukannya getList().

Butuh lapisan pemetaan untuk menangani tranforming getListke

Search<Widget>(DataTransferObject<Widget> Dto)

dan sebaliknya. Seperti yang telah disebutkan, kadang-kadang hal ini dilakukan melalui ORM, tetapi pada akhirnya Anda berakhir dengan banyak jenis kode boilerplate, terutama jika Anda memiliki 100-an tabel. ORM secara ajaib membuat parameter SQL dan menjalankannya terhadap basis data. Jika menggulirkan milik Anda sendiri, juga di lapisan data itu sendiri, pemetaan akan diperlukan untuk mengatur SP, LINQ dll. (Pada dasarnya sql pergi ke database).

Seperti yang disebutkan sebelumnya, DTO adalah objek yang dibuat oleh komposisi. Mungkin salah satu objek yang terkandung di dalamnya adalah objek yang disebut QueryParameters. Ini akan menjadi semua parameter untuk kueri yang akan diatur dan digunakan oleh kueri. Objek lain akan menjadi Daftar objek yang dikembalikan dari querys, pembaruan, ext. Ini payloadnya. Jika hal ini payload akan menjadi Daftar Daftar widget.

Jadi, strategi dasarnya adalah:

  • Panggilan Lapisan Layanan
  • Transform Service Layer Call To Database menggunakan semacam Repositori / Pemetaan
  • Panggilan Database

Dalam kasus Anda, saya pikir model dapat memiliki banyak metode, tetapi secara optimal Anda ingin panggilan database menjadi generik. Anda masih berakhir dengan banyak kode pemetaan boilerplate (terutama dengan SP) atau kode ORM ajaib yang secara dinamis membuat SQL parametisasi untuk Anda.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.