TCIS.EventBus.MultiTenancy 1.0.0

There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package TCIS.EventBus.MultiTenancy --version 1.0.0
                    
NuGet\Install-Package TCIS.EventBus.MultiTenancy -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TCIS.EventBus.MultiTenancy" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TCIS.EventBus.MultiTenancy" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="TCIS.EventBus.MultiTenancy" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TCIS.EventBus.MultiTenancy --version 1.0.0
                    
#r "nuget: TCIS.EventBus.MultiTenancy, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TCIS.EventBus.MultiTenancy@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TCIS.EventBus.MultiTenancy&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=TCIS.EventBus.MultiTenancy&version=1.0.0
                    
Install as a Cake Tool

TCIS.EventBus.MultiTenancy

Cho consumer của TCIS.EventBus tự nạp hồ sơ tenant từ header t-site-code, để handler chạy được trên tầng dữ liệu đa cảng của TCIS.Pluggable.

Gói cầu nối: nó biết cả hai bên, để không bên nào phải biết bên kia. TCIS.EventBus giữ nguyên purity — chỉ tham chiếu TCIS.Core và TCIS.Logging.Abstractions.

Mục lục

  1. Vấn đề nó giải
  2. Cài đặt
  3. Hành vi
  4. Nó móc vào đâu, và vì sao đúng chỗ đó
  5. Tự viết IConsumerContextInitializer
  6. Những chỗ dễ vấp

1. Vấn đề nó giải

SubscribeExecutor khôi phục WorkContext từ header — đủ để định tuyến nghiệp vụ theo cảng (KeyedMediator chọn handler theo SiteCode), nhưng không đủ để mở kết nối.

Tầng dữ liệu đọc một nguồn khác: TenantConnectionSource và TenantFilterPolicy đều lấy TenantInfo từ IMultiTenantContextAccessor — nơi chứa chuỗi kết nối và mức cách ly. Consumer chạy trên thread nền của vòng lặp Kafka, không đi qua MultiTenantMiddleware, nên không thành phần nào nạp nó.

Host đa cảng KHÔNG cài gói này
Handler chạm database ném "connection string is missing for tenant 'Unknown'"
Handler không mở kết nối TenantFilterPolicy fail-closed ⇒ truy vấn trả rỗng, message ghi Succeeded, không ai biết

Vế thứ hai nguy hiểm hơn: hệ thống báo thành công sau khi không làm gì cả.


2. Cài đặt

dotnet add package TCIS.EventBus.MultiTenancy
// 1. Tenant Store — nguồn sự thật.
//    KHÔNG cần strategy: mã cảng đã có sẵn trong header, không cần cơ chế đi tìm nó.
builder.Services.AddTMultiTenant<TenantInfo>()
                .WithConfigurationStore();

// 2. EventBus như bình thường
builder.Services.AddTEventBus(o =>
{
    o.DefaultGroupName = "tcis.queue.berth-service";
    o.UseInMemoryStorage();
    o.UseKafka(k => k.Servers = "…");
})
.AddSubscriberAssembly(typeof(Program).Assembly);

// 3. Cầu nối
builder.Services.AddTEventBusTenantContext();

Host đơn tenant không gọi dòng 3, và EventBus hoạt động y hệt như trước: không có hiện thực IConsumerContextInitializer nào thì vòng lặp trong SubscribeInvoker không chạy lần nào.

Dùng kiểu TenantInfo dẫn xuất thì gọi overload generic:

builder.Services.AddTEventBusTenantContext<MyTenantInfo>();

3. Hành vi

Message Kết quả
Có t-site-code, Store biết Nạp TenantInfo + phần Tenant của WorkContext. Handler chạy
Có t-site-code, Store không biết SEC_TENANT_NOT_FOUND ⇒ handler không chạy, message ghi Failed, không thử lại
Không có t-site-code Theo RequireTenant — mặc định true ⇒ từ chối
// Trạm tích hợp chỉ chuyển tiếp dữ liệu, không chạm DB đa cảng:
builder.Services.AddTEventBusTenantContext(o => o.RequireTenant = false);

RequireTenant mặc định true — ngược với TCIS.Hangfire.MultiTenancy.

Mặc định Vì sao
EventBus true Mọi message đều xuất phát từ nghiệp vụ của một cảng; message không có mã cảng là bất thường
Hangfire false Job hệ thống (recurring lúc khởi động, job dọn dẹp) chạy với TenantId = null — đó là chuyện bình thường

Khác biệt này có chủ đích. Đừng "thống nhất" hai bên.

Lỗi cấu hình được phân loại vĩnh viễn: SubscribeExecutor.IsPermanentFailure nhận ra tiền tố SEC_ và CONFIG_, nên message hỏng vì cấu hình không chạy đủ FailedRetryCount vòng.


4. Nó móc vào đâu, và vì sao đúng chỗ đó

SubscribeInvoker gọi initializer giữa CreateAsyncScope() và GetInstance() — sau khi có scope, nhưng trước khi subscriber và mọi dependency của nó được dựng.

CreateAsyncScope() → ResolveAsync → Apply → GetInstance() → handler → finally: Cleanup()

Thứ tự này là điều kiện đúng đắn: DbContext chọn chuỗi kết nối theo tenant ngay trong constructor. Chuẩn bị ngữ cảnh sau GetInstance là quá muộn.

Cleanup() luôn chạy, kể cả khi handler ném, và theo thứ tự ngược với khởi tạo.


5. Tự viết IConsumerContextInitializer

Hợp đồng IConsumerContextInitializer nằm trong TCIS.EventBus và cố ý không biết chữ "tenant" — bạn có thể viết hiện thực cho feature flag, ngôn ngữ, hay bất cứ ngữ cảnh nào khác. Nhiều hiện thực cùng đăng ký thì chạy theo thứ tự đăng ký.

Hai ràng buộc bắt buộc:

5.1. Apply KHÔNG được là async

Ngữ cảnh nằm trong AsyncLocal, mà AsyncLocal chảy xuôi theo ExecutionContext chứ không chảy ngược: ghi nó sau một await đã thực sự nhường luồng thì thay đổi biến mất khi hàm trả về.

Đó là lý do hợp đồng tách đôi — ResolveAsync làm mọi việc cần await (tra store, gọi mạng) và không đụng tới ngữ cảnh; Apply chỉ ghi, và chạy trọn vẹn đồng bộ.

Hỏng không tất định: store trả kết quả đồng bộ (cache nóng) thì thay đổi lan ra bình thường. Chạy đúng trên máy dev, hỏng lần đầu gặp cache lạnh trên production.

5.2. Fail-closed là trách nhiệm của bên hiện thực

Thư viện không áp đặt chính sách. Ngữ cảnh là bắt buộc mà chuẩn bị không được thì ném từ Apply — message khi đó đi vào nhánh lỗi bình thường của consumer (có log, chuyển Failed) thay vì chạy nghiệp vụ với ngữ cảnh thiếu.


6. Những chỗ dễ vấp

# Chỗ vấp Hậu quả
1 Cài TCIS.Pluggable nhưng quên gói này Truy vấn trả rỗng mà message vẫn ghi Succeeded (§1)
2 Đăng ký ITenantResolver rồi mong nó chạy cho consumer ClaimStrategy/HeaderStrategy mở đầu bằng if (context is not HttpContext) return null — chúng không bao giờ trả về gì cho luồng nền. Gói này đi thẳng tới Store
3 Gộp ResolveAsync + Apply vào một hàm async Ngữ cảnh mất, không tất định (§5.1)
4 Đặt RequireTenant = false cho service chạm DB đa cảng Handler chạy mà không biết mình ở cảng nào — ghi nhầm cảng, hoặc đọc rỗng rồi kết luận "không có dữ liệu"
5 Trông đợi tier đi theo message Không kênh nào chuyên chở mức cách ly. Consumer tra Store bằng t-site-code; header t-isolation-level đã bị gỡ 14/08/2026

Chi tiết vận hành: md/16 §6. Bộ test đối chứng: TCIS.EventBus.SiteRouting.Tests (Platform + hai Plugin ở ba assembly riêng, ba cảng ba tier, không cần Docker).

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.1-rc.2 37 10/5/2026
1.0.1-rc.1 40 10/5/2026
1.0.0 71 10/1/2026
1.0.0-rc.52 41 9/29/2026
1.0.0-rc.51 57 9/23/2026
1.0.0-rc.50 60 9/21/2026
1.0.0-rc.49 58 9/18/2026
1.0.0-rc.47 59 9/17/2026
1.0.0-rc.46 58 9/17/2026
1.0.0-rc.45 62 9/11/2026
1.0.0-rc.44 65 9/10/2026
1.0.0-rc.42 76 9/9/2026
1.0.0-rc.41 69 9/9/2026
1.0.0-rc.40 64 9/8/2026
1.0.0-rc.39 67 9/3/2026
1.0.0-rc.38 65 9/3/2026
1.0.0-rc.37 66 8/27/2026
1.0.0-rc.36 73 8/27/2026
1.0.0-rc.35 60 8/26/2026
1.0.0-rc.34 75 8/26/2026
Loading failed