TCIS.EventBus.MultiTenancy
1.0.0
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
<PackageReference Include="TCIS.EventBus.MultiTenancy" Version="1.0.0" />
<PackageVersion Include="TCIS.EventBus.MultiTenancy" Version="1.0.0" />
<PackageReference Include="TCIS.EventBus.MultiTenancy" />
paket add TCIS.EventBus.MultiTenancy --version 1.0.0
#r "nuget: TCIS.EventBus.MultiTenancy, 1.0.0"
#:package TCIS.EventBus.MultiTenancy@1.0.0
#addin nuget:?package=TCIS.EventBus.MultiTenancy&version=1.0.0
#tool nuget:?package=TCIS.EventBus.MultiTenancy&version=1.0.0
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
- Vấn đề nó giải
- Cài đặt
- Hành vi
- Nó móc vào đâu, và vì sao đúng chỗ đó
- Tự viết
IConsumerContextInitializer - 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 | Versions 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. |
-
net8.0
- Microsoft.Extensions.Caching.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Configuration (>= 9.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Http (>= 9.0.0)
- Microsoft.Extensions.Logging (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.0)
- TCIS.EventBus (>= 1.0.0)
- TCIS.MultiTenancy (>= 1.0.0)
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 |