TDataSetProxy adalah komponen pembungkus untuk komponen dataset Delphi klasik. Ini memungkinkan untuk mengganti kumpulan data apa pun dengan kumpulan data palsu (tabel dalam memori). Proxy dapat digunakan untuk memisahkan kelas bisnis dari kumpulan data, pemisahan ini berguna ketika kode bisnis perlu dimasukkan ke dalam rangkaian pengujian otomatis (pengujian unit).
Inspirasi . Ide didasarkan pada pola Proxy GoF dan pola Rekaman Aktif, yang didefinisikan oleh Martin Fowler dalam buku Patterns of Enterprise Application Architecture
Pola Proksi DataSet berguna selama ekstraksi logika bisnis. Hal ini bisa sangat berguna untuk meningkatkan proyek-proyek lama yang sangat terkait. Ketika kode produksi bergantung pada data SQL dan koneksi SQL, sangat sulit untuk menulis pengujian unit untuk kode tersebut.
Mengganti kumpulan data dengan proksi memperkenalkan tingkat abstraksi baru yang dapat memfasilitasi keduanya: kumpulan data SQL dalam kode produksi dan kumpulan data memori dalam proyek pengujian. Proxy memiliki antarmuka (daftar metode) yang sangat mirip dengan kumpulan data klasik, yang membantu memudahkan migrasi. Kumpulan data palsu akan memungkinkan untuk memverifikasi (menegaskan) kode produksi tanpa terhubung ke database.
DataSet Proxy bersama dengan dua proyek pendamping (DataSet Generator, Delphi Command Pattern) memberikan kesempatan kepada pengembang untuk memperkenalkan pengujian unit dengan pemfaktoran ulang yang aman.
Proksi kumpulan data adalah solusi sementara dan setelah mencakup kode dengan pengujian, teknisi dapat menerapkan pemfaktoran ulang yang lebih canggih: memisahkan kode atau membuatnya lebih dapat disusun dan digunakan kembali. Sebagai salah satu proksi pemfaktoran ulang ini, proksi dapat dengan aman digantikan oleh objek DAO atau struktur data model.
Bersama dengan kode dan peningkatan kualitas, pengembang akan belajar cara menulis kode yang lebih bersih atau cara menggunakan pendekatan pengujian terlebih dahulu dan bekerja lebih baik.
Proyek yang mendukung:
Proyek | Repo GitHub |
---|---|
Pembuat Kumpulan Data | https://github.com/bogdanpolak/dataset-generator |
Proyek mencakup kode sumber kelas dasar TDataSetProxy
dan dua jenis generator proksi yang berbeda:
src/Comp.Generator.DataProxy.pas
TDataSetProxy
tools/generator-app
Komponen TDataProxyGenerator
berguna ketika insinyur ingin membuat proksi untuk keluar dari kumpulan data dalam kode produksi. Ini adalah tugas mudah dua langkah: (1) menambahkan unit komponen ke bagian penggunaan, (2) menemukan kode menggunakan kumpulan data dan metode generator panggilan:
Kode produksi saat ini:
aBooksDataSet := fDBConnection.ConstructSQLDataSet(
aOwner, APPSQL_SelectBooks);
dbgridBooks.DataSource.Dataset := aBooksDataSet;
Kode generator yang disuntikkan:
TDataProxyGenerator.SaveToFile( ' ../../src/Proxy.Books ' ,
aBooksDataSet, ' TBookProxy ' );
Aplikasi Generator untuk FireDAC adalah alat alternatif yang sebagian besar dibuat untuk tujuan demo. Dalam prakteknya penggunaan alat ini kurang bermanfaat dibandingkan penggunaan generator komponen secara langsung. Aplikasi Generator didedikasikan untuk tujuan pembinaan dan pelatihan. Untuk informasi lebih lanjut, periksa: Aplikasi Generator untuk FireDAC - Panduan Pengguna.
type
TBookProxy = class (TDatasetProxy)
private
fISBN :TWideStringField;
fTitle :TWideStringField;
fReleseDate :TDateField;
fPages :TIntegerField;
fPrice :TBCDField;
protected
procedure ConnectFields ; override;
public
property ISBN :TWideStringField read fISBN;
property Title :TWideStringField read fTitle;
property ReleseDate :TDateField read fReleseDate;
property Pages :TIntegerField read fPages;
property Price :TBCDField read fPrice;
end ;
procedure TBookProxy.ConnectFields ;
begin
Assert(fDataSet.Fields.Count = 5 );
fISBN := fDataSet.FieldByName( ' ISBN ' );
fTitle := fDataSet.FieldByName( ' Title ' );
fReleseDate := fDataSet.FieldByName( ' ReleseDate ' );
fPages := fDataSet.FieldByName( ' Pages ' );
fPrice := fDataSet.FieldByName( ' Price ' );
end ;
Komponen DataSetProxy adalah kelas proxy, yang memiliki metode hampir sama dengan komponen TDataSet klasik. Pengembang dapat dengan mudah mengganti komponen DataSet apa pun dengan proksi ini dengan hanya menerapkan sedikit perubahan dan risiko rendah pada kode produksi. Dari sudut pandang kode produksi, perubahannya kecil dan tidak terlalu penting, tetapi dari sudut pandang pengujian, ini adalah perubahan mendasar, karena pengembang dapat mengkonfigurasi ulang proxy untuk menggunakan kumpulan data memori ringan.
Sebagian besar metode TDataSetProxy
hanyalah tiruan dari TDataSet satu kali. Anda dapat dengan mudah memperluas kumpulan metode ini dengan menambahkan metode yang hilang satu kali atau membuat metode unik baru. Metode proxy ini adalah: Append
, Edit
, Cancel
, Delete
, Close
, Post
, RecordCount
, First
, Last
, Eof
, Next
, Prior
, EnableControls
, DisableControls
, Locate
, Lookup
, Refresh
dan lain-lain. Dokumentasi dan penggunaan metode ini sama seperti dokumentasi Delphi standar untuk kelas TDataSet
.
Metode TDataSetProxy
lainnya dapat dibagi menjadi dua kelompok: metode penyiapan proxy (konfigurasi) dan metode pembantu proxy (memperluas fungsionalitas kumpulan data klasik).
procedure TDataModule1.OnCreate (Sender: TObject);
begin
fOrdersProxy := TOrdersProxy.Create(fOwner);
fOrdersDataSource := fOrdersProxy.ConstructDataSource;
end ;
procedure TDataModule1.InitOrders (aYear, aMonth: word);
begin
fOrdersProxy.WithFiredacSQL( FDConnection1,
' SELECT OrderID, CustomerID, OrderDate, Freight ' +
' FROM {id Orders} WHERE OrderDate between ' +
' :StartDate and :EndDate ' ,
[ GetMonthStart(aYear, aMonth),
GetMonthEnd(aYear, aMonth) ],
[ftDate, ftDate])
.Open;
fOrdersInitialized := True;
end ;
procedure TDataModule1.InitOrders (aDataSet: TDataSet);
begin
fOrdersProxy.WithDataSet(aDataSet).Open;
fOrdersInitialized := True;
end ;
Rilis komponen TDataSetProxy
saat ini hanya berisi satu metode pembantu yang diimplementasikan sebagai contoh. Pengembang dapat memperluas koleksi ini sesuai dengan praktik pengkodean tim. Disarankan untuk memperluas kelas proxy menggunakan warisan. Contoh penggunaan metode pembantu ForEach
yang ada:
function TDataModule.CalculateTotalOrders ( const aCustomerID: string): Currency;
begin
Result := 0 ;
fOrdersProxy.ForEach(procedure
begin
if fOrdersProxy.CustomerID. Value = aCustomerID then
Result := Result + fOrdersProxy.GetTotalOrderValue;
end ;
end ;
Comp.Generator.DataProxy.pas
SaveToFile
SaveToClipboard
Execute
Code
DataSet
GeneratorMode
DataSetAccess
FieldNamingStyle
NameOfUnit
NameOfClass
IndentationText
Pilihan | Nilai-nilai | Keterangan |
---|---|---|
GeneratorMode | ( pgmClass , pgmUnit ) | Hanya menghasilkan header kelas dan implementasi atau seluruh unit dengan kelas |
NameOfUnit | String | Nama unit yang dihasilkan digunakan untuk membuat header unit |
NameOfClass | String | Nama kelas proksi yang dihasilkan |
FieldNamingStyle | ( fnsUpperCaseF , fnsLowerCaseF ) | Memutuskan bagaimana bidang kelas diberi nama: menggunakan akhiran F huruf besar atau huruf kecil |
IndentationText | String | Teks digunakan untuk setiap lekukan kode, nilai defaultnya adalah dua spasi |
DataSetAccess | ( dsaNoAccess , dsaGenComment , dsaFullAccess ) | Mendefinisikan akses ke kumpulan data proksi internal: akses penuh = properti baca-saja dihasilkan untuk memiliki akses. Tidak ada opsi akses yang default dan direkomendasikan |
Untuk menghasilkan kelas poxy Anda dapat menggunakan metode Execute, tetapi sebelum memanggilnya Anda harus mengatur semua opsi dan properti DataSet
. Setelah memanggil Execute
kode yang dihasilkan akan disimpan di TStringList
internal yang dapat diakses melalui properti Code
. Lihat contoh kode di bawah ini:
aProxyGenerator:= TDataProxyGenerator.Create(Self);
try
aProxyGenerator.DataSet := fdqEmployees;
aProxyGenerator.NameOfUnit := ' Proxy.Employee ' ;
aProxyGenerator.NameOfClass := ' TEmployeeProxy ' ;
aProxyGenerator.IndentationText := ' ' ;
aProxyGenerator.Execute;
Memo1.Lines := aProxyGenerator.Code;
finally
aProxyGenerator.Free;
end ;
Cara yang lebih mudah dan ringkas untuk menghasilkan kelas proxy adalah dengan menggunakan metode kelas generator: SaveToFile
atau SaveToClipboard
. Namanya cukup bermakna untuk memahami fungsinya. SaveToFile menghasilkan seluruh unit dan menuliskannya ke dalam file dan SaveToClipboard hanya menghasilkan kelas dan menulis ke Windows Clipboard. Lihat contoh di bawah ini:
TDataProxyGenerator.SaveToFile(
' src/Proxy.Employee ' ,
fdqEmployees,
' TEmployeeProxy ' ,
' '
fnsLowerCaseF);
TDataProxyGenerator.SaveToClipboard(
fdqEmployees,
' TEmployeeProxy ' ,
' '
fnsLowerCaseF);
Proyek ini adalah hasil dari pengalaman bertahun-tahun dan banyak tim. Tim ini menemukan bahwa pendekatan Delphi berbasis peristiwa klasik tidak hanya kurang produktif, namun bahkan berbahaya bagi pengembang, manajer, dan pelanggan.
Bekerja dengan RDBMS (server SQL) di Delphi terlihat sangat produktif dan sederhana. Pengembang menjatuhkan komponen Query
, memasukkan perintah SQL, menetapkan properti Aktif, menghubungkan semua kontrol DB-aware ke query dan Anda selesai ... hampir selesai, hampir tetapi sebenarnya masih jauh dari siap untuk mengirimkan aplikasi.
Dengan menggunakan pola visual sederhana ini, pengembang dapat mengekspos dan memodifikasi data server SQL dengan sangat cepat. Kenyataannya, apa yang tampak sederhana pada awalnya, justru menjadi tantangan. Seiring berjalannya waktu, para insinyur membuat lebih banyak kumpulan data dan peristiwa, mendefragmentasi alur bisnis dan memadukan presentasi, konfigurasi, dan kode domain. Proyek menjadi semakin berantakan dan berpasangan. Setelah beberapa tahun, manajer dan pengembang kehilangan kendali atas proyek tersebut: rencana dan tenggat waktu tidak mungkin diukur, pelanggan berjuang dengan bug yang tidak terduga dan aneh, perubahan sederhana memerlukan banyak jam kerja.
Mengganti kumpulan data klasik dengan proxy memerlukan waktu untuk mempelajari dan memvalidasi tindakannya. Pendekatan ini mungkin terlihat sedikit aneh bagi pengembang Delphi, namun mudah untuk diadopsi dan dipelajari. Dengan motivasi manajemen dan tim pembinaan insinyur senior akan lebih cepat mengadopsi ekstraksi kode dan mengganti kumpulan data dengan teknik proxy.
Didefinisikan di sini pendekatan proxy adalah teknik refactoring sederhana dan aman yang didedikasikan untuk aplikasi VCL klasik yang dibangun dengan cara EDP (Event Driven Programming). Menggunakan solusi ini dalam evolusi, bagian kecil namun penting dari kode bisnis dapat diekstraksi dan ditutupi dengan pengujian unit. Setelah beberapa waktu, dengan jaring pengaman yang lebih baik (cakupan pengujian unit), para insinyur dapat menukar proxy dengan OOP DAO dan meningkatkan kode lebih lanjut menggunakan pemfaktoran ulang dan pola arsitektur tingkat lanjut.
Proses modernisasi meliputi langkah-langkah berikut:
Lihat contoh yang menunjukkan jalur migrasi proyek VCL lama menggunakan TDataSetProxy. Kita akan mulai dengan metode klasik yang didefinisikan dalam bentuk:
procedure TFormMain.LoadBooksToListBox ();
var
aIndex: integer;
aBookmark: TBookmark;
aBook: TBook;
isDatePrecise: boolean;
begin
ListBox1.ItemIndex := - 1 ;
for aIndex := 0 to ListBox1.Items.Count - 1 do
ListBox1.Items.Objects[aIndex].Free;
ListBox1.Clear;
aBookmark := fdqBook.GetBookmark;
try
fdqBook.DisableControls;
try
while not fdqBook.Eof do
begin
aBook := TBook.Create;
ListBox1.AddItem(fdqBook.FieldByName( ' ISBN ' ).AsString + ' - ' +
fdqBook.FieldByName( ' Title ' ).AsString, aBook);
aBook.ISBN := fdqBook.FieldByName( ' ISBN ' ).AsString;
aBook.Authors.AddRange(BuildAuhtorsList(
fdqBook.FieldByName( ' Authors ' ).AsString));
aBook.Title := fdqBook.FieldByName( ' Title ' ).AsString;
aBook.ReleaseDate := ConvertReleaseDate(
fdqBook.FieldByName( ' ReleaseDate ' ).AsString);
aBook.Price := fdqBook.FieldByName( ' Price ' ).AsCurrency;
aBook.PriceCurrency := fdqBook.FieldByName( ' Currency ' ).AsString;
ValidateCurrency(aBook.PriceCurrency);
fdqBook.Next;
end ;
finally
fdqBook.EnableControls;
end
finally
fdqBook.FreeBookmark(aBookmark);
end ;
end ;
Melihat! Solusi yang disajikan di atas adalah praktik yang buruk, namun sayangnya sering digunakan oleh pengembang Delphi. Tujuan penggunaan TDataProxy adalah untuk meningkatkan keadaan ini dan memisahkan logika bisnis dari visualisasi.
Metode ini memuat data dari database SQL, menggunakan fdqBook
TFDQuery. Objek kelas TBook
dibuat untuk setiap baris, bidangnya diisi dengan nilai kumpulan data dan divalidasi. Karena objek TBook
disimpan di kontrol TListBox
, yang juga memilikinya, metode ini harus melepaskannya terlebih dahulu.
Kami mengganti kumpulan data dengan objek proxy. Selain itu, kami memodernisasi kode dengan mengubah loop while-not-eof
klasik dengan metode ForEach
yang berfungsi. Pada saat yang sama, kami memperkenalkan varian yang lebih aman dalam mengakses nilai bidang. Fase ini dapat dipisahkan menjadi 3 fase terpisah, namun untuk artikel ini kita perlu menjaga konten tetap kompak.
procedure TFormMain.LoadBooksToListBox ();
var
aIndex: integer;
aBook: TBook;
begin
ListBox1.ItemIndex := - 1 ;
for aIndex := 0 to ListBox1.Items.Count - 1 do
ListBox1.Items.Objects[aIndex].Free;
ListBox1.Clear;
fProxyBooks.ForEach(
procedure
begin
aBook := TBook.Create;
ListBox1.AddItem(fProxyBooks.ISBN. Value + ' - ' +
fProxyBooks.Title. Value , aBook);
aBook.ISBN := fProxyBooks.ISBN. Value ;
aBook.Authors.AddRange(
BuildAuhtorsList(fProxyBooks.Authors. Value ));
aBook.Title := fProxyBooks.Title. Value ;
aBook.ReleaseDate := ConvertReleaseDate(
fProxyBooks.ReleaseDate. Value );
aBook.Price := fProxyBooks.Price.AsCurrency;
aBook.PriceCurrency := fProxyBooks.Currency. Value ;
ValidateCurrency(aBook.PriceCurrency);
end );
end ;
Kode lebih mudah dibaca dan aman, namun masih dalam bentuk. Saatnya untuk menghapusnya dan memisahkannya dari semua dependensi untuk mengaktifkan pengujian.
Kita harus mulai dengan keputusan arsitektur yang penting. Saat ini dalam kode kami memiliki dua kelas serupa: TBook
yang menyimpan data dan TBookProxy
yang memprosesnya. Penting untuk memutuskan kelas mana yang bergantung satu sama lain. TBook
adalah bagian dari lapisan model dan tidak boleh mengetahui tentang objek akses data.
procedure TForm1.LoadBooksToListBox ();
begin
ListBox1.Clear;
fProxyBooks.LoadAndValidate;
fProxyBooks.FillStringsWithBooks(ListBox1.Items);
end ;
Terakhir, metode form terlihat bagus dan jelas. Ini pertanda baik bahwa kita menuju ke arah yang benar. Kode yang diekstraksi dan dipindahkan ke proxy kumpulan data terlihat hampir seperti sebelumnya:
procedure TBooksProxy.LoadAndValidate ;
var
aBook: TBook;
isDatePrecise: boolean;
begin
fBooksList.Clear;
ForEach(
procedure
begin
aBook := TBook.Create;
fBooksList.Add(aBook);
aBook.ISBN := ISBN. Value ;
aBook.Authors.AddRange(
BuildAuhtorsList(Authors. Value ));
aBook.Title := Title. Value ;
aBook.ReleaseDate := ConvertReleaseDate(
ReleaseDate. Value , isDatePrecise);
aBook.IsPreciseReleaseDate := isDatePrecise;
aBook.Price := Price.AsCurrency;
aBook.PriceCurrency := Currency. Value ;
ValidateCurrency(aBook.PriceCurrency);
end );
end ;
Bersama dengan kode ini kami harus memindahkan semua metode dependen yang bertanggung jawab untuk mengonversi dan memvalidasi data: BuildAuhtorsList
, ConvertReleaseDate
dan ValidateCurrency
.
Proxy ini berisi kumpulan internal buku fBookList
yang digunakan untuk mengisi ListBox. Pada saat itu kami memindahkan kode ini ke kelas proxy kumpulan data untuk mengurangi jumlah perubahan, tetapi kode tersebut harus dipindahkan ke kelas yang sesuai:
procedure TBooksProxy.FillStringsWithBooks (
aStrings: TStrings);
var
aBook: TBook;
begin
aStrings.Clear;
for aBook in fBooksList do
aStrings.AddObject(
aBook.ISBN + ' - ' + aBook.Title, aBook);
end ;
TBookProxy
di (Unit Data.Proxy.Book.pas
)function CreateMockTableBook
di ( Unit Data.Mock.Book.pas
)