เรียก:
cargo run
จากรากที่เก็บและคุณพร้อมที่จะไป
ลองสำรวจเอกสาร API เพื่อรับทราบว่าทุกอย่างอยู่ที่ไหน:
cargo doc --open
ที่เก็บนี้มีแอปพลิเคชันตัวอย่างสนิมสำหรับร้านค้าออนไลน์ เป้าหมายคือการสำรวจรูปแบบการออกแบบบางอย่างที่ใช้ประโยชน์จากภาษาสนิมเพื่อสร้างแอพพลิเคชั่นที่ปรับขนาดได้และบำรุงรักษาได้
มันเป็นสนามเด็กเล่นสำหรับความคิดที่แตกต่างกันบางคนอาจไม่ได้ออกไปในทางปฏิบัติ หากคุณมีข้อเสนอแนะใด ๆ ที่นี่โปรดเปิดปัญหา!
เป็นการยากที่จะออกแบบซอฟต์แวร์ในสุญญากาศ เมื่อคุณไม่มีโดเมนที่แท้จริงในการขับเคลื่อนสิ่งที่สำคัญการตัดสินใจออกแบบอาจรู้สึกโดยพลการ ฉันได้ใช้ความพยายามในการจัดทำเอกสารการตัดสินใจและเหตุผลที่อยู่เบื้องหลังพวกเขา แต่คำถามเช่น เราควรแยกรายการสั่งซื้อจากคำสั่งซื้อหรือไม่? หรือ การสอบถามในคำสั่งซื้อสามารถเข้าถึงตารางฐานข้อมูลสำหรับผลิตภัณฑ์ได้หรือไม่? ไม่สามารถตอบได้จากมุมมองทางเทคนิคล้วนๆ พวกเขาต้องการมุมมองเกี่ยวกับเป้าหมายของโครงการด้วย สำหรับทุกคนที่อ่านรหัสนี้ฉันขอแนะนำให้คุณตรวจสอบมันตามการตัดสินใจออกแบบโดยพลการคิดถึงข้อ จำกัด ที่คุณเผชิญในสภาพแวดล้อมของคุณเองและวิธีการเหล่านั้นอาจแจ้งการตัดสินใจของคุณเองเมื่อสร้างแอปพลิเคชันใน Rust
มันไม่เกี่ยวกับกรอบการเกิดสนิมหรือห้องสมุดเฉพาะหรือเกี่ยวกับการแก้ปัญหาที่มีอยู่ในแอปพลิเคชันการช็อปปิ้งออนไลน์
ส่วนต่อไปนี้อธิบายส่วนต่าง ๆ ของแอปพลิเคชันและอธิบายว่าทำไมพวกเขาถึงรวมกันเป็นอย่างที่พวกเขาเป็น
เค้าโครงโครงการมุ่งเน้นไปที่ความเป็นส่วนตัว โดยการ จำกัด ขอบเขตของรายการบางรายการคุณยัง จำกัด ขอบเขตของการแตกหักที่อาจเกิดขึ้น โดยการ จำกัด ขอบเขตของรายการบางรายการคุณยัง จำกัด ขอบเขตของภาระในการรักษาสถานะแอปพลิเคชัน ในสนิมรายการที่เป็นส่วนตัวในโมดูล สามารถมองเห็นได้สำหรับเด็กทุกคนของโมดูลนั้น นั่นอาจฟังดูไม่ดี แต่เราใช้ประโยชน์จากมันเพื่อป้องกันไม่ให้ Domain APIs จากรายละเอียดการใช้งานรั่วไหลเพื่อความกังวลภายนอกเช่นการทำให้เป็นอนุกรมและการจัดเก็บ
แนวคิดธุรกิจหลักในแอปพลิเคชันแต่ละรายการจะถูกแบ่งออกเป็นโฟลเดอร์ที่มีอยู่ในตัวเอง (ส่วนใหญ่) เช่น products หรือ customers แต่ละโมดูลห่อหุ้มทุกอย่างที่ต้องรู้เกี่ยวกับชุดของเอนทิตีเฉพาะ:
/store )/queries )/commands ) เอนทิตีสามารถขึ้นอยู่กับเอนทิตีจากโมดูลอื่นเช่น Order ขึ้นอยู่กับ Product เมื่อเพิ่ม มีลำดับชั้นความเป็นส่วนตัวในแต่ละโมดูลโดเมน:
from_datafrom_data ไปยังเอนทิตีไฮเดรตโมดูลเหล่านี้ค่อนข้างหนัก แต่ในแอปพลิเคชันที่เหมาะสมการเพิ่มโมดูลโดเมนใหม่อาจทำให้ง่ายขึ้นโดยใช้มาโคร ฉันไม่ได้ใช้มาโครในแอปพลิเคชันนี้ดังนั้นรหัสยังคงติดตามได้ง่าย
ปัญหาหนึ่งเกี่ยวกับลำดับชั้นของโมดูลที่สร้างขึ้นอย่างสมบูรณ์แบบคือมันสามารถแตกสลายได้เมื่อคุณจบลงด้วยแนวคิดที่ไม่เหมาะกับเค้าโครงปัจจุบัน ยิ่งเกิดสิ่งนี้บ่อยเท่าไหร่ก็ยิ่งยากที่จะสอดคล้องกับเลย์เอาต์ที่มีอยู่ก่อนหน้านี้เพราะมันเป็นไปไม่ได้ที่จะบอกว่ามันควรจะเป็นอย่างไร
เราต้องการให้โมดูลเหล่านี้จัดการชะตากรรมของตัวเอง แต่เราไม่ต้องการให้พวกเขาอยู่ในตัวเองจนถึงจุดที่พวกเขาสามารถแบ่งออกเป็นบริการแยกต่างหาก นี่คือการทำให้สิ่งต่าง ๆ ง่าย หากคุณต้องการทำสิ่งนี้ฉันขอแนะนำให้ใช้ลังแยกแทนเพียงแค่โมดูลแยกต่างหาก
แอปพลิเคชันเป็นไปตามการออกแบบการแยกความรับผิดชอบคำสั่งแบบสอบถามอย่างง่าย นี่เป็นวิธีการที่ใช้งานได้ดีสำหรับแอปพลิเคชันที่ขับเคลื่อนด้วยข้อมูลโดยไม่มีตรรกะที่ซับซ้อนมากมาย คำสั่งจับการโต้ตอบของโดเมนและทำงานโดยตรงกับเอนทิตีในขณะที่การสืบค้นนั้นเป็นไปโดยพลการทั้งหมด แอปพลิเคชันนี้ไม่ได้ใช้โครงสร้างพื้นฐานพิเศษใด ๆ เพื่อรับรู้ CQRs พวกเขาเป็นเพียงลักษณะง่าย ๆ ที่ใช้โดยใช้รูปแบบการฉีดพึ่งพา เป็นหลัก:
Result<()>Result<T>&mut self&self Receiverความแตกต่างในความไม่แน่นอนหมายถึงคำสั่งสามารถเรียกสืบค้นได้ แต่แบบสอบถามไม่สามารถเรียกคำสั่งได้
หน่วยงานเป็นหัวใจสำคัญของแอปพลิเคชัน แม้จะไม่มีธุรกิจที่แท้จริง แต่ฉันก็พยายามทำให้โมเดลโดเมนร่ำรวย เอนทิตีไม่ได้เป็นเพียงแค่ถุง Cruddy พวกเขาคือ:
.to_data() ในขณะที่ดูเอนทิตีคุณไม่สามารถเรียกพฤติกรรมการแก้ไขได้ สิ่งนี้รับประกันได้ด้วยระบบการกู้ยืมของ Rust เอนทิตีสามารถย้ายการเป็นเจ้าของไปยังข้อมูลแบบอ่านอย่างเดียวกับ .into_data() นี่เป็นการดำเนินการทางเดียวดังนั้นการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นกับรัฐจึงไม่สามารถคงอยู่กับร้านค้าได้เป้าหมายของเอนทิตีคือการห่อหุ้มค่าคงที่ของแนวคิดโดเมนที่สำคัญบางอย่าง เอนทิตีที่นี่ใช้งานง่ายกับร้านค้าในหน่วยความจำจำลองหรือฐานข้อมูลภายนอก เราควรระวังไม่ให้พึ่งพาการเปลี่ยนแปลงของรัฐโดยมีเอนทิตีหนึ่งที่สะท้อนในอีกหน่วยหนึ่งเพราะพวกเขาเกิดขึ้นเพื่อชี้ไปที่แหล่งเดียวกัน
เอนทิตียังต้องระวังไม่ให้ขึ้นอยู่กับประเภทข้อมูลของเอนทิตีอื่นเนื่องจากไม่มีการรับประกันว่าข้อมูลนั้นถูกต้องจริง แต่พวกเขาขึ้นอยู่กับเอนทิตีและแปลงเป็นข้อมูลตามความจำเป็นดังนั้นพวกเขาจึงรู้ว่าสถานะนั้นถูกต้อง
เราใช้คุณสมบัติสนิมต่อไปนี้เพื่อปกป้องสถานะเอนทิตีของเรา:
Serialize หรือ Deserialize สิ่งนี้อาจเปลี่ยนไปตามแทร็กได้ แต่ฉันคิดว่ามันง่ายกว่าที่จะรักษาสถานะที่รวดเร็วและปล่อยให้เข้ากันได้ง่ายขึ้นสำหรับความเข้ากันได้ย้อนหลังเอนทิตีห่อหุ้มบางรัฐหรือข้อมูลและตรวจสอบให้แน่ใจว่าการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นกับข้อมูลนั้นจะไม่ทำลายค่าคงที่ใด ๆ ที่ข้อมูลคาดว่าจะถือ แทนที่จะใช้ getters เราเปิดเผยมุมมองแบบอ่านอย่างเดียวของข้อมูลเป็นโครงสร้าง ประโยชน์คือคุณไม่จำเป็นต้องยอมแพ้คุณสมบัติที่ดีของ Rust สำหรับการทำงานกับโครงสร้างที่เป็นข้อมูลเช่นเดียวกับวิธีการ getter มุมมองนี้เป็น แบบอ่านอย่างเดียว ดังนั้นการเปลี่ยนแปลงไม่สามารถเขียนกลับไปที่โครงสร้างได้โดยตรง เอนทิตียังคงมีวิธีการตั้งค่าสำหรับสิ่งนั้น
คุณสามารถยืนยันได้ว่าการเปิดเผยสถานะในวิธีนี้รายละเอียดการใช้งานการรั่วไหลเช่น version ที่ไม่มีคุณค่าต่อสาธารณะ นี่อาจเป็นความจริง ในการแก้ไขปัญหาคุณสามารถย้ายอายุการใช้งานของมุมมองแบบอ่านอย่างเดียวไปยังฟิลด์และเขียนมุมมองที่ยืมมาที่แตกต่างกันของรัฐและรักษาโครงสร้างข้อมูลที่จัดการโดยกิจการส่วนตัว
คุณอาจยืนยันว่าการถือค่าคงที่ในโครงสร้างที่ไม่ได้เก็บไว้นั้นเปราะ สิ่งนี้สมเหตุสมผลเมื่อขอบเขตความเป็นส่วนตัวสำหรับบางฟิลด์อยู่ที่ระดับวัตถุเช่นเดียวกับใน C# สนิมแตกต่างกันเล็กน้อย ขอบเขตความเป็นส่วนตัวที่เข้มงวดที่สุดอยู่ ที่โมดูลและลูก ๆ ดังนั้นภาระในการรักษาค่าคงที่ของฟิลด์ที่กำหนดนั้นตกอยู่ในรายการทั้งหมดในโมดูลที่กำหนดไว้รวมถึงลูก ๆ ของโมดูลทั้งหมด
สิ่งนี้อาจฟังดูคล้ายกับการรั่วไหลอันน่ากลัว แต่แอปพลิเคชันนี้ใช้ประโยชน์จากการสร้างที่เก็บข้อมูลที่เป็นนามธรรม แทนที่จะต้องเปิดเผยหลุมใน API ของเราเพื่อสนับสนุน ORM การรักษาสถานะของค่าคงที่เพียงแค่ขยายเข้าไปในร้านค้าโมเดลโดยไม่รั่วไหลออกสู่สาธารณะ
ประเภท Id และ Version ทั้งคู่มีพารามิเตอร์ phantom ทั่วไป พารามิเตอร์นี้มีอยู่อย่างหมดจดเพื่อให้คุณแสดง ID ที่มีประเภทที่เข้ากันไม่ได้เช่น Id<ProductData> และ Id<OrderData> แต่ยังคงแบ่งปันรายละเอียดการใช้งานอื่น ๆ
มันเป็นรูปแบบที่ง่ายต่อการติดตามมากกว่าการใช้แมโครเพื่อลดแผ่นหม้อไอน้ำเพราะมีแหล่งที่มาที่แตกต่างกันเสมอที่คุณสามารถย้อนกลับไปได้
แต่ละเอนทิตีที่คงอยู่มีฟิลด์ version ฟิลด์นี้เป็นตัวระบุที่ไม่ต่อเนื่องซึ่งสอดคล้องกับสถานะของนิติบุคคล ณ เวลาที่กำหนด เมื่อเอนทิตีถูกดึงมาจากร้านค้าเราให้ความชุ่มชื้นเวอร์ชันของมันจะถูกตรวจสอบก่อนที่จะอัปเดตและหากพวกเขาไม่ตรงกับเรา balk
การตรวจสอบเวอร์ชันทำงานได้ดีสำหรับร้านค้าในหน่วยความจำเนื่องจากเรามีล็อคข้อมูลพิเศษ (ผู้โทรเพียง 1 คนเท่านั้นที่สามารถแก้ไขสถานะได้ในแต่ละครั้ง) แต่จะต้องมีวิธีการที่แตกต่างกันสำหรับฐานข้อมูลที่เหมาะสม เราอาจอัปเดตตำแหน่งที่ ID และเวอร์ชันจับคู่เลือกจำนวนระเบียนที่อัปเดตและ Balk หากเป็น 0 (หมายถึงเวอร์ชันไม่ตรงกันหรือไม่มีอยู่)
เลเยอร์หน่วยเก็บข้อมูลใช้รูปแบบการทำธุรกรรมอย่างง่ายที่ช่วยให้ที่เก็บข้อมูลอิสระเข้าร่วมในการทำธุรกรรม ที่เก็บกลางติดตามการทำธุรกรรมที่ใช้งานอยู่และได้รับการปรึกษาเมื่อข้อมูลถูกดึงมาจากที่เก็บข้อมูลเพื่อให้แน่ใจว่าพวกเขาพร้อมที่จะใช้ การพร้อมกันในแง่ดีของข้อมูลทำให้มั่นใจได้ว่าการทำธุรกรรมที่ใช้งานอยู่หลายรายการไม่สามารถลองตั้งค่าเดียวกันได้ในเวลาเดียวกัน สิ่งนี้ละเมิดความโดดเดี่ยวที่แท้จริง แต่ทำให้สิ่งต่าง ๆ ง่ายขึ้นและช่วยให้เราลดสถานะที่จำเป็นสำหรับแต่ละค่าที่ถูกเก็บไว้
การฉีดขึ้นอยู่กับการพึ่งพานั้นเป็นประโยชน์ในการฝึกฝนเมื่อออกแบบแอปพลิเคชัน มันช่วยให้คุณแยกข้อกังวลของการแก้ไขการพึ่งพาออกจากตรรกะแอป นอกจากนี้ยังให้วิธีที่ชัดเจนในการขยายแอปพลิเคชัน แอปพลิเคชันนี้ใช้รูปแบบง่าย ๆ ที่ให้ประโยชน์เหล่านี้แก่เราโดยไม่มีโครงสร้างพื้นฐานมากมาย
แอปพลิเคชันนี้ไม่ได้ใช้การผกผันของคอนเทนเนอร์ควบคุมเช่นคุณอาจคุ้นเคยกับการเขียนแอปพลิเคชัน. NET ส่วนใหญ่เป็นเพราะไม่มีอะไรเกิดขึ้นจริง มันเป็นปัญหาที่ยาก มันใช้รูปแบบการฉีดพึ่งพาการพึ่งพาอย่างง่ายสำหรับการเขียนคำสั่งและการสืบค้นแม้ว่าจะไม่มีคอนเทนเนอร์ที่ซับซ้อน
เป้าหมายหลักของการฉีดพึ่งพาที่นี่ไม่สนับสนุนการเยาะเย้ย มันคือการลดความซับซ้อนโดยการผลักดันความกังวลต่อพ่วงให้ห่างไกลจากตรรกะขององค์ประกอบแต่ละตัว
ส่วนประกอบที่ฉีดได้อาศัยอยู่ในโมดูลของตัวเอง โมดูลนั้นมี:
Resolver ใช้ร่วมกันซึ่งมีวิธีการที่ส่งคืนการใช้งานเริ่มต้นโดยไม่ต้องใช้การพึ่งพาimpl Trait คุณไม่มีทางรู้ว่าการใช้งานที่เป็นรูปธรรมนี้ใช้อะไรArc , Box Resolver ใช้ร่วมกันจะฟังดูเป็นบริการของผู้ให้บริการและมันเป็นเพราะความละเอียดของการพึ่งพามีอยู่ในบล็อกที่รวมอยู่ใน Resolver ตัวเองเราจึงหลีกเลี่ยงปัญหาขึ้นอยู่กับสถานะของเวทมนตร์ทั่วโลกในตรรกะแอปของเรา
เพื่อลดหม้อไอน้ำสำหรับส่วนประกอบที่มีเพียงวิธีเดียวเท่านั้นที่เรายังใช้ผ้าห่มนำไปใช้สำหรับลักษณะ Fn สิ่งนี้ช่วยให้คุณหลีกเลี่ยงการประกาศโครงสร้างสำหรับพวกเขาที่มีอยู่ทั่วไปมากกว่าการพึ่งพาทั้งหมดของพวกเขา คอมไพเลอร์สนิมจะดูแลคุณ
รูปแบบนี้ยากที่จะอธิบายในร้อยแก้วคุณต้องเห็นมัน ดูที่ domain/products/commands/create_product หรือโมดูล domain/products/model/store สำหรับตัวอย่างของรูปแบบการฉีดพึ่งพาการพึ่งพานี้ในที่ทำงาน
Resolver ไม่ใช่ "พระเจ้าวัตถุ" หรือไม่? วัตถุพระเจ้า " เป็นวัตถุในแอปพลิเคชันของคุณที่รวบรวมตรรกะที่สำคัญทั้งหมดจนถึงจุดที่คุณไม่สามารถทำงานกับส่วนประกอบได้โดยไม่ต้องทำงานผ่านวัตถุพระเจ้าพวก Resolver เป็นปัญหาเพราะพวกเขายากที่จะสร้างหรือเปลี่ยนแปลงรูปแบบ Resolver ที่นี่เป็นวัตถุพระเจ้า แต่ไม่จำเป็นต้องสร้างส่วนประกอบแต่ละตัว