Panggilan:
cargo run
dari root repositori dan Anda siap untuk pergi.
Coba juga menjelajahi dokumen API untuk mendapatkan gambaran di mana segalanya tinggal:
cargo doc --open
Repositori ini berisi sampel aplikasi karat untuk toko online. Tujuannya adalah untuk mengeksplorasi beberapa pola desain yang memanfaatkan bahasa karat untuk membangun aplikasi yang dapat diskalakan dan dipelihara.
Ini adalah taman bermain untuk ide -ide yang berbeda, beberapa di antaranya mungkin tidak berhasil dalam praktik. Jika Anda memiliki umpan balik tentang apa pun di sini, jangan ragu untuk membuka masalah!
Sulit untuk merancang perangkat lunak dalam ruang hampa. Ketika Anda tidak memiliki domain nyata untuk menggerakkan apa yang penting maka keputusan desain dapat terasa sewenang -wenang. Saya telah berusaha untuk mendokumentasikan keputusan dan alasan di baliknya, tetapi pertanyaan seperti haruskah kita membagi item dari pesanan? Atau haruskah kueri dalam pesanan dapat mengakses tabel basis data untuk produk? tidak bisa dijawab dari sudut pandang teknis murni. Mereka membutuhkan perspektif tentang tujuan proyek juga. Bagi siapa pun yang membaca kode ini, saya akan mendorong Anda untuk meneliti itu berdasarkan keputusan desain yang sewenang -wenang itu, pikirkan kendala yang Anda hadapi di lingkungan Anda sendiri dan bagaimana itu dapat menginformasikan keputusan Anda sendiri ketika membangun aplikasi di Rust.
Ini bukan tentang kerangka kerja karat atau perpustakaan tertentu, atau tentang menyelesaikan masalah yang melekat pada aplikasi belanja online.
Bagian berikut menggambarkan bagian -bagian dari aplikasi dan menjelaskan mengapa mereka menyusun apa adanya.
Tata letak proyek difokuskan pada privasi. Dengan membatasi ruang lingkup barang -barang tertentu, Anda juga membatasi ruang lingkup potensi kerusakan. Dengan membatasi ruang lingkup barang -barang tertentu, Anda juga membatasi ruang lingkup beban mempertahankan status aplikasi. Dalam karat, barang -barang yang bersifat pribadi dalam suatu modul terlihat oleh semua anak modul itu . Itu mungkin terdengar seperti hal yang buruk, tetapi kami memanfaatkannya untuk mencegah API domain membocorkan detail implementasi demi keprihatinan luar, seperti serialisasi dan penyimpanan.
Setiap konsep bisnis inti dalam aplikasi ini dibagi menjadi folder mandiri (kebanyakan) sendiri, seperti products atau customers . Setiap modul merangkum semua yang perlu diketahui tentang serangkaian entitas tertentu:
/store )/queries )/commands ) Entitas dapat bergantung pada entitas dari modul lain, seperti Order tergantung pada Product saat menambahkannya. Ada hierarki privasi di setiap modul domain:
from_datafrom_data ke entitas hidratModul-modul ini agak berat, tetapi dalam aplikasi yang tepat menambahkan modul domain baru dapat disederhanakan menggunakan makro. Saya belum menggunakan makro dalam aplikasi ini sehingga kode tetap mudah diikuti.
Satu masalah dengan hierarki modul yang dibuat dengan sempurna adalah semuanya bisa berantakan ketika Anda berakhir dengan konsep yang tidak sesuai dengan tata letak saat ini. Semakin sering hal ini terjadi, semakin sulit untuk menyesuaikan diri dengan tata letak yang ada sebelumnya karena menjadi tidak mungkin untuk mengatakan apa yang seharusnya.
Kami ingin modul-modul ini mengelola takdir mereka sendiri, tetapi kami tidak ingin mereka mandiri sampai pada titik di mana mereka dapat dibagi menjadi layanan yang terpisah. Ini untuk menjaga hal -hal sederhana. Jika Anda memang ingin melakukan ini maka saya sarankan menggunakan peti terpisah alih -alih hanya modul terpisah.
Aplikasi ini mengikuti desain pemisahan tanggung jawab kueri perintah sederhana. Ini adalah pendekatan yang berfungsi dengan baik untuk aplikasi berbasis data tanpa banyak logika kompleks. Perintah menangkap beberapa interaksi domain dan bekerja langsung pada entitas sedangkan pertanyaan benar -benar sewenang -wenang. Aplikasi ini tidak menggunakan infrastruktur khusus untuk mewujudkan CQRS, mereka hanya sifat sederhana yang diimplementasikan menggunakan pola injeksi ketergantungan. Pada dasarnya:
Result<()>Result<T>&mut self&self penerimaPerbedaan dalam mutability berarti perintah dapat memanggil kueri tetapi pertanyaan tidak dapat memanggil perintah.
Entitas adalah jantung dari aplikasi. Meskipun kurangnya bisnis yang nyata, saya telah berupaya menjaga model domain tetap kaya. Entitas bukan hanya sekantong negara bagian yang kasar. Mereka adalah:
.to_data() . Saat melihat entitas, Anda tidak dapat memanggil perilaku memodifikasi di atasnya. Ini dijamin oleh sistem pinjaman Rust. Suatu entitas dapat memindahkan kepemilikan ke dalam data baca-saja dengan .into_data() . Ini adalah operasi satu arah, sehingga setiap perubahan yang dilakukan untuk menyatakan tidak dapat bertahan kembali ke toko.Tujuan suatu entitas adalah untuk merangkum invarian dari beberapa konsep domain utama. Entitas di sini mudah digunakan dengan toko mock dalam memori atau database eksternal. Kita harus berhati -hati untuk tidak mengandalkan perubahan negara dengan satu entitas yang tercermin dalam hal lain karena kebetulan menunjuk ke sumber yang sama.
Entitas juga perlu berhati -hati untuk tidak bergantung pada tipe data entitas lain karena tidak ada jaminan bahwa data sebenarnya valid. Sebaliknya mereka bergantung pada suatu entitas dan mengubahnya menjadi data sesuai kebutuhan, jadi mereka selalu tahu bahwa keadaan itu valid.
Kami menggunakan fitur karat berikut untuk melindungi status entitas kami:
Serialize atau Deserialize . Ini dapat diubah di trek, tetapi saya merasa lebih mudah untuk menjaga status serial yang dapat diseriali dengan cepat untuk kompatibilitas ke belakang.Entitas merangkum beberapa negara, atau data dan memastikan perubahan apa pun yang dilakukan pada data itu tidak merusak invarian yang diharapkan data. Daripada mengimplementasikan getters, kami mengekspos tampilan baca-saja dari data sebagai struktur. Manfaatnya adalah Anda tidak perlu melepaskan fitur bagus Rust untuk bekerja dengan struktur data, seperti yang Anda lakukan dengan metode Getter. Pandangan ini hanya baca , jadi perubahan tidak dapat ditulis langsung kembali ke struktur. Entitas masih memberikan metode setter untuk itu.
Anda dapat berargumen bahwa mengekspos keadaan dengan cara ini membocorkan detail implementasi, seperti version yang tidak memiliki nilai menjadi publik. Ini mungkin benar. Untuk mengatasinya, Anda bisa memindahkan masa pakai tampilan baca-hanya ke ladang, dan menyusun pandangan pinjaman yang berpotensi berbeda dari negara, dan menjaga struktur data dikelola oleh entitas swasta.
Anda juga bisa berargumen bahwa memegang invarian pada struktur yang tidak menyimpannya rapuh. Ini masuk akal ketika batas privasi untuk beberapa bidang berada di tingkat objek, seperti di C#. Karat sedikit berbeda. Batas privasi paling ketat ada di modul dan anak -anaknya . Jadi beban mempertahankan invarian dari bidang yang diberikan jatuh pada semua item dalam modul yang didefinisikan, ditambah semua anak -anak modul itu.
Ini mungkin terdengar seperti kebocoran yang mengerikan tetapi aplikasi ini mengeksploitasi itu untuk membangun penyimpanan yang diabstraksikan dengan baik. Alih -alih harus mengekspos lubang di API kami untuk mendukung ORM, mempertahankan keadaan invarian hanya meluas ke toko model, tanpa bocor kembali ke publik.
Jenis Id dan Version keduanya memiliki parameter generik phantom. Parameter ini ada murni untuk memungkinkan Anda mengekspresikan ID dengan tipe yang tidak kompatibel, seperti Id<ProductData> dan Id<OrderData> , tetapi masih berbagi detail implementasi lainnya.
Ini adalah pola yang lebih mudah diikuti daripada menggunakan makro untuk mengurangi boilerplate karena selalu ada difinisi sumber yang dapat Anda kembalikan.
Setiap entitas yang dapat dipertahankan memiliki bidang version . Bidang ini adalah pengidentifikasi non-sekuensial yang sesuai dengan keadaan entitas pada titik waktu tertentu. Ketika suatu entitas diambil dari toko, kami melembabkan versinya, ini kemudian diperiksa sebelum memperbarui dan jika tidak cocok, kami menolak.
Cek versi berfungsi dengan baik untuk toko di dalam memori karena kami memiliki kunci eksklusif pada data (hanya 1 penelepon yang dapat memodifikasi status sekaligus), tetapi akan membutuhkan pendekatan yang berbeda untuk DB yang tepat. Kami mungkin dapat memperbarui di mana ID dan versi cocok, pilih jumlah catatan yang diperbarui dan menolak jika 0 (berarti versi tidak cocok, atau tidak ada).
Lapisan penyimpanan menggunakan skema transaksional sederhana yang memungkinkan penyimpanan data independen untuk berpartisipasi dalam transaksi. Repositori pusat melacak transaksi aktif dan dikonsultasikan ketika data diambil dari penyimpanan data untuk memastikan mereka siap digunakan. Konkurensi optimis pada data memastikan beberapa transaksi aktif tidak dapat mencoba mengatur nilai yang sama secara bersamaan. Ini melanggar isolasi sejati, tetapi membuat hal -hal sederhana, dan memungkinkan kita meminimalkan keadaan yang diperlukan untuk setiap nilai yang disimpan.
Injeksi ketergantungan bermanfaat sebagai praktik untuk bersandar saat merancang aplikasi. Ini memungkinkan Anda memisahkan kekhawatiran resolusi ketergantungan dari logika aplikasi. Ini juga memberi Anda cara yang jelas untuk skala aplikasi. Aplikasi ini mengadopsi pola sederhana yang memberi kita manfaat ini tanpa banyak infrastruktur.
Aplikasi ini tidak menggunakan inversi wadah kontrol seperti yang mungkin Anda gunakan jika Anda menulis aplikasi .net. Ini sebagian besar karena sebenarnya tidak ada karat. Ini masalah yang sulit. Itu memang menggunakan pola injeksi ketergantungan sederhana untuk menyusun perintah dan pertanyaan, bahkan tanpa wadah yang canggih.
Tujuan utama injeksi ketergantungan di sini bukan untuk mendukung mengejek. Ini untuk mengurangi kompleksitas dengan mendorong kekhawatiran periferal lebih jauh dari logika komponen individu.
Komponen yang dapat disuntikkan hidup dalam modul mereka sendiri. Modul itu berisi:
Resolver bersama yang berisi metode yang mengembalikan implementasi default tanpa memerlukan ketergantungannya.impl Trait . Anda tidak pernah tahu jenis konkret apa yang digunakan implementasi default ini.Arc , Box . Resolver bersama terdengar sedikit servis-locator-y, dan memang begitu, tetapi karena resolusi ketergantungan sepenuhnya terkandung dalam blok IMP pada Resolver itu sendiri, kami menghindari masalah tergantung pada keadaan global ajaib dalam logika aplikasi kami.
Untuk mengurangi boilerplate, untuk komponen dengan hanya satu metode kami juga mengimplementasikannya untuk sifat Fn . Ini memungkinkan Anda menghindari mendeklarasikan struktur untuk mereka yang generik atas semua ketergantungan mereka. Kompiler karat akan mengurusnya untuk Anda.
Pola ini sulit dijelaskan dalam prosa, Anda perlu melihatnya. Lihatlah modul domain/products/commands/create_product , atau modul domain/products/model/store untuk contoh -contoh pola injeksi ketergantungan ini di tempat kerja.
Resolver "objek Tuhan"? Objek Tuhan " adalah objek dalam aplikasi Anda yang mengumpulkan semua logika penting ke titik bahwa Anda tidak dapat bekerja dengan komponen tanpa juga bekerja melalui objek Tuhan. Mereka menjadi masalah karena mereka menjadi sulit untuk dibangun atau diubah. Pola Resolver di sini adalah objek Tuhan, tetapi tidak diperlukan untuk membangun komponen individu. Resolver hanya dengan menyerahkan komponen dengan komponen yang tidak diperlukan.