ViennaNET: ชุดไลบรารีสำหรับแบ็กเอนด์ ส่วนที่ 2

ชุมชนนักพัฒนา Raiffeisenbank .NET ยังคงตรวจสอบเนื้อหาของ ViennaNET โดยย่อต่อไป เกี่ยวกับวิธีการและเหตุผลที่เรามาถึงสิ่งนี้ คุณสามารถอ่านส่วนแรกได้.

ในบทความนี้ เราจะพูดถึงไลบรารีที่ยังไม่ได้รับการพิจารณาสำหรับการทำงานกับธุรกรรมแบบกระจาย คิว และฐานข้อมูล ซึ่งสามารถพบได้ในพื้นที่เก็บข้อมูล GitHub ของเรา (แหล่งที่มาอยู่ที่นี่) และ แพ็คเกจ Nuget ที่นี่.

ViennaNET: ชุดไลบรารีสำหรับแบ็กเอนด์ ส่วนที่ 2

ViennaNET.Sagas

เมื่อโปรเจ็กต์สลับไปใช้สถาปัตยกรรม DDD และไมโครเซอร์วิส เมื่อตรรกะทางธุรกิจถูกกระจายไปยังบริการต่างๆ ปัญหาก็เกิดขึ้นที่เกี่ยวข้องกับความจำเป็นในการใช้กลไกธุรกรรมแบบกระจาย เนื่องจากหลายสถานการณ์มักส่งผลกระทบต่อหลายโดเมนในคราวเดียว คุณสามารถทำความคุ้นเคยกับกลไกดังกล่าวได้อย่างละเอียดมากขึ้น เช่น ในหนังสือ "Microservices Patterns" โดย Chris Richardson.

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

การใช้งานของเรายังคงดำเนินการในรูปแบบพื้นฐานและไม่เกี่ยวข้องกับการใช้วิธีการโต้ตอบกับบริการอื่น ๆ การใช้งานไม่ยาก เพียงสร้างคลาสสืบทอดของคลาสนามธรรมพื้นฐาน SagaBase<T> โดยที่ T คือคลาสบริบทของคุณ ซึ่งคุณสามารถจัดเก็บข้อมูลเริ่มต้นที่จำเป็นสำหรับการทำงานของ Saga รวมถึงผลลัพธ์ระดับกลางบางส่วนได้ อินสแตนซ์บริบทจะถูกส่งผ่านไปยังทุกขั้นตอนระหว่างการดำเนินการ Saga นั้นเป็นคลาสไร้สัญชาติ ดังนั้นจึงสามารถวางอินสแตนซ์ใน DI เป็น Singleton เพื่อรับการขึ้นต่อกันที่จำเป็น

โฆษณาตัวอย่าง:

public class ExampleSaga : SagaBase<ExampleContext>
{
  public ExampleSaga()
  {
    Step("Step 1")
      .WithAction(c => ...)
      .WithCompensation(c => ...);
	
    AsyncStep("Step 2")
      .WithAction(async c => ...);
  }
}

ตัวอย่างการโทร:

var saga = new ExampleSaga();
var context = new ExampleContext();
await saga.Execute(context);

สามารถดูตัวอย่างการใช้งานต่างๆ แบบเต็มได้ ที่นี่ และประกอบกับ การทดสอบ.

ViennaNET.อรม.*

ชุดไลบรารีสำหรับการทำงานกับฐานข้อมูลต่างๆ ผ่าน Nhibernate เราใช้แนวทาง DB-First โดยใช้ Liquibase ดังนั้นจึงมีเพียงฟังก์ชันการทำงานสำหรับการทำงานกับข้อมูลในฐานข้อมูลสำเร็จรูปเท่านั้น

ViennaNET.Orm.Seedwork и ViennaNET.Orm – แอสเซมบลีหลักที่มีอินเทอร์เฟซพื้นฐานและการใช้งานตามลำดับ เรามาดูรายละเอียดเนื้อหากันดีกว่า

อินเตอร์เฟซ IEntityFactoryService และการนำไปปฏิบัติ EntityFactoryService เป็นจุดเริ่มต้นหลักสำหรับการทำงานกับฐานข้อมูลเนื่องจากมีการสร้างหน่วยงานที่เก็บข้อมูลสำหรับการทำงานกับเอนทิตีเฉพาะตลอดจนผู้ดำเนินการคำสั่งและการสืบค้น SQL โดยตรง บางครั้งการจำกัดความสามารถของคลาสสำหรับการทำงานกับฐานข้อมูลก็สะดวก เช่น เพื่อให้ความสามารถในการอ่านข้อมูลเท่านั้น สำหรับกรณีดังกล่าว IEntityFactoryService มีบรรพบุรุษ - อินเทอร์เฟซ IEntityRepositoryFactoryซึ่งประกาศเฉพาะวิธีการสร้างที่เก็บข้อมูลเท่านั้น

ในการเข้าถึงฐานข้อมูลโดยตรง จะใช้กลไกของผู้ให้บริการ แต่ละ DBMS ที่เราใช้ในทีมของเรามีการใช้งานของตัวเอง: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

ในเวลาเดียวกัน ผู้ให้บริการหลายรายสามารถลงทะเบียนในแอปพลิเคชันเดียวในเวลาเดียวกันได้ ซึ่งช่วยให้ดำเนินการย้ายข้อมูลแบบทีละขั้นตอนจากภายในกรอบของบริการเดียวได้โดยไม่มีค่าใช้จ่ายใดๆ สำหรับการปรับเปลี่ยนโครงสร้างพื้นฐาน DBMS หนึ่งไปยังอีกอันหนึ่ง กลไกในการเลือกการเชื่อมต่อที่ต้องการและดังนั้นผู้ให้บริการสำหรับคลาสเอนทิตีเฉพาะ (ซึ่งมีการเขียนการแมปกับตารางฐานข้อมูล) จะถูกนำไปใช้ผ่านการลงทะเบียนเอนทิตีในคลาส BoundedContext (ประกอบด้วยวิธีการสำหรับการลงทะเบียนเอนทิตีโดเมน) หรือตัวตายตัวแทน ApplicationContext (ประกอบด้วยวิธีการสำหรับการลงทะเบียนเอนทิตีแอปพลิเคชัน คำขอโดยตรง และคำสั่ง) โดยที่ตัวระบุการเชื่อมต่อจากการกำหนดค่าได้รับการยอมรับเป็นอาร์กิวเมนต์:

"db": [
  {
    "nick": "mssql_connection",
    "dbServerType": "MSSQL",
    "ConnectionString": "...",
    "useCallContext": true
  },
  {
    "nick": "oracle_connection",
    "dbServerType": "Oracle",
    "ConnectionString": "..."
  }
],

ตัวอย่างบริบทของแอปพลิเคชัน:

internal sealed class DbContext : ApplicationContext
{
  public DbContext()
  {
    AddEntity<SomeEntity>("mssql_connection");
    AddEntity<MigratedSomeEntity>("oracle_connection");
    AddEntity<AnotherEntity>("oracle_connection");
  }
}

หากไม่ได้ระบุ ID การเชื่อมต่อ การเชื่อมต่อที่ชื่อ "ค่าเริ่มต้น" จะถูกนำมาใช้

การแมปโดยตรงของเอนทิตีกับตารางฐานข้อมูลถูกนำไปใช้โดยใช้เครื่องมือ NHibernate มาตรฐาน คุณสามารถใช้คำอธิบายทั้งผ่านไฟล์ xml และผ่านคลาส เพื่อความสะดวกในการเขียน Stub Repositories ใน Unit Tests มี Library ไว้ด้วย ViennaNET.TestUtils.Orm.

สามารถดูตัวอย่างการใช้ ViennaNET.Orm.* ทั้งหมดได้ ที่นี่.

ViennaNET.ข้อความ.*

ชุดไลบรารีสำหรับการทำงานกับคิว

ในการทำงานกับคิว มีการเลือกวิธีการเดียวกันกับ DBMS ต่างๆ กล่าวคือ วิธีการแบบรวมที่เป็นไปได้สูงสุดในแง่ของการทำงานกับไลบรารี โดยไม่คำนึงถึงตัวจัดการคิวที่ใช้ ห้องสมุด ViennaNET.Messaging มีหน้าที่รับผิดชอบในการรวมกลุ่มนี้อย่างชัดเจนและ ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue มีการใช้งานอะแดปเตอร์สำหรับ IBM MQ, RabbitMQ และ Kafka ตามลำดับ

เมื่อทำงานกับคิว มีสองกระบวนการ: การรับข้อความและการส่ง

พิจารณารับ. มี 2 ​​ตัวเลือกที่นี่: สำหรับการฟังอย่างต่อเนื่องและการรับข้อความเดียว หากต้องการฟังคิวอย่างต่อเนื่อง คุณต้องอธิบายคลาสตัวประมวลผลที่สืบทอดมาก่อน IMessageProcessorซึ่งจะรับผิดชอบในการประมวลผลข้อความที่เข้ามา ถัดไปจะต้อง "เชื่อมโยง" ไปยังคิวเฉพาะ ซึ่งทำได้โดยการลงทะเบียน IQueueReactorFactory ระบุตัวระบุคิวจากการกำหนดค่า:

"messaging": {
    "ApplicationName": "MyApplication"
},
"rabbitmq": {
    "queues": [
      {
        "id": "myQueue",
        "queuename": "lalala",
        ...
      }
    ]
},

ตัวอย่างการเริ่มฟัง:

_queueReactorFactory.Register<MyMessageProcessor>("myQueue");
var queueReactor = queueReactorFactory.CreateQueueReactor("myQueue");
queueReactor.StartProcessing();

จากนั้นเมื่อบริการเริ่มต้นและเรียกใช้เมธอดเพื่อเริ่มฟัง ข้อความทั้งหมดจากคิวที่ระบุจะไปที่ตัวประมวลผลที่เกี่ยวข้อง

เพื่อรับข้อความเดียวในอินเทอร์เฟซของโรงงาน IMessagingComponentFactory มีวิธีการ CreateMessageReceiverซึ่งจะสร้างผู้รับรอข้อความจากคิวที่ระบุ:

using (var receiver = _messagingComponentFactory.CreateMessageReceiver<TestMessage>("myQueue"))
{
    var message = receiver.Receive();
}

เพื่อส่งข้อความ คุณต้องใช้แบบเดียวกัน IMessagingComponentFactory และสร้างผู้ส่งข้อความ:

using (var sender = _messagingComponentFactory.CreateMessageSender<MyMessage>("myQueue"))
{
    sender.SendMessage(new MyMessage { Value = ...});
}

มีตัวเลือกสำเร็จรูปสามตัวเลือกสำหรับซีเรียลไลซ์และดีซีเรียลไลซ์ข้อความ: แค่ข้อความ, XML และ JSON แต่หากจำเป็น คุณสามารถสร้างการใช้งานอินเทอร์เฟซของคุณเองได้อย่างง่ายดาย IMessageSerializer и IMessageDeserializer.

เราได้พยายามรักษาความสามารถเฉพาะตัวของผู้จัดการคิวแต่ละคน เช่น ViennaNET.Messaging.MQSeriesQueue ช่วยให้คุณส่งไม่เพียงแต่ข้อความเท่านั้น แต่ยังรวมถึงข้อความแบบไบต์และ ViennaNET.Messaging.RabbitMQQueue รองรับการกำหนดเส้นทางและการจัดคิวแบบทันที Wrapper อะแดปเตอร์ของเราสำหรับ RabbitMQ ยังใช้ RPC ที่มีลักษณะคล้ายกัน: เราส่งข้อความและรอการตอบกลับจากคิวชั่วคราวพิเศษ ซึ่งสร้างขึ้นสำหรับข้อความตอบกลับเดียวเท่านั้น

ที่นี่ ตัวอย่างการใช้คิวที่มีความแตกต่างในการเชื่อมต่อพื้นฐาน.

ViennaNET.CallContext

เราใช้คิวไม่เพียงแต่สำหรับการบูรณาการระหว่างระบบที่แตกต่างกัน แต่ยังสำหรับการสื่อสารระหว่างไมโครเซอร์วิสของแอปพลิเคชันเดียวกัน เช่น ภายในเทพนิยาย สิ่งนี้นำไปสู่ความจำเป็นในการส่งข้อมูลเสริม เช่น การเข้าสู่ระบบของผู้ใช้ ตัวระบุคำขอสำหรับการบันทึกจากต้นทางถึงปลายทาง ที่อยู่ IP ต้นทาง และข้อมูลการอนุญาต เพื่อดำเนินการส่งต่อข้อมูลนี้ เราได้พัฒนาห้องสมุด ViennaNET.CallContextซึ่งช่วยให้คุณสามารถจัดเก็บข้อมูลจากการร้องขอที่เข้าสู่บริการได้ ในกรณีนี้ วิธีการส่งคำขอผ่านคิวหรือผ่าน Http นั้นไม่สำคัญ จากนั้น ก่อนที่จะส่งคำขอหรือข้อความขาออก ข้อมูลจะถูกดึงมาจากบริบทและวางไว้ในส่วนหัว ดังนั้นบริการถัดไปจะได้รับข้อมูลเสริมและจัดการในลักษณะเดียวกัน

ขอบคุณสำหรับความสนใจของคุณ เราหวังว่าจะได้รับความคิดเห็นของคุณและดึงคำขอ!

ที่มา: will.com

เพิ่มความคิดเห็น