เอกสารในห้องสมุดของฉัน

แถบด้านข้างหลายราก

flex-layout

  • Getting Started

  • Guides

  • Reference

typed-rx-http

  • Getting Started

  • Guides

  • Reference

global-rx-state

  • Getting Started

  • Guides

  • Reference

webflux-fe-dev-assistant

reactive-mongo-dsl

ภาพรวม

ผู้พัฒนาที่เคยทำงานกับ RDBMS เป็นครั้งแรกได้สร้าง helper ขนาดเล็กที่เติบโตตามความต้องการของโปรเจกต์จริง และอธิบายประวัติและเกณฑ์การออกแบบจนกลายเป็น Driver-first MongoDB convenience layer ใน 1.0.0


เรื่องราวต้นกำเนิด

ฉันพัฒนามาเป็นเวลาประมาณ 4 ปี โดยมุ่งเน้นที่ RDBMS และคุ้นเคยกับสภาพแวดล้อมการทำงานส่วนใหญ่ใน AWS RDS เมื่อเริ่มโปรเจกต์ส่วนตัวใหม่ ฉันเลือก MongoDB เป็นครั้งแรก และเมื่อได้รู้จักมากขึ้น ฉันรู้สึกว่ามันไม่ใช่แค่ฐานข้อมูลที่มี schema ที่ยืดหยุ่น แต่เป็นเครื่องมือที่สามารถเปลี่ยนแปลงประสิทธิภาพการพัฒนาของฉันได้อย่างมาก

โดยเฉพาะ MongoDB Atlas ทำให้ฉันรู้สึกเหมือนอยู่ในโลกใหม่ ฉันสามารถใช้การค้นหาเฉพาะและการค้นหาเวกเตอร์ในแพลตฟอร์มข้อมูลเดียวกัน ซึ่งก่อนหน้านี้คิดว่าต้องใช้ระบบแยกต่างหาก และการทำงานด้านการสำรองข้อมูล การตรวจสอบ และการขยายก็สะดวกกว่ามากเมื่อเปรียบเทียบกับมาตรฐานที่ฉันเคยใช้ใน AWS RDS หากต้องการขนาดและฟังก์ชันที่มีความเชี่ยวชาญมากขึ้น ฉันอาจแยกบริการค้นหาหรือฐานข้อมูลเวกเตอร์ออก แต่ในขั้นตอนการสร้างและดำเนินการโปรเจกต์อย่างรวดเร็ว การใช้ Atlas เพียงอย่างเดียวก็มีความน่าสนใจมาก

ปัญหาคือการเขียน query แบบไดนามิกสำหรับฉันที่ไม่เคยใช้ MongoDB มาก่อนนั้นช้ามาก ในตอนแรกรูปแบบ findBy... ของ repository ก็เพียงพอ แต่เมื่อเงื่อนไขการค้นหาซับซ้อนขึ้น ฉันต้องประกอบ Query, Criteria และตัวดำเนินการด้วยตนเอง การประกอบเงื่อนไขซ้ำๆ ในสภาพที่ไม่คุ้นเคยทำให้เกิดข้อผิดพลาดได้ง่าย และการเขียนโค้ดในรูปแบบเดียวกันซ้ำๆ ก็ทำให้ความเร็วในการพัฒนาลดลงมาก

เริ่มต้นจาก DSL แบบง่ายสำหรับการค้นหา

ดังนั้นสิ่งแรกที่ฉันสร้างคือคลาสเล็กๆ เพื่อทำ R ใน CRUD ให้เรียบง่าย ซึ่งเป็นจุดเริ่มต้นของคลาสที่ปัจจุบันเรียกว่า ReactiveMongoDsl ในตอนนั้นยังไม่มีฟังก์ชันเช่น aggregation, pipeline, lookup และเพียงแค่รวบรวม Query, Criteria, paging ของ Spring Data ReactiveMongoTemplate ให้รวดเร็วขึ้นเล็กน้อย ฉันคิดว่าสามารถใช้ ReactiveMongoTemplate หรือ MongoDB Driver พื้นฐานได้เพียงพอสำหรับงานที่เหลือ

แต่ความคิดของนักพัฒนาที่ทำงานร่วมกันแตกต่างออกไป เขาก็ไม่เคยใช้ MongoDB มาก่อนเช่นกัน และทุกครั้งที่ต้องการทำงานเช่น upsert หรือ bulk เขาจะถามว่าฟังก์ชันนั้นมีใน DSL ที่ฉันสร้างหรือไม่ แม้ว่าฉันอาจบอกให้ใช้ API พื้นฐานโดยตรง แต่ฉันคิดว่าเขาจะต้องเผชิญกับความยากลำบากเดียวกันที่ฉันเคยประสบเมื่อเรียนรู้ MongoDB เป็นครั้งแรก ดังนั้นฉันจึงเริ่มเพิ่มฟังก์ชันที่จำเป็นลงใน ReactiveMongoDsl ทีละตัว

ในตอนแรกมีประมาณ 600 บรรทัด และเมื่อถึง 1,000 บรรทัดก็ไม่คิดว่าจำเป็นต้องแบ่งออกเป็นหลายคลาส เมื่อถึงประมาณ 2,000 บรรทัดก็เริ่มคิดมากขึ้น แต่โครงสร้างสำหรับการแยกออกกลับรู้สึกว่ามันใหญ่ขึ้น และที่สำคัญที่สุดคือรู้สึกว่ามันน่าเบื่อมาก เมื่อเกิน 5,000 บรรทัดก็ไม่สามารถเลื่อนออกไปได้อีกจึงย้ายฟังก์ชันบางอย่างที่สามารถแยกออกได้ แต่ก็ยังรู้สึกว่ามันยากที่จะแบ่งแยกกระแสหลักอย่างละเอียด เหตุผลที่คลาสหลักมีขนาดใหญ่ไม่ใช่เพราะออกแบบ DSL ขนาดใหญ่ตั้งแต่แรก แต่เป็นประวัติการสะสมฟังก์ชันที่จำเป็นในโปรเจกต์จริงที่มีจุดเข้าเดียวกัน

เมื่อเพิ่มการค้นหา การรวม การ lookup การอัปเดตแบบอะตอมิก bulk ประวัติ Atlas Search และ Vector Search เข้าไปแล้ว ในที่สุดฟังก์ชันส่วนใหญ่ที่ฉันมักต้องการเมื่อใช้ MongoDB ในโปรเจกต์ Java ก็ถูกเพิ่มเข้ามา ฉันแยกมันออกเพื่อให้สามารถใช้ในหลายโปรเจกต์ได้แทนที่จะเก็บไว้เป็น helper ภายในโปรเจกต์เดียว และนั่นคือกระบวนการเกิดขึ้นของ reactive-mongo-dsl

จาก Spring helper สู่ไลบรารี Driver-first

จุดเริ่มต้นคือ helper เพื่อทำให้การใช้ ReactiveMongoTemplate สะดวกขึ้น แต่เมื่อฟังก์ชันเพิ่มขึ้นและเริ่มนำกลับมาใช้ใหม่ในหลายโปรเจกต์ ฉันตัดสินใจว่ารูปแบบการทำงานของเฟรมเวิร์กเฉพาะไม่ควรเป็นขอบเขตของไลบรารี หลักการของ 1.0.0 คือการใช้ MongoExecutionContext เป็นสัญญาการดำเนินการและใช้ MongoDB Reactive Streams Driver โดยตรง

การเปลี่ยนแปลงนี้ไม่ได้มีจุดมุ่งหมายเพื่อกำจัด Spring Data MongoDB ในแอปพลิเคชัน Spring สามารถเชื่อมโยงการตั้งชื่อ collection ของ ReactiveMongoTemplate, MongoConverter, การแปลงแบบกำหนดเอง และการตรวจสอบแบบ reactive กับ MongoExecutionContext adapter โครงสร้างหลักจะถูกเก็บไว้อย่างอิสระจากเฟรมเวิร์ก และแอปพลิเคชันที่จำเป็นเท่านั้นที่จะใช้การตั้งค่า Spring ที่มีอยู่

ในเวลาเดียวกัน แนวทางในการไม่ทำซ้ำฟังก์ชันที่ MongoDB Driver มีอยู่แล้วใน DSL ก็ชัดเจนมากขึ้น การรวมทั่วไปสามารถรับ Bson stage ได้โดยตรง และสำหรับ Search/Vector จะมี escape hatch เช่น SearchOperator, VectorSearchQuery, driverOptions(...), stage(Bson) แม้ว่าตัวไดรเวอร์จะมีฟังก์ชันใหม่ๆ ก่อน แต่ก็เลือกที่จะทำให้สามารถใช้งานได้โดยไม่ต้องรอการเผยแพร่ครั้งถัดไปของ DSL

ปรัชญาการพัฒนา

ไม่ปิดบัง MongoDB

ฉันไม่ได้ต้องการสร้าง ORM ที่เปลี่ยนฟังก์ชันของ MongoDB ให้เป็นแนวคิดของฐานข้อมูลอื่น ฉันเชื่อมโยง query, aggregation, transaction, Atlas Search และ Vector Search ของ MongoDB เข้าด้วยกันในกระบวนการที่สั้นและค้นหาได้ง่ายขึ้น

เพิ่มเฉพาะฟังก์ชันที่จำเป็นจริงๆ เท่านั้น

นี่ไม่ใช่ไลบรารีที่ออกแบบฟังก์ชันล่วงหน้าแล้วจึงนำไปใช้งาน ในโครงการที่ดำเนินการอยู่มีความจำเป็นต้องค้นหา จึงได้เพิ่มฟังก์ชันการค้นหา, เนื่องจากต้องการ upsert และ bulk จึงได้เพิ่มฟังก์ชันเหล่านั้น และได้เพิ่ม Atlas Search และ Vector Search สำหรับฟังก์ชันการค้นหา.

ให้ความสำคัญกับผลผลิตมากกว่าความสมบูรณ์แบบเชิงโครงสร้าง

ไม่อ้างว่าโครงสร้างที่ใหญ่ที่สุดคือโครงสร้างที่เหมาะสมที่สุด การที่ทีมสามารถจัดการกับการทำงานของ MongoDB ที่ไม่คุ้นเคยได้อย่างรวดเร็วในลักษณะเดียวกันเป็นปัญหาที่สำคัญกว่าในช่วงเริ่มต้น และยังคงให้ความสำคัญกับการไหลการใช้งานจริงมากกว่าการทำให้เป็นนามธรรม.

ไม่สร้างสิ่งที่ Driver ทำอยู่แล้วอีกครั้ง

หาก MongoDB Java Driver มี typed builder จะใช้ประเภทนั้นให้มากที่สุดเท่าที่จะทำได้ การรวมกันที่ DSL สามารถลดทอนค่าได้จะถูกนำเสนอเป็น convenience API แต่จะไม่ทำการคัดลอก Driver API โดยเปลี่ยนชื่อเพียงอย่างเดียว.

ต้องสามารถลดลงไปยัง API พื้นฐานได้

ไม่คิดว่า DSL จะสามารถแทนที่ทุกสถานการณ์ได้ จะมี escape hatch เช่น Bson, Driver filter/sort/operator/options, publisher customizer และหากจำเป็นสามารถใช้ MongoDB Driver โดยตรงได้.

สี่กระบวนการที่กล่าวถึงในเอกสารปัจจุบัน

Mongo Query ทั่วไป

นี่คือกระบวนการที่เริ่มต้นเมื่อไลบรารีนี้เริ่มต้นขึ้น การจัดเรียงเงื่อนไขและการดำเนินการในลำดับ execute* → fields(...) → end() → find/findAll/count/delete/exists/atomicUpdate.

Aggregation แบบ Driver-native

หากต้องการควบคุมตั้งแต่ stage แรกของ pipeline โดยตรงหรือใช้ฟังก์ชัน aggregation ใหม่ที่ Driver ให้มา สามารถใช้กระบวนการ aggregation().stage(Bson) ได้ DSL จะไม่ทำการนำเสนอ stage ที่ Driver ให้มาแล้วอีกครั้ง เช่น $score, $scoreFusion.

Atlas Search

นี่คือกระบวนการที่เพิ่มขึ้นในขณะที่นำ Atlas Search มาใช้ในโครงการโดยไม่ต้องนำบริการค้นหาแยกต่างหากเข้ามาก่อน หลังจาก search(index) จะมีการจัดเรียง text, compound, score, highlight และ sequence token.

Vector Search

นี่คือกระบวนการที่เพิ่มขึ้นเพื่อเชื่อมโยงการค้นหา embedding ภายใน MongoDB โดยจัดเรียง query vector หรือ automated embedding, ANN/ENN, pre/post filter และ nested/array embedding options.

โมเดลการดำเนินการของเวอร์ชัน 1.0.0
text

การแยก query ทั่วไปและ Search/Vector เป็นการไม่ซ่อนข้อจำกัดจริงของ pipeline ของ MongoDB $search และ $vectorSearch มีข้อจำกัดใน stage แรก และฟังก์ชันที่ต้องการให้ผู้เรียกควบคุม stage แรกทั้งหมดสามารถลดลงไปยัง aggregation().

ขอบเขตที่ใช้ในโครงการจริง

ส่งคู่ที่ไม่มีเงื่อนไขเป็น null เพื่อประกอบเงื่อนไขการค้นหาที่ปรับเปลี่ยนได้ตามหน้าจอ.

แยก data และ totalCount ด้วย PageStream เพื่อรักษากระบวนการประมวลผลจำนวนมากในสถานะ reactive publisher.

สร้างงานที่ปลอดภัยต่อการดำเนินการซ้ำด้วย atomicUpdate().upsertOne().document().setOnInsert(...).

จัดเก็บข้อมูลที่รวบรวมจาก bulk upsert ตาม ID หรือคีย์งานและข้อมูลที่เชื่อมต่อจากภายนอก.

ส่งคืนผลลัพธ์การ lookup และจำนวนทั้งหมดใน pipeline เดียวด้วย executeLookupAndCount.

ใช้ sequence token ของ Atlas Search และ automated embedding query ของ Vector Search.

stage aggregation ใหม่ที่ Driver ให้มาสามารถเชื่อมต่อได้โดยตรงด้วย aggregation().stage(Bson) หรือ stage(Bson) ของ Search/Vector.

หากต้องการความเป็นอะตอมระหว่างการดำเนินการ DSL ให้ระบุขอบเขต transaction ของ ClientSession ด้วย getTxJob(...).