Entity Framework Entities - Beberapa Data Dari Layanan Web - Arsitektur Terbaik?

10

Kami saat ini menggunakan Entity Framework sebagai ORM di beberapa aplikasi web, dan sampai sekarang, itu cocok untuk kami karena semua data kami disimpan dalam satu basis data. Kami menggunakan pola repositori, dan memiliki layanan (lapisan domain) yang menggunakannya, dan mengembalikan entitas EF langsung ke pengontrol ASP.NET MVC.

Namun, suatu persyaratan telah muncul untuk menggunakan API pihak ke-3 (melalui layanan web) yang akan memberi kami informasi tambahan yang berkaitan dengan pengguna dalam basis data kami. Di basis data Pengguna lokal kami, kami akan menyimpan ID eksternal yang dapat kami berikan kepada API untuk mendapatkan informasi tambahan. Ada sedikit informasi yang tersedia, tetapi demi kesederhanaan, salah satunya terkait dengan perusahaan pengguna (nama, manajer, kamar, jabatan, lokasi, dll). Informasi ini akan digunakan di berbagai tempat di seluruh aplikasi web kami - bukan hanya digunakan di satu tempat.

Jadi pertanyaan saya adalah, di mana tempat terbaik untuk mengisi dan mengakses informasi ini? Karena digunakan di berbagai tempat, tidaklah masuk akal untuk mengambilnya secara ad-hoc di mana pun kami menggunakan aplikasi web - jadi masuk akal untuk mengembalikan data tambahan ini dari lapisan domain.

Pikiran awal saya adalah hanya untuk membuat kelas model pembungkus yang akan berisi entitas EF (EFUser), dan kelas 'ApiUser' baru yang berisi informasi baru - dan ketika kami mendapatkan pengguna, kami mendapatkan EFUser, dan kemudian mendapatkan tambahan info dari API, dan isi objek ApiUser. Namun, sementara ini tidak masalah untuk mendapatkan pengguna tunggal, itu jatuh ketika mendapatkan beberapa pengguna. Kami tidak dapat menekan API ketika mendapatkan daftar pengguna.

Pikiran kedua saya adalah hanya menambahkan metode tunggal ke entitas EFUser yang mengembalikan ApiUser, dan hanya mengisinya ketika diperlukan. Ini menyelesaikan masalah di atas karena kami hanya mengaksesnya saat kami membutuhkannya.

Atau pemikiran terakhir adalah menyimpan salinan data lokal di dalam basis data kami, dan menyinkronkannya dengan API ketika pengguna masuk. Ini adalah pekerjaan minimal karena ini hanya proses sinkronisasi - dan kami tidak memiliki overhead untuk memukul DB dan API setiap kali kami ingin mendapatkan informasi pengguna. Namun, ini berarti menyimpan data di dua tempat, dan juga berarti data tersebut sudah usang bagi setiap pengguna yang belum masuk untuk sementara waktu.

Adakah yang punya saran atau saran tentang cara terbaik untuk menangani skenario semacam ini?

stevehayter
sumber
it's not really sensible to fetch it on an ad-hoc basis- Kenapa? Untuk alasan kinerja?
Robert Harvey
Maksud saya tidak memukul API berdasarkan ad-hoc - Maksud saya menjaga struktur entitas yang ada sebagaimana adanya, dan kemudian memanggil API ad-hoc di aplikasi web bila diperlukan - Saya hanya bermaksud ini tidak akan masuk akal karena perlu dilakukan di banyak tempat.
stevehayter

Jawaban:

3

Kasus Anda

Dalam kasus Anda, ketiga opsi tersebut layak. Saya pikir pilihan terbaik mungkin untuk menyinkronkan sumber data Anda di suatu tempat di mana aplikasi asp.net bahkan tidak menyadarinya. Artinya, hindari dua pengambilan di latar depan setiap kali, sinkronkan API dengan db secara diam-diam). Jadi jika itu pilihan yang layak dalam kasus Anda - saya katakan lakukan itu.

Sebuah solusi di mana Anda membuat pengambilan 'sekali' seperti jawaban lain menyarankan sepertinya tidak terlalu layak karena tidak bertahan respon di mana saja dan ASP.NET MVC hanya akan membuat pengambilan untuk setiap permintaan berulang kali.

Saya akan menghindari singleton, saya pikir itu bukan ide yang baik untuk banyak alasan yang biasa.

Jika opsi ketiga tidak dapat dijalankan - satu pilihan adalah malas memuatnya. Yaitu, minta kelas untuk memperluas entitas, dan tekan API sesuai kebutuhan . Itu abstraksi yang sangat berbahaya karena itu bahkan lebih ajaib dan tidak jelas.

Saya kira itu benar-benar bermuara pada beberapa pertanyaan:

  • Seberapa sering perubahan data panggilan API? Tidak sering? Opsi ketiga. Sering? Tiba-tiba opsi ketiga tidak terlalu layak. Saya tidak yakin saya menentang panggilan ad-hoc seperti Anda.
  • Seberapa mahal panggilan API? Apakah Anda membayar per panggilan? Apakah mereka cepat? Gratis? Jika mereka cepat, membuat panggilan setiap kali mungkin berhasil, jika lambat, Anda harus memiliki semacam prediksi dan membuat panggilan. Jika mereka membutuhkan uang - itu adalah insentif besar untuk caching.
  • Seberapa cepat waktu respon harus? Jelas lebih cepat lebih baik, tetapi mengorbankan kecepatan untuk kesederhanaan mungkin layak dilakukan dalam beberapa kasus, terutama jika tidak langsung berhadapan dengan pengguna.
  • Seberapa berbeda data API dari data Anda? Apakah mereka dua hal yang berbeda secara konseptual? Jika demikian, mungkin lebih baik jika hanya mengekspos API di luar daripada mengembalikan hasil API dengan hasilnya secara langsung dan membiarkan pihak lain melakukan panggilan kedua dan menanganinya.

Satu atau dua kata tentang pemisahan masalah

Izinkan saya untuk membantah apa yang dikatakan Bobson tentang pemisahan masalah di sini. Pada akhirnya - menempatkan logika dalam entitas seperti itu melanggar pemisahan kekhawatiran sama buruknya.

Memiliki repositori seperti itu melanggar pemisahan kekhawatiran sama buruknya dengan menempatkan logika sentris presentasi di lapisan logika bisnis. Repositori Anda tiba-tiba menyadari hal-hal yang berhubungan dengan presentasi seperti bagaimana Anda menampilkan pengguna di pengontrol MVC asp.net Anda.

Dalam pertanyaan terkait ini saya telah bertanya tentang mengakses entitas langsung dari pengontrol. Izinkan saya mengutip salah satu jawaban di sana:

"Selamat datang di BigPizza, toko Pizza khusus, bolehkah saya menerima pesanan Anda?" "Yah, aku ingin makan Pizza dengan zaitun, tetapi saus tomat di atasnya dan keju di bagian bawah dan panggang dalam oven selama 90 menit sampai hitam dan keras seperti batu granit yang rata." "Oke, Tuan, Pizza khusus adalah profesi kami, kami akan berhasil."

Kasir pergi ke dapur. "Ada psiko di konter, dia ingin makan Pizza dengan ... itu batu granit dengan ... tunggu ... kita harus punya nama dulu", katanya kepada si juru masak.

"Tidak!", Si juru masak berteriak, "tidak lagi! Kamu tahu kita sudah mencobanya." Dia mengambil setumpuk kertas dengan 400 halaman, "di sini kita memiliki batu granit dari tahun 2005, tetapi ... tidak memiliki zaitun, tetapi paprica sebagai gantinya ... atau di sini adalah tomat top ... tetapi pelanggan menginginkannya dipanggang hanya setengah menit. " "Mungkin kita harus menyebutnya TopTomatoGraniteRockSpecial?" "Tapi itu tidak memperhitungkan keju di bagian bawah ..." Kasir: "Seharusnya yang diungkapkan Special." "Tetapi memiliki batu Pizza yang dibentuk seperti piramida akan menjadi istimewa juga", jawab si juru masak. "Hmmm ... sulit ...", kata kasir putus asa itu.

"APAKAH PIZZA SAYA SUDAH BERADA DI OVEN?", Tiba-tiba ia berteriak melalui pintu dapur. "Mari kita hentikan diskusi ini, katakan saja padaku bagaimana membuat Pizza ini, kita tidak akan memiliki Pizza untuk kedua kalinya", si juru masak memutuskan. "OK, ini Pizza dengan zaitun, tetapi saus tomat di atasnya dan keju di bagian bawah dan panggang dalam oven selama 90 menit sampai hitam dan keras seperti batu granit yang rata."

(Baca sisa jawabannya, ini imo yang sangat bagus).

Adalah naif untuk mengabaikan fakta bahwa ada basis data - ada basis data, dan sekeras apa pun Anda ingin mengabstraksikannya, ia tidak ke mana-mana. Aplikasi Anda akan mengetahui sumber data. Anda tidak akan bisa 'menukar hot itu'. ORM berguna tetapi mereka bocor karena betapa rumitnya masalah yang mereka selesaikan dan karena banyak alasan kinerja (Seperti Pilih n + 1 misalnya).

Benjamin Gruenbaum
sumber
Terima kasih atas tanggapan Anda yang sangat teliti @Benjamin. Saya awalnya mulai membuat prototipe menggunakan solusi Bobson di atas (bahkan sebelum dia memposting jawabannya), tetapi Anda meningkatkan beberapa poin penting. Untuk menjawab pertanyaan Anda ,: - Panggilan API tidak terlalu mahal (gratis, dan juga cepat). - Beberapa bagian data akan berubah secara teratur (beberapa bahkan setiap beberapa jam). - Kecepatan cukup penting, tetapi pemirsa aplikasi sedemikian rupa sehingga pemuatan yang cepat bukanlah persyaratan mutlak.
stevehayter
@stevehayter Dalam hal ini saya kemungkinan besar akan melakukan panggilan ke API dari sisi klien. Lebih murah dan lebih cepat, dan memberi Anda kontrol berbutir yang lebih halus.
Benjamin Gruenbaum
1
Berdasarkan jawaban ini, saya kurang condong ke arah menyimpan salinan data lokal. Saya sebenarnya condong ke arah mengekspos API secara terpisah, dan menangani data tambahan seperti itu. Saya pikir ini mungkin kompromi yang baik antara kesederhanaan solusi @ Bobson, tetapi juga menambahkan tingkat pemisahan yang saya sedikit lebih nyaman. Saya akan melihat strategi ini dalam prototipe saya, dan melaporkan kembali dengan temuan saya (dan memberikan hadiahnya!).
stevehayter
@BenjaminGruenbaum - Saya tidak yakin saya mengikuti argumen Anda. Bagaimana saran saya membuat repositori mengetahui presentasi? Tentu, ini mengetahui bahwa bidang yang didukung API telah diakses, tetapi itu tidak ada hubungannya dengan apa yang dilakukan oleh pandangan dengan informasi itu.
Bobson
1
Saya memilih untuk memindahkan semuanya ke sisi klien - tetapi sebagai metode ekstensi pada EFUser (yang ada di lapisan presentasi, dalam rakitan terpisah). Metode ini hanya mengembalikan data dari API, dan menetapkan singleton sehingga tidak terkena berulang kali. Masa pakai objek sangat singkat sehingga saya tidak punya masalah menggunakan singleton di sini. Dengan cara ini ada beberapa tingkat pemisahan, tetapi saya masih mendapatkan kenyamanan bekerja dengan entitas EFUser. Terima kasih kepada semua responden atas bantuan mereka. Pasti diskusi yang menarik :).
stevehayter
2

Dengan pemisahan kekhawatiran yang tepat , tidak ada yang di atas tingkat Kerangka Entitas / API yang seharusnya menyadari dari mana data berasal. Kecuali panggilan API itu mahal (dalam hal waktu atau pemrosesan), mengakses data yang menggunakannya harus transparan seperti mengakses data dari database.

Maka, cara terbaik untuk mengimplementasikan ini adalah dengan menambahkan properti tambahan ke EFUserobjek yang malas memuat data API sesuai kebutuhan. Sesuatu seperti ini:

partial class EFUser
{
    private APIUser _apiUser;
    private APIUser ApiUser
    {
       get { 
          if (_apiUser == null) _apiUser = API.LoadUser(this.ExternalID);
          return _apiUser;
       }
    }
    public string CompanyName { get { return ApiUser.UserCompanyName; } }
    public string JobTitle{ get { return ApiUser.UserJobTitle; } }
}

Secara eksternal, pertama kali salah satu CompanyNameatau JobTitledigunakan akan ada panggilan API tunggal dibuat (dan dengan demikian penundaan kecil), tetapi semua panggilan berikutnya sampai objek dihancurkan akan sama cepat dan mudahnya dengan akses basis data.

Bobson
sumber
Terima kasih @ Bobson ... ini sebenarnya rute awal yang saya mulai turun (dengan beberapa metode ekstensi ditambahkan ke bulk memuat rincian untuk daftar pengguna - misalnya, menampilkan nama perusahaan untuk daftar pengguna). Tampaknya sesuai dengan kebutuhan saya sejauh ini - tetapi Benjamin di bawah ini memunculkan beberapa poin penting, jadi saya akan terus mengevaluasi minggu ini.
stevehayter
0

Satu ide adalah memodifikasi ApiUser untuk tidak selalu memiliki info tambahan. Sebagai gantinya, Anda meletakkan metode pada ApiUser untuk mengambilnya:

ApiUser apiUser = backend.load($id);
//Locally available data directly on the ApiUser like so:
String name = apiUser.getName();
//Expensive extra data available after extra call:
UserExtraData extraData = apiUser.fetchExtraData();
String managerName = extraData.getManagerName();

Anda juga dapat sedikit memodifikasi ini untuk menggunakan pemuatan data tambahan yang malas, sehingga Anda tidak perlu mengekstrak UserExtraData dari objek ApiUser:

//Extra data fetched on first get:
String managerName = apiUser.lazyGetExtraData().getManagerName();

Dengan cara ini, ketika Anda memiliki daftar, data tambahan tidak akan diambil secara default. Tetapi Anda masih dapat mengaksesnya sambil melintasi daftar!

Alexander Torstling
sumber
Tidak begitu yakin apa yang Anda maksud di sini - di backend.load (), kami sudah melakukan pemuatan - jadi pasti itu akan memuat data API?
stevehayter
Maksud saya, Anda harus menunggu melakukan pemuatan tambahan hingga diminta secara eksplisit - malas memuat data api.
Alexander Torstling