+1

Từ Tầm nhìn đến Cỗ máy - Bản thiết kế Thực thi cho CScaf Core - CScaf #2.2

Do giới hạn của Viblo là 70,000 từ mỗi bài nên bài sẽ được chia làm nhiều phần và mỗi ngày sẽ đăng một phần.

Cuộc thảo luận về tương lai của ngành phát triển phần mềm bắt đầu từ đây. Mọi ý kiến của bạn, dù là đồng tình hay thách thức, đều là một phần quan trọng của cuộc đối thoại này. Hãy tham gia bằng cách bình luận hoặc gửi email về contact@axx83.com.

Chào mừng bạn đến với sự khởi đầu của một kỷ nguyên mới.

3. CScaf.Inator: Runtime Phổ quát (The Universal Runtime)


Nếu Platypus là bản thiết kế và Wcd là công trường xây dựng, thì Inator chính là hệ thống điện, nước, và mạng internet được thổi vào, biến những tòa nhà tĩnh do Wcd dựng nên thành một thành phố sống động, nhộn nhịp, và kết nối với nhau. Nó là sự sống. Nó là hành động. Nó là thứ chạy đằng sau hậu trường, đảm bảo rằng toàn bộ hệ thống hữu cơ phức tạp này có thể hít thở và giao tiếp.

"Nó đơn giản là một... Inator"

Có một triết lý đơn giản đằng sau cái tên này. Tại sao nó lại được gọi là Inator? Bởi vì nó là một cỗ máy tôi đã chế tạo ra để làm một việc gì đó. Và trong vũ trụ của CScaf, bất cứ thứ gì được tạo ra để thực thi một nhiệm vụ, để biến ý định thành hành động, thì nó là một "-inator". Một runtime? Nó là một Runtime-inator. Một bộ host? Nó là một Host-inator. Rút gọn lại, nó đơn giản là Inator.

Mục đích của nó được gắn liền với chính cái tên. Nó không phải là một thư viện mà bạn gọi. Nó là một môi trường mà code của bạn sống bên trong. Khi Wcd hoàn thành việc build một Space, nó không chỉ tạo ra một file DLL chứa logic nghiệp vụ của bạn. Nó bọc file DLL đó bên trong một bộ khung mạnh mẽ, và bộ khung đó chính là Inator. Do đó, mỗi file thực thi được sinh ra, về bản chất, là một instance của Inator được cấu hình để "host" một Space cụ thể.

Nó là cỗ máy toàn năng đảm nhận toàn bộ gánh nặng của việc vận hành một hệ thống phân tán. Nó là điểm khởi đầu, là người quản lý, là tổng đài viên, và là người đưa thư. Nó tồn tại để code nghiệp vụ của bạn có thể tập trung vào việc nó làm tốt nhất: giải quyết vấn đề kinh doanh, trong khi nó lo phần còn lại.

Giải phẫu Sơ bộ một Cỗ máy Inator

Mỗi Inator instance, giống như bất kỳ phát minh vĩ đại (và có thể hơi thừa thãi) nào, được cấu thành từ nhiều bộ phận chuyên biệt hoạt động hài hòa với nhau. Để hiểu nó, chúng ta hãy nhìn vào bản vẽ sơ bộ.

  • Nút "ON" Lớn Màu Đỏ (The Entry Point): Mọi cỗ máy đều cần một công tắc nguồn. Đây chính là file Program.MainWcd đã cẩn thận đặt vào mỗi Space. Khi bạn chạy một file thực thi, bạn thực chất đang nhấn vào nút này, khởi động toàn bộ cỗ máy Inator và cung cấp năng lượng cho Space bên trong nó.
  • Bộ phận Điều khiển Sự sống (The Space Lifecycle Manager): Mỗi Inator chỉ chịu trách nhiệm cho một Space duy nhất mà nó đang host. Bộ phận này giống như hệ thống hỗ trợ sự sống của tòa nhà, quản lý mọi tài nguyên cục bộ: các luồng (threads), bộ nhớ, và đảm bảo Space hoạt động ổn định trong môi trường của nó.
  • Harmony - Hệ thống Thần kinh Phi tập trung (The Decentralized Fabric): Đây là bộ phận kỳ diệu và quan trọng nhất. Harmony không phải là một "máy chủ trung tâm" ở đâu đó. Nó là một phần của MỌI Inator. Nó là hệ thống thần kinh kết nối tất cả các cỗ máy Inator lại với nhau, cho phép chúng khám phá, trò chuyện, và phối hợp một cách liền mạch, tạo ra một ý thức tập thể cho toàn bộ hệ thống.
  • Blueford - Bộ não Logic (The Space Virtualization Engine): Nếu Harmony là hệ thần kinh, thì Blueford là bộ não chịu trách nhiệm về các quy tắc vật lý của vũ trụ CScaf. Nó là nền tảng mà Harmony dựa vào, định nghĩa một Space thực sự gì, ranh giới của nó ở đâu, và dữ liệu có thể di chuyển qua các ranh giới đó như thế nào. Đây cũng là nơi chứa Data Marshalling Engine, cỗ máy chịu trách nhiệm đóng gói và mở gói dữ liệu cho các chuyến đi giữa các Space.
  • Cổ họng và Tai - Lớp Giao tiếp (The Communication Transports): Cuối cùng, để giao tiếp, cỗ máy cần có miệng và tai. Lớp này cung cấp các cơ chế vật lý để gửi và nhận thông tin. Trong giai đoạn MVP, để giữ cho cỗ máy đơn giản nhất có thể, nó sẽ chỉ có một cặp "miệng-tai" duy nhất: MessagePack qua RESTful API.

Đây chỉ là cái nhìn tổng quan về các bộ phận hình thành nên một Inator. Trong các phần tiếp theo, chúng ta sẽ mang theo bộ dụng cụ và mổ xẻ chi tiết từng con ốc, từng sợi dây bên trong cỗ máy phức tạp này để hiểu rõ cách nó thực sự vận hành.

3.1. Ngưỡng Cửa (The Threshold)


Mọi cỗ máy, dù đơn giản hay phức tạp, đều cần một điểm để thế giới bên ngoài có thể tương tác với nó. Với một Space được host bởi Inator, điểm đó mang một hình hài rất quen thuộc: một file Program.cs với một hàm public static void Main(string[] args). Wcd đã cẩn thận đặt cánh cổng này vào mỗi Space mà nó xây dựng.

Nhưng đừng để sự quen thuộc đó đánh lừa bạn. Tôi không gọi nó là "Điểm Bắt đầu" (Entry Point) theo nghĩa truyền thống. Tôi gọi nó là "Ngưỡng Cửa" (The Threshold). Sự thay đổi tên gọi này không phải là một trò chơi chữ; nó là một lời tuyên bố về ý định, một nỗ lực có chủ đích nhằm phá vỡ cách chúng ta nhìn nhận về một "ứng dụng" và tạo tiền đề cho những thứ hữu cơ hơn.

Lý do cho sự Tái định nghĩa: Vòng đời Dữ liệu Hữu cơ

Vậy tại sao lại cần một sự thay đổi triệt để như vậy? Bởi vì mô hình "Entry Point" truyền thống được sinh ra để phục vụ một mô hình dữ liệu và tác vụ cũng rất truyền thống: tuyến tính và có thể dự đoán được. Trong một dịch vụ web, dữ liệu đến trong một request, được xử lý, và biến mất cùng với response. Nó có vòng đời ngắn ngủi và được kiểm soát chặt chẽ.

CScaf được thiết kế cho một thực tại hoàn toàn khác, một thực tại của vòng đời dữ liệu hữu cơ. Dữ liệu và trạng thái bên trong một Space không bị ràng buộc bởi một request đơn lẻ. Nó có thể tồn tại dai dẳng, biến đổi từ từ, và vòng đời của nó được quyết định bởi ý thức tập thể của toàn bộ hệ thống.

Hãy tưởng tượng một Space tính toán vật lý. Nó có thể được một Space khác yêu cầu khởi tạo một mô phỏng vũ trụ phức tạp và giữ toàn bộ trạng thái đó trong bộ nhớ. Nó sẽ tiếp tục chạy mô phỏng đó, tiêu tốn tài nguyên, trong nhiều giờ hoặc nhiều ngày, cho đến khi một Space thứ ba—có thể là một Space phân tích dữ liệu—gửi một tín hiệu bảo nó rằng "dữ liệu đã được thu thập đủ, hãy dừng mô phỏng và giải phóng bộ nhớ."

Để hỗ trợ một mô hình vận hành phi tuyến tính và khó đoán trước như vậy, chúng ta không cần một "điểm bắt đầu" cho một công việc. Chúng ta cần một "ngưỡng cửa" cho một thực thể tồn tại.

Một Bước tiến Logic, không phải một Cuộc cách mạng Xa vời

Cách nhìn nhận này không hề xa rời thực tế. Ngược lại, nó chính là bước đi logic tiếp theo của một xu hướng đã và đang diễn ra trong thế giới dịch vụ hiện đại. Các framework như ASP.NET Core đã nhận ra những hạn chế của mô hình request-response thuần túy và đã giới thiệu các khái niệm để cố gắng thoát khỏi nó:

  • Các Singleton Service: Đây là những nỗ lực đầu tiên để tạo ra trạng thái tồn tại dai dẳng trong suốt vòng đời của ứng dụng, không bị hủy đi sau mỗi request.
  • Các IHostedService / Background Service: Đây là một bước tiến xa hơn, cho phép chạy các luồng công việc dài hạn, hoàn toàn tách biệt khỏi pipeline request-response.

Những thứ này chính là những "tế bào" đầu tiên cố gắng sống một cuộc đời độc lập bên trong một cơ thể vẫn được thiết kế chủ yếu cho các tác vụ ngắn hạn. Vấn đề là, chúng vẫn thường được xem là những trường hợp đặc biệt, những "add-on" cho mô hình chính.

CScaf chỉ đơn giản là lật ngược mô hình đó lại. Nó tuyên bố rằng: trạng thái hữu cơ, dài hạn mới là tiêu chuẩn, là mặc định. Mô hình request-response chỉ là một trong nhiều loại tương tác có thể xảy ra trong hệ thống. "Ngưỡng Cửa" chính là sự hiện thực hóa ở cấp độ kiến trúc cho sự thay đổi triết lý này. Nó tạo ra một môi trường được thiết kế từ đầu để nuôi dưỡng các "tế bào" sống lâu, thay vì chỉ là một dây chuyền lắp ráp cho các tác vụ ngắn hạn.

Nhiệm vụ của Người Gác cổng

Khi đã hiểu rõ bối cảnh, vai trò của "Ngưỡng Cửa" trở nên cực kỳ rõ ràng. Nó không phải là người quản lý nhà máy. Nó là một người gác cổng canh giữ một khu vườn sinh học. Công việc của người gác cổng cực kỳ đơn giản nhưng vô cùng quan trọng:

  1. Mở khóa cổng (Khởi động tiến trình): Cho phép Space bắt đầu tồn tại.
  2. Bật đèn và hệ thống liên lạc (Khởi tạo Inator): Khởi động các bộ phận cốt lõi bên trong như BluefordHarmony.
  3. Mở radio và lắng nghe (Ra lệnh cho Harmony): Bắt đầu "lắng nghe" và cố gắng kết nối với các Space khác trong mạng lưới.
  4. Đứng gác (Treo tiến trình): Giữ cho cánh cổng luôn mở, giữ cho "tế bào" này sống và sẵn sàng nhận tín hiệu.

Người gác cổng không quyết định công việc gì sẽ diễn ra bên trong. Anh ta không biết và cũng không cần biết. Anh ta chỉ đảm bảo rằng khu vườn đã sẵn sàng để trở thành một phần của một hệ sinh thái lớn hơn, một hệ sinh thái có khả năng nuôi dưỡng những vòng đời phức tạp và không thể đoán trước.

3.2. Bộ phận Điều khiển Sự sống (The Space Lifecycle Manager)


Sau khi "Ngưỡng Cửa" được bước qua và Space đã thức tỉnh, một câu hỏi quan trọng nảy sinh: Điều gì thực sự quản lý sự sống bên trong nó? Ai quyết định một mẩu dữ liệu tồn tại trong bao lâu? Ai dọn dẹp khi công việc đã xong? Câu trả lời nằm ở "Bộ phận Điều khiển Sự sống" của Inator.

Nhưng để hiểu được nó, chúng ta phải phân tách nó thành hai phần rõ rệt: phần vật lý—cơ sở hạ tầng quen thuộc—và phần logic—nơi cuộc cách mạng thực sự diễn ra.

Phần Vật lý: Nền tảng CLR Quen thuộc

Hãy bắt đầu với những gì quen thuộc. Trong giai đoạn MVP, "Bộ phận Điều khiển Sự sống" không cố gắng phát minh lại bánh xe ở cấp độ thấp. Mọi khía cạnh vật lý của Space đều được ủy thác hoàn toàn cho .NET CLR (Common Language Runtime) truyền thống xử lý:

  • Quản lý Luồng (Threads): Inator không có một bộ lập lịch luồng (thread scheduler) riêng. Nó chỉ đơn giản yêu cầu CLR tạo ra các luồng khi cần thiết.
  • Quản lý Bộ nhớ (Memory): Việc cấp phát và thu hồi bộ nhớ cho các đối tượng vẫn do trình quản lý bộ nhớ của .NET đảm nhiệm.
  • Thu gom Rác (Garbage Collection): Trình thu gom rác (GC) tiêu chuẩn của .NET vẫn là người chịu trách nhiệm chính trong việc dọn dẹp các đối tượng không còn được tham chiếu bên trong Space đó.
  • Toàn bộ là Code Managed: Để giữ cho mọi thứ đơn giản và dễ dự đoán, MVP sẽ không đụng đến code unmanaged hay các tối ưu hóa cấp thấp.

Tóm lại, về mặt vật lý, một Space trong giai đoạn MVP hoạt động giống hệt như bất kỳ ứng dụng .NET nào khác. Sự đổi mới không nằm ở cách chúng ta quản lý bytethread, mà nằm ở cách chúng ta định nghĩa lại vòng đời của thông tin.

Phần Logic: Sự ra đời của Dữ liệu Hữu cơ & Vòng đời Bán-Đường ống (Semi-Pipeline Lifecycle)

Đây là nơi mọi thứ thay đổi. Mô hình giao tiếp dịch vụ truyền thống giống như một dây chuyền lắp ráp (pipeline): nguyên liệu (request) đi vào, được xử lý qua các trạm, và thành phẩm (response) đi ra. Mọi thứ liên quan đến công việc đó sẽ bị loại bỏ khi dây chuyền dừng lại.

Inator khai tử một phần mô hình này và giới thiệu khái niệm Vòng đời Bán-Đường ống (Semi-Pipeline), được xây dựng trên một nguyên tắc cốt lõi: Dữ liệu Bền bỉ trong Space (Space-Persistent Data).

Trong mô hình này, một Space không chỉ là một trạm xử lý. Nó có thể là một cái xưởng làm việc. Dữ liệu không chỉ đi qua xưởng; nó có thể được gửi đến xưởng và để lại ở đó. Chủ xưởng sẽ giữ nó trên bàn làm việc, và lần sau khi có yêu cầu, họ sẽ tiếp tục làm việc trên chính mẩu dữ liệu đó thay vì yêu cầu gửi lại từ đầu.

Ở góc nhìn của một Space đơn lẻ (một cái xưởng), việc phải giữ lại một đống đồ đạc của người khác có vẻ là một sự vô lý và tốn tài nguyên. Nhưng khi nhìn từ góc độ của toàn bộ hệ thống (cả thành phố), việc này lại là lẽ tự nhiên để mọi thứ được trôi chảy. Các cơ chế caching cross-service phức tạp, thứ mà trước đây chúng ta phải tự xây dựng một cách khó khăn, giờ đây trở thành một biểu hiện mặc định của toàn bộ kiến trúc.

Hãy xem hai ví dụ để thấy sức mạnh của mô hình này:

  • Ví dụ 1: Mô phỏng Vật lý Bạn đang ở Space "GameLogic". Bạn muốn mô phỏng chuyển động của một đối tượng. Thay vì mỗi frame lại gửi toàn bộ trạng thái (vị trí, vận tốc, gia tốc, khối lượng...) của đối tượng sang Space "Physics", bạn chỉ làm điều đó một lần duy nhất:

    // Gửi đối tượng đến "Physics" và ra lệnh "giữ nó ở đó"
    await SAction.Swait<bool>("Physics", () => {
        var myObject = new PhysicsObject(...);
        // Một API của Inator sẽ cho phép "pin" đối tượng này lại
        Inator.CurrentSpace.PinObject("Player1_Car", myObject);
        return true;
    });
    
    

    Từ giờ trở đi, đối tượng đó đang sống bên trong Space "Physics". Ở các frame sau, lời gọi của bạn trở nên cực kỳ nhẹ nhàng:

    // Ra lệnh cho "Physics" chạy một tick mô phỏng trên đối tượng đã có sẵn
    var newPosition = await SAction.Swait<Vector3>("Physics", () => {
        var myObject = Inator.CurrentSpace.GetObject<PhysicsObject>("Player1_Car");
        myObject.SimulateTick();
        return myObject.Position;
    });
    
    
  • Ví dụ 2: Giữ Ngữ cảnh Người dùng Một request đến Space "ApiGateway". Bạn xác thực người dùng và cần gọi sang Space "Billing" để lấy lịch sử giao dịch.

    // Lời gọi đầu tiên, gửi đầy đủ thông tin User
    await SAction.Swait<bool>("Billing", () => {
        var user = GetUserFromRequest(); // Lấy user object
        // Ra lệnh cho Billing giữ lại ngữ cảnh user này cho luồng hiện tại
        Inator.CurrentSpace.SetContext("CurrentUser", user);
        return true;
    });
    
    // Ngay sau đó, trong cùng luồng logic, bạn gọi sang Billing lần nữa
    var history = await SAction.Swait<List<Transaction>>("Billing", () => {
        // KHÔNG CẦN gửi lại user! Cứ thế dùng thẳng.
        var user = Inator.CurrentSpace.GetContext<User>("CurrentUser");
        return BillingService.GetHistoryFor(user.Id);
    });
    
    

Mô hình này được hiện thực hóa bằng cách trao một quyền năng (và cả một trách nhiệm lớn) cho nhà phát triển:

  1. Quyền Năng của Người Gọi: Người dùng (tức Space A) có toàn quyền quyết định sau khi một Swait tới Space B kết thúc, dữ liệu và trạng thái được tạo ra ở Space B sẽ tiếp tục tồn tại hay bị hủy đi.
  2. Trách nhiệm Quản lý Bộ nhớ: Trong giai đoạn MVP, quyền năng này đi kèm với một trách nhiệm lớn. Nếu bạn "pin" một đối tượng trong một Space khác và quên không bao giờ "unpin" nó, bạn sẽ tạo ra một memory leak xuyên hệ thống.

Đây là một sự đánh đổi có chủ đích. Trước mắt, CScaf trao toàn bộ quyền quản lý bộ nhớ và vòng đời logic cho lập trình viên. Trong tương lai, một cơ chế SpaceGC (Garbage Collector liên-Space) sẽ được giới thiệu để tự động hóa việc này, mang lại sức mạnh của mô hình dữ liệu hữu cơ mà không đi kèm với gánh nặng quản lý thủ công

Lớp trừu tượng khổng lồ cần có

Sức mạnh to lớn của việc tạo ra trạng thái bền bỉ xuyên ranh giới Space ngay lập tức đặt ra một câu hỏi sâu sắc và phức tạp: Nếu Space A "pin" một đối tượng trong Space B, thì Space A thực sự đang nắm giữ cái gì? Nó không thể là một con trỏ bộ nhớ trực tiếp; đó là điều bất khả thi trong một hệ thống phân tán.


Thứ mà Space A nắm giữ là một khái niệm hoàn toàn mới: một Con trỏ Đối tượng Ảo (Virtual Object Pointer) hay một Tay nắm Xuyên-Space (Cross-Space Handle). Nó không phải là dữ liệu. Nó là một "vé gửi đồ", một định danh duy nhất trên toàn hệ thống cho phép Space A, vào một thời điểm nào đó trong tương lai, có thể nói với Space B: "Hãy đưa cho tôi cái đồ vật có mã số XYZ mà tôi đã gửi anh giữ."

Bản thân mô hình này, dù cực kỳ mạnh mẽ, cũng mở ra một chiếc hộp Pandora của sự phức tạp. Nó đòi hỏi một cơ chế quản lý hoàn toàn mới với quy mô chưa từng có. Chúng ta không còn có thể dựa vào các cơ chế quản lý trạng thái đơn giản của một tiến trình đơn lẻ. Chúng ta cần một thứ gì đó có thể hoạt động trong một không gian liên-Space đầy hỗn loạn, nơi các thông điệp có thể bị trễ, các Space có thể bị sập, và hàng triệu "tay nắm ảo" có thể tồn tại đồng thời.

Việc thiết kế chi tiết cơ chế này sẽ là chủ đề của một bài thảo luận chuyên sâu trong tương lai. Tuy nhiên, tôi có thể gợi ý trước về con đường để tìm ra lời giải:

Hãy nhìn vào cách các hệ thống hiện đại quản lý ngữ cảnh luồng truyền thống. Lấy ví dụ như context của GoLang hay AsyncLocal<T> của .NET. Chúng là những công cụ tuyệt vời để truyền tải trạng thái (như ID request, thông tin xác thực) xuống một chuỗi lời gọi bên trong một tiến trình duy nhất.

Bây giờ, hãy tưởng tượng lấy khái niệm đó, xé nó ra khỏi những giới hạn chật hẹp của một process, và kéo dãn nó ra trên toàn bộ một mạng lưới. Hãy biến nó thành bất đồng bộ, và trang bị cho nó khả năng chịu lỗi trước sự hỗn loạn của mạng. Khi đó, bạn sẽ có một công cụ tương tự như thứ mà CScaf cần: một Ngữ cảnh Thực thi Bất đồng bộ Phân tán (Distributed Asynchronous Execution Context).

Đây là một "siêu context" vô hình, tự động chảy theo mọi lời gọi Swait. Nó sẽ chịu trách nhiệm:

  1. Gán Định danh: Cung cấp một ID duy nhất cho mỗi "luồng logic" xuyên hệ thống.
  2. Theo dõi Tay nắm: Ghi nhận các "tay nắm ảo" được tạo ra trong luồng logic đó, liên kết chúng với Space nguồn và Space đích.
  3. Truyền tín hiệu Hủy/Timeout: Nếu Space nguồn quyết định hủy bỏ một chuỗi thao tác, context này sẽ mang tín hiệu đó đi khắp hệ thống, cho phép các Space khác dọn dẹp các đối tượng bền bỉ mà chúng đang giữ.

Việc xây dựng một hệ thống như vậy là một thách thức kỹ thuật to lớn, nhưng nó là mảnh ghép cuối cùng cần thiết để thuần hóa sức mạnh của mô hình dữ liệu hữu cơ, biến một thứ có vẻ hỗn loạn thành một hệ thống có trật tự và khả năng phục hồi.

Phương án Dự phòng (Fallback Plan): Quay về Bản chất của Tự động hóa

Cần phải thừa nhận rằng, việc hiện thực hóa đầy đủ mô hình "Dữ liệu Hữu cơ" và "Ngữ cảnh Thực thi Phân tán", ngay cả ở dạng cơ bản nhất, cũng là một thách thức kỹ thuật đáng kể cho giai đoạn MVP. Nó đòi hỏi phải giải quyết các vấn đề phức tạp về quản lý trạng thái, thu hồi tài nguyên, và xử lý lỗi trong một môi trường phân tán.

Do đó, nếu nguồn lực hoặc thời gian không cho phép, dự án có một phương án dự phòng (fallback plan) rõ ràng và thực dụng, một cách để co quy mô của MVP lại mà vẫn giữ được giá trị cốt lõi của nó.

Phương án đó là: tạm thời gác lại toàn bộ mô hình dữ liệu hữu cơ.

Trong kịch bản này, chúng ta sẽ tạm thời đối xử với mọi lời gọi SAction.Swait như những lời gọi API (RPC - Remote Procedure Call) truyền thống, không hơn không kém.

  • Không Semi-pipeline: Mỗi lời gọi Swait sẽ là một vòng đời khép kín. Dữ liệu đi vào, được xử lý, và dữ liệu đi ra.
  • Không SPD (Space-Persistent Data): Mọi trạng thái được tạo ra trong Space đích để phục vụ cho một lời gọi sẽ được hủy bỏ ngay khi lời gọi đó kết thúc. Space sẽ trở lại trạng thái "không biết gì" (stateless) đối với lời gọi đó, giống hệt như một controller trong một API web truyền thống.
  • Thuần túy là API call: Inator sẽ vẫn thực hiện việc đóng gói dữ liệu bằng MessagePack và gửi nó qua RESTful API, nhưng nó sẽ không cung cấp bất kỳ cơ chế nào để "pin" hay "giữ lại" đối tượng.

Tại sao Phương án này Vẫn là một Thành công Lớn?

Thoạt nghe, đây có vẻ là một sự thụt lùi đáng kể. Tuy nhiên, ngay cả khi bị co lại ở quy mô này, MVP vẫn sẽ đạt được một thành tựu đột phá và cực kỳ có giá trị:

Tự động hóa hoàn toàn việc tạo ra và kết nối một hệ thống microservices từ một codebase duy nhất.

Chỉ riêng việc đó đã là một thành công vang dội. Hãy tưởng tượng một thế giới nơi nhà phát triển có thể:

  1. Viết code trong một "siêu project" duy nhất.
  2. Định nghĩa ranh giới dịch vụ bằng một Attribute đơn giản.
  3. Thực hiện một lời gọi xuyên dịch vụ bằng một cú pháp SAction.Swait duy nhất.
  4. Nhấn "Build".

Wcd cùng Inator sẽ tự động lo phần còn lại: tự động tạo DTO, tự động sinh ra client và server, tự động serialization/deserialization, tự động thiết lập endpoint. Toàn bộ sự phức tạp của việc xây dựng và duy trì giao tiếp API giữa các dịch vụ sẽ hoàn toàn biến mất, được trừu tượng hóa hoàn toàn bởi công cụ.

Ngay cả khi không có dữ liệu hữu cơ, việc loại bỏ được gánh nặng khổng lồ này đã là một bước tiến vượt bậc về năng suất và khả năng bảo trì. Nó vẫn sẽ chứng minh được giá trị cốt lõi của mô hình "meta-build" và tạo ra một nền tảng vững chắc để từ đó, chúng ta có thể từng bước xây dựng lại các tính năng nâng cao hơn như dữ liệu bền bỉ trong các phiên bản tương lai. Phương án này đảm bảo rằng dự án có một con đường dẫn đến một kết quả có giá trị, bất kể những thách thức kỹ thuật gặp phải trên đường đi.

3.3. Harmony - Hệ thống Thần kinh Phi tập trung (The Decentralized Fabric)

Khi một Space đã bước qua "Ngưỡng Cửa" và được "Bộ phận Điều khiển Sự sống" nuôi dưỡng, nó vẫn chỉ là một tế bào đơn độc. Để trở thành một phần của một cơ thể sống, nó cần phải được kết nối vào hệ thống thần kinh. Trong CScaf, hệ thống thần kinh đó được gọi là Harmony.

Harmony là câu trả lời cho câu hỏi cơ bản nhất của mọi hệ thống phân tán: "Làm thế nào để các thành phần có thể tìm thấy và nói chuyện với nhau một cách đáng tin cậy, mà không cần một ông chủ ra lệnh?"

Nó không phải là một node điều phối trung tâm, không phải là một "service discovery" server mà bạn phải triển khai riêng. Harmony là một cơ chế, một bộ quy tắc ứng xử được nhúng vào bên trong MỌI Inator instance. Nó là một phần DNA của hệ thống, cho phép các Space tự tổ chức thành một mạng lưới ngang hàng (peer-to-peer) có khả năng phục hồi cao.

Để làm được điều này, Harmony không phát minh ra những khái niệm cao siêu. Thay vào đó, nó học hỏi và áp dụng một cách thông minh những mô hình đã được thử thách qua thời gian từ thế giới của các hệ thống phân tán quy mô lớn.

Vấn đề đầu tiên cần giải quyết là làm thế nào một Space mới khởi động có thể biết được những Space nào khác đang tồn tại trong hệ thống. Harmony giải quyết vấn đề này bằng cách sử dụng một trong những mô hình phi tập trung mạnh mẽ và đáng tin cậy nhất: Gossip Protocol (Giao thức Tin đồn).

Đây là một kỹ thuật đã được sử dụng và chứng minh trong các hệ thống hàng đầu như Apache Cassandra hay Consul. Ý tưởng của nó đơn giản một cách đáng ngạc nhiên, mô phỏng cách tin đồn lan truyền trong một ngôi làng:

  1. Khởi đầu (The Seed): Khi một Inator instance (Space A) khởi động, nó chỉ cần biết địa chỉ của một hoặc một vài "seed node" khác trong hệ thống. Danh sách seed node này có thể được cung cấp qua một file cấu hình đơn giản.
  2. Lời chào hỏi đầu tiên: Space A sẽ liên lạc với một seed node và nói: "Chào, tôi là Space A, đang chạy ở địa chỉ này. Anh biết những ai khác không?"
  3. Trao đổi Danh bạ: Seed node sẽ ghi nhận sự tồn tại của Space A, và sau đó gửi lại cho Space A toàn bộ "danh bạ" của nó – tức là danh sách tất cả các Space khác mà nó biết, cùng với trạng thái của chúng (còn sống, đã chết).
  4. Lan truyền Tin đồn: Từ thời điểm đó trở đi, cứ sau vài giây, Space A sẽ ngẫu nhiên chọn một Space khác từ danh bạ của mình (Space B) và hai bên sẽ trao đổi toàn bộ danh bạ cho nhau. Nếu Space A biết một điều gì đó mà Space B chưa biết (ví dụ: Space C vừa mới tham gia), thông tin đó sẽ được lan truyền.

Chỉ sau một khoảng thời gian rất ngắn, nhờ vào sự lan truyền theo cấp số nhân của "tin đồn", mọi Space trong hệ thống sẽ có một cái nhìn gần như nhất quán về toàn bộ mạng lưới.

Ưu điểm của mô hình này là gì?

  • Hoàn toàn Phi tập trung: Không có một "máy chủ danh bạ" trung tâm nào. Nếu một Space chết đi, hệ thống vẫn tiếp tục hoạt động bình thường.
  • Khả năng Phục hồi Cao: Thông tin được nhân bản ở mọi nơi. Việc mất mát một vài node không làm ảnh hưởng đến nhận thức chung của hệ thống.
  • Tự động Liên kết: Một Space mới chỉ cần biết một địa chỉ duy nhất là có thể tự động tham gia và được cả hệ thống nhận biết.

Khi Space A đã biết địa chỉ của Space B, nó cần một cách để nói chuyện. Như đã quyết định trong giai đoạn MVP, kênh giao tiếp vật lý sẽ là MessagePack qua RESTful API. Tuy nhiên, chỉ gửi đi dữ liệu là chưa đủ. Làm thế nào để Space B có thể chắc chắn rằng bức thư nó nhận được từ Space A vẫn còn nguyên vẹn, không bị thay đổi trên đường đi do lỗi mạng hay một lý do nào khác?

Harmony một lần nữa lại áp dụng một kỹ thuật tiêu chuẩn: Checksum Dữ liệu.

  1. Niêm phong tại Nguồn: Trước khi Inator của Space A gửi đi một gói tin MessagePack, nó sẽ chạy gói tin đó qua một hàm băm (hashing function) tiêu chuẩn (ví dụ: SHA-256) để tạo ra một "dấu vân tay" duy nhất cho gói tin đó.
  2. Gửi kèm Dấu vân tay: Dấu vân tay này sẽ được đính kèm vào request dưới dạng một HTTP header (ví dụ: X-CScaf-Payload-Hash).
  3. Kiểm tra tại Đích: Khi Inator của Space B nhận được request, nó sẽ làm hai việc:
    • Tách riêng gói tin MessagePack ra.
    • Tự mình chạy gói tin đó qua cùng một hàm băm SHA-256.
    • So sánh kết quả của nó với "dấu vân tay" trong header.
  4. Chấp nhận hoặc Từ chối: Nếu hai dấu vân tay khớp nhau, Inator biết rằng gói tin là toàn vẹn và sẽ tiếp tục xử lý. Nếu chúng không khớp, nó sẽ ngay lập tức hủy bỏ request và báo lỗi, đảm bảo rằng dữ liệu bị hỏng không bao giờ lọt được vào logic nghiệp vụ.

Bằng cách kết hợp Gossip Protocol để khám phá và Checksum để đảm bảo tính toàn vẹn, Harmony tạo ra một tấm vải kết nối phi tập trung, mạnh mẽ, và đáng tin cậy. Nó không phải là ma thuật, mà là sự áp dụng kỷ luật của những kỹ thuật đã được chứng minh là hoạt động hiệu quả trong thế giới thực, được tích hợp liền mạch vào runtime để nhà phát triển không bao giờ phải bận tâm về chúng.

Sự đổi mới của mô hình

Nếu Harmony chỉ đơn giản là ghép nối Gossip Protocol và Checksum, vậy điều gì làm cho nó khác biệt với việc tự mình thiết lập một cụm Cassandra hay Consul?

Sự khác biệt nằm ở triết lý cốt lõi của CScaf: đơn giản hóa triệt để bằng cách đẩy sự phức tạp xuống nền tảng.

Trong các hệ thống truyền thống, Giao thức Tin đồn vẫn đòi hỏi một lượng cấu hình và thiết lập thủ công đáng kể. Bạn phải định nghĩa một danh sách các "seed node" một cách tường minh trong các file cấu hình YAML hoặc XML. Bạn phải quản lý các địa chỉ IP và port này. Việc thêm hoặc bớt một node vẫn thường yêu cầu phải cập nhật các file cấu hình đó. Về bản chất, bạn vẫn đang làm việc với các "máy chủ" và "địa chỉ".

CScaf đặt mục tiêu làm cho khái niệm về "địa chỉ gốc" hay "seed node" trở nên hoàn toàn vô hình đối với nhà phát triển. Nền tảng phải tự động xử lý việc này, khiến cho trải nghiệm trở nên liền mạch hơn, thậm chí còn "ma thuật" hơn cả Cassandra, bởi vì nó không yêu cầu một bước thiết lập thủ công nào cả. Làm thế nào để đạt được điều này? Bằng cách kết hợp code được sinh ra tự động và các mô hình tự phát hiện ở cấp độ mạng cục bộ.

Để hiện thực hóa tầm nhìn "không cần thiết lập" này ngay trong giai

đoạn MVP, Harmony sẽ áp dụng một kịch bản khởi động đơn giản nhưng hiệu quả, đặc biệt phù hợp với môi trường phát triển cục bộ (chạy nhiều Space trên cùng một máy).

  1. Lấy Port Ngẫu nhiên: Khi một Inator instance khởi động, thay vì cố gắng đọc một port đã được cấu hình trước, nó sẽ yêu cầu hệ điều hành cấp cho nó một port TCP trống ngẫu nhiên để lắng nghe các request API. Điều này ngay lập tức loại bỏ vấn đề xung đột port khi chạy nhiều Space trên cùng một máy.
  2. Khám phá qua Mạng Cục bộ (LAN Discovery): Sau khi đã có một port để lắng nghe, làm thế nào để nó có thể tìm thấy những Space khác? Harmony sẽ sử dụng một cơ chế khám phá ngang hàng trên mạng cục bộ, ví dụ như UDP Multicast.
    • Mỗi Inator instance khi khởi động sẽ bắt đầu lắng nghe trên một địa chỉ multicast và port UDP đã được định nghĩa trước (ví dụ: 239.0.0.1:8888).
    • Đồng thời, nó sẽ gửi đi một gói tin "beacon" (phao tín hiệu) đến chính địa chỉ multicast đó, thông báo rằng: "Chào cả làng, tôi là Space 'Billing', đang chạy ở địa chỉ 192.168.1.10:54321."
    • Tất cả các Inator instance khác đang lắng nghe trên kênh multicast đó sẽ nhận được gói tin này và ngay lập tức cập nhật "danh bạ" của chúng với thông tin về Space 'Billing'.
  3. Dự phòng với "Alpha Space": Mô hình UDP Multicast hoạt động rất tốt trong nhiều môi trường mạng, nhưng không phải tất cả. Để tăng cường sự ổn định, Wcd sẽ tự động áp dụng thêm một quy tắc dự phòng:
    • Trong quá trình build, Wcd sẽ tự động chọn ra một Space (ví dụ, Space đầu tiên theo thứ tự bảng chữ cái) và chỉ định nó là "Alpha Space".
    • Trong code được sinh ra cho tất cả các Space khác, Wcd sẽ nhúng cứng địa chỉ và một port mặc định cho Alpha Space này (ví dụ: localhost:5000).
    • Khi Alpha Space khởi động, nó sẽ cố gắng chiếm lấy port mặc định đó.
    • Khi các Space khác khởi động, ngoài việc lắng nghe multicast, chúng cũng sẽ thử kết nối trực tiếp đến địa chỉ của Alpha Space.

Sự kết hợp này tạo ra một hệ thống tự khởi động cực kỳ mạnh mẽ:

  • Trường hợp lý tưởng: UDP Multicast hoạt động, tất cả các Space tự tìm thấy nhau ngay lập tức mà không cần một điểm trung tâm nào.
  • Trường hợp dự phòng: Nếu Multicast bị chặn, tất cả các Space vẫn biết cách tìm đến Alpha Space, và Alpha Space sẽ đóng vai trò là "người kết nối" đầu tiên, chia sẻ danh bạ của nó cho những người đến sau, từ đó khởi động Giao thức Tin đồn.

Bằng cách này, nhà phát triển không cần phải làm gì cả. Họ chỉ cần chạy các file thực thi. Nền tảng sẽ tự lo phần còn lại. Sự phức tạp của việc thiết lập một cụm dịch vụ được ẩn giấu hoàn toàn, hiện thực hóa lời hứa về một trải nghiệm phát triển thực sự đơn giản và liền mạch.

3.4. Blueford - Bộ não Logic (The Space Virtualization Engine)


Nếu Harmony là hệ thống thần kinh, là mạng lưới truyền tin tức thời, thì Blueford chính là bộ não, là nơi chứa đựng các quy tắc vật lý của vũ trụ CScaf. Harmony biết cách gửi một thông điệp, nhưng chính Blueford mới là thứ định nghĩa một Space thực sự gì và dữ liệu có thể tồn tại và di chuyển giữa chúng như thế nào. Harmony phụ thuộc vào Blueford để hiểu được thế giới mà nó đang kết nối.

Tuy nhiên, trong giai-đoạn-này, bộ não này sẽ chỉ hoạt động ở một phần công suất rất nhỏ, tập trung vào một nhiệm vụ duy nhất và cấp bách nhất.

Lời hứa về Tương lai: "Hypervisor" cho các Space

Tầm nhìn dài hạn cho Blueford là cực kỳ tham vọng. Nó được thiết kế để trở thành một "hypervisor" cho các Space. Trong tương lai, nó sẽ là thành phần chịu trách nhiệm cho những khả năng đáng kinh ngạc nhất của hệ thống:

  • Nhận thức về Bản thân và Môi trường: Một Space sẽ có khả năng tự nhận thức được trạng thái của chính nó (mức sử dụng CPU, memory) và của toàn bộ hệ thống thông qua Blueford.
  • Tự động Mở rộng (Auto-Scaling): Dựa trên nhận thức đó, Blueford có thể ra quyết định tự động nhân bản một Space để đáp ứng tải tăng đột biến.
  • Tự động Triển khai (Auto-Deployment): Nó sẽ là nền tảng cho phép một hệ thống CScaf tự động triển khai các Space mới hoặc cập nhật các Space hiện có mà không cần sự can thiệp của con người.

Đây là một lĩnh vực nghiên cứu và phát triển khổng lồ. Nó là chìa khóa để tạo ra các hệ thống thực sự tự động và có khả năng phục hồi. Nhưng bây giờ chưa phải là lúc để phát triển nó. Giai đoạn MVP phải tập trung vào việc giải quyết những vấn đề nền tảng hơn.

Nhiệm vụ Duy nhất trong MVP: Trở thành Người Dịch giả Hoàn hảo

Trong giai đoạn MVP, Blueford gác lại mọi tham vọng về "hypervisor" và tập trung vào một công việc duy nhất, nhưng không kém phần quan trọng: xử lý dữ liệu liên-Space một cách mượt mà và hiệu quả nhất có thể. Nó đảm nhận vai trò của Data Marshalling Engine.

"Marshalling" là một thuật ngữ kỹ thuật để chỉ quá trình lấy một đối tượng phức tạp trong bộ nhớ (một class, một struct) và biến nó thành một chuỗi byte để có thể gửi qua mạng, sau đó tái tạo lại đối tượng đó một cách hoàn hảo ở phía bên kia. Đây là một trong những công việc tẻ nhạt và dễ gây lỗi nhất trong các hệ thống phân tán.

Blueford tự động hóa hoàn toàn quá trình này, hoạt động như một người dịch giả hoàn hảo, song hành cùng Wcd:

  1. Wcd (Lúc build): Như đã thảo luận, khi Wcd phân tích một lời gọi SAction.Swait, nó sẽ tạo ra các DTO tối ưu và các "công thức" dịch thuật (code serialization/deserialization) cho lời gọi đó. Nó chuẩn bị sẵn bản hướng dẫn chi tiết.
  2. Blueford (Lúc runtime): Khi lời gọi Swait thực sự được thực thi, Inator sẽ giao nhiệm vụ cho Blueford. Blueford lấy "công thức" mà Wcd đã chuẩn bị sẵn và thực hiện nó:
    • Space nguồn: Nó nhận đối tượng DTO, đọc công thức, và dịch nó thành một chuỗi byte.
    • Space đích: Nó nhận chuỗi byte, đọc công thức tương ứng, và tái tạo lại đối tượng DTO một cách chính xác.

Hướng tới SpaceWire: Một Giao thức Tốt hơn

Để thực hiện việc dịch thuật này trong giai đoạn MVP, Blueford sẽ sử dụng một cặp công cụ đã được chứng minh và hiệu quả: MessagePack cho việc serialization và RESTful API làm phương tiện vận chuyển. MessagePack là một định dạng nhị phân hiệu quả, nhỏ gọn hơn nhiều so với JSON, và RESTful API là một tiêu chuẩn phổ quát. Đây là một lựa chọn thực dụng để MVP có thể hoạt động nhanh chóng.

Tuy nhiên, đây không phải là đích đến cuối cùng. Tương lai của giao tiếp trong CScaf là một giao thức được thiết kế riêng có tên là SpaceWire.

Lý do cần đến SpaceWire nằm ở một lợi thế độc nhất mà CScaf có được: vì Wcd build cả bên gửi và bên nhận từ cùng một nguồn, nó có kiến thức hoàn hảo và tuyệt đối về cấu trúc dữ liệu của cả hai phía. Các giao thức như MessagePack hay Protobuf vẫn cần phải gửi kèm một lượng metadata nhỏ (như tên trường hoặc thẻ số) để bên nhận biết cách diễn giải dữ liệu.

SpaceWire sẽ loại bỏ hoàn toàn lượng metadata này. "Schema" sẽ không được gửi đi lúc runtime. Nó đã được đốt cứng vào cả hai đầu của đường ống tại thời điểm build. Gói tin SpaceWire sẽ chỉ là dữ liệu thô, được sắp xếp theo một thứ tự byte đã được cả hai bên thống nhất một cách cứng nhắc. Điều này sẽ tạo ra các gói tin nhỏ nhất có thể về mặt lý thuyết và mang lại hiệu năng tối đa.

Do đó, Blueford trong giai đoạn MVP, dù chỉ sử dụng MessagePack, chính là bước đi đầu tiên, là nền tảng để xây dựng nên Data Marshalling Engine. Nó thiết lập quy trình và các API nội bộ, để rồi trong tương lai, chúng ta có thể dễ dàng rút "động cơ" MessagePack ra và thay thế nó bằng động cơ phản lực SpaceWire mạnh mẽ hơn.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí