PubSubCClient 1.0.0
See the version list below for details.
dotnet add package PubSubCClient --version 1.0.0
NuGet\Install-Package PubSubCClient -Version 1.0.0
<PackageReference Include="PubSubCClient" Version="1.0.0" />
<PackageVersion Include="PubSubCClient" Version="1.0.0" />
<PackageReference Include="PubSubCClient" />
paket add PubSubCClient --version 1.0.0
#r "nuget: PubSubCClient, 1.0.0"
#:package PubSubCClient@1.0.0
#addin nuget:?package=PubSubCClient&version=1.0.0
#tool nuget:?package=PubSubCClient&version=1.0.0
tên PubSubC Mô tả PubSubC là một thư viện C# để quản lý phạm vi theo dõi trong 1 khu vực nhất định. Nó có các đối tượng chính: Region : đại diện cho 1 khu vực nhất định Cell : đại diện cho 1 ô vuông trong khu vực, chứa danh sách Watcher và Unit phân loại theo UnitType Watcher : đại diện cho 1 đối tượng theo dõi, đăng ký vào nhiều ô vuông theo radius Unit : đại diện cho 1 đơn vị, nằm trong đúng 1 cell tại mỗi thời điểm UnitGroup : nhóm các Unit cùng loại trong một Region, quản lý ping timeout riêng
Config Cấu hình qua command-line args khi khởi động: --cell-size : kích thước mỗi ô vuông (mặc định 50) --region-ids : danh sách Region ID, phân cách bằng dấu phẩy (mặc định "1,2,3,...10") --ping-expire-time : thời gian tối đa chờ ping, tính bằng ms (mặc định 10000) --nats-url : URL kết nối NATS (mặc định "nats://localhost:4222")
Mã hóa dữ liệu Protobuf để mã hóa/giải mã dữ liệu truyền qua NATS. Incoming: WatcherEvent.Parser.ParseFrom(byte[]) hoặc UnitEvent.Parser.ParseFrom(byte[]). Outgoing: tái sử dụng 3 instance proto (_outWatcher, _outUnit, _outWarning). Scalar fields ghi đè trực tiếp, repeated fields gọi .Clear() rồi .AddRange(). Publish: message.ToByteArray() → NATS publish. Đơn giản, 1 bước.
Phương thức giao tiếp Thông qua NATS (package NATS.Net, INatsConnection)
Subscriber Subject (nhận event từ bên ngoài) #nats_subscriber
PUB_SUB_C.Region.{regionId}.WatcherSubscriber
byte[] => WatcherEvent.Parser.ParseFrom => xử lý trong Region
PUB_SUB_C.Region.{regionId}.UnitSubscriber
byte[] => UnitEvent.Parser.ParseFrom => xử lý trong Region
Publisher Subject (gửi event ra bên ngoài) #nats_publisher
PUB_SUB_C.Region.{regionId}.WatcherPublisher
WatcherEvent => ToByteArray() => NATS publish
Gửi khi: Watcher subscribe, unsubscribe, moved, hoặc bị timeout
PUB_SUB_C.Region.{regionId}.UnitPublisher
UnitEvent => ToByteArray() => NATS publish
Gửi khi: Unit added (ENTER_REGION), removed (EXIT_REGION), moved
Gửi khi: WatcherCheck phát hiện lệch → ENTER_REGION, EXIT_REGION, UNIT_UPDATE
Chứa notifyWatcherIds: danh sách watcherId bị ảnh hưởng bởi sự kiện (trừ WatcherCheck)
Chứa watcherCheckId: ID watcher được kiểm tra (chỉ khi WatcherCheck)
PUB_SUB_C.Region.{regionId}.WarningPublisher
WarningEvent => ToByteArray() => NATS publish
Gửi khi: entity not found, wrong version, hoặc not responding (timeout)
Luồng Mỗi Region chạy trên một Task riêng (TaskCreationOptions.LongRunning). Event queue sử dụng Channel<QueueEntry> (UnboundedChannel, SingleReader). QueueEntry là readonly record struct(SubjectType, byte[]).
Vòng lặp chính (RegionLoopAsync):
1. WaitToReadAsync với timeout 100ms (đánh thức khi có event hoặc hết timeout)
2. Drain toàn bộ queue: TryRead liên tục, gọi ProcessEventAsync cho mỗi entry
3. CheckPingTimeoutsAsync: kiểm tra watcher/unit quá hạn ping
4. Lặp lại
RegionManager (IHostedService):
StartAsync: tạo NatsConnection, tạo các Region theo --region-ids, subscribe NATS cho mỗi Region
StopAsync: cancel subscription, dispose các Region, đóng NATS connection
Cấu trúc dữ liệu
Manager (RegionManager : IHostedService)
NatsConnection nats
Dictionary<long, Region> regions
List<Task> subscriptionTasks
Phương thức:
StartAsync: kết nối NATS, tạo Region, subscribe NATS subjects
StopAsync: cancel, dispose regions, đóng NATS
Region
Thuộc tính
long id
int cellSize
int pingExpireTimeMs
INatsConnection nats
Dictionary<string, Cell> cells
Dictionary<long, Watcher> watchers
Dictionary<string, UnitGroup> unitGroups
Ping tracking (watcher):
SortedSet<(long Timestamp, long Id)> watcherPingOrder
Sắp xếp theo timestamp để scan timeout hiệu quả, dừng sớm khi gặp entry chưa hết hạn
Dictionary<long, long> watcherPingTs
watcherId → TickCount64, dùng cho lookup O(1) khi cập nhật ping
Channel<QueueEntry> channel
Unbounded, SingleReader. NATS subscriber ghi vào, Region thread đọc ra.
Protobuf reuse (outgoing):
WatcherEvent _outWatcher
UnitEvent _outUnit
WarningEvent _outWarning
Temp collections (tái sử dụng, clear trước mỗi lần dùng, tránh allocation):
HashSet<string> _tempCellIds
Kết quả CellHelper.FillSurroundingCellIds
List<string> _tempCellDiff
Diff old/new cells khi watcher moved (thay cho Except().ToList())
HashSet<long> _tempAffectedWatcherIds
Thu thập watcherIds bị ảnh hưởng bởi unit event
List<(string,long)> _tempExpiredUnits
Danh sách unit hết hạn ping trong CheckPingTimeoutsAsync
List<long> _tempWarnWatcherIds
Tham số watcherIds cho PublishWarningAsync
List<long> _tempWarnUnitIds
Tham số unitIds cho PublishWarningAsync
HashSet<long> _tempCheckUnitIds
Thu thập unitIds server-side khi WatcherCheck, dùng HashSet để tra Contains O(1)
Dictionary<long, int> _tempClientUnitVersions
Bảng unitId → version từ client khi WatcherCheck
Task loopTask
CancellationTokenSource cts
Phương thức
EnqueueEvent(string subjectType, byte[] data)
Ghi vào channel, đánh thức region loop
DisposeAsync()
Cancel, complete channel, chờ loopTask
Watcher
long Id
float PositionX, PositionY
int Radius
int Version
HashSet<string> CellIds
Danh sách cell mà Watcher đang theo dõi
Dictionary<string, HashSet<long>> UnitIdsByUnitType
UnitId mà Watcher nhìn thấy, phân loại theo UnitType
Unit
long Id
string Type
float PositionX, PositionY
int Version
string? CellId
Cell hiện tại chứa Unit (1 cell duy nhất)
UnitGroup
string Type
Dictionary<long, Unit> Units
Dictionary<long, long> PingTimestamps
unitId → TickCount64
Lưu ý: khi rỗng thì bị xóa khỏi Region
Cell
string Id
HashSet<long> WatcherIds
Dictionary<string, HashSet<long>> UnitIdsByUnitType
UnitId trong cell, phân loại theo UnitType
Key = unitType, Value = tập hợp unitId
bool IsEmpty => WatcherIds.Count == 0 && UnitIdsByUnitType.Count == 0
Lưu ý: khi rỗng thì bị xóa khỏi Region
Logic
Suy Position sang Cell
cellX = floor(position.x / cellSize)
cellY = floor(position.y / cellSize)
cellId = "{cellX}_{cellY}"
Lấy Cell xung quanh Watcher theo radius
CellHelper.FillSurroundingCellIds(x, y, radius, cellSize, resultHashSet)
resultHashSet.Clear()
for dx = -radius to +radius
for dy = -radius to +radius
cellX = floor((x + dx * cellSize) / cellSize)
cellY = floor((y + dy * cellSize) / cellSize)
resultHashSet.Add("{cellX}_{cellY}")
Xử lý WatcherEvent:
SUBSCRIBER:
Tạo Watcher mới, tính cellIds theo radius (FillSurroundingCellIds → _tempCellIds)
Đăng ký watcher vào từng cell, ghi nhận unit hiện có vào watcher.UnitIdsByUnitType
Cập nhật ping timestamp
Publish WatcherEvent(SUBSCRIBER) ra WatcherPublisher
UNSUBSCRIBER:
Xóa watcher khỏi tất cả cell + Region.Watchers + ping tracking
Cleanup cell rỗng
Publish WatcherEvent(UNSUBSCRIBER) ra WatcherPublisher
MOVED:
Tính newCells (_tempCellIds), diff với oldCells (_tempCellDiff)
Removed cells: xóa watcher khỏi cell, loại unit khỏi watcher.UnitIdsByUnitType
Added cells: thêm watcher vào cell, ghi nhận unit mới
Cập nhật position, radius, version, ping
Publish WatcherEvent(MOVED) ra WatcherPublisher
PING:
Không tìm thấy → warning WATCHER_NOT_FOUND
Version không khớp → warning WATCHER_WRONG_VERSION
OK → cập nhật ping timestamp
Xử lý UnitEvent:
ADDED:
Tạo Unit, tính cellId, thêm vào cell (theo UnitType) và UnitGroup
Cập nhật tất cả watcher đang theo dõi cell: thêm unitId vào watcher.UnitIdsByUnitType
Thu thập affected watcherIds (_tempAffectedWatcherIds)
Publish UnitEvent(ENTER_REGION, notifyWatcherIds) ra UnitPublisher
REMOVED:
Thu thập affected watcherIds TRƯỚC KHI xóa (_tempAffectedWatcherIds)
Xóa unit khỏi cell, watcher.UnitIdsByUnitType, UnitGroup
Cleanup cell rỗng, UnitGroup rỗng
Publish UnitEvent(EXIT_REGION, notifyWatcherIds) ra UnitPublisher
MOVED:
Nếu đổi cell: xóa khỏi old cell + watcher cũ, thêm vào new cell + watcher mới
Nếu cùng cell: chỉ thu thập watcher hiện tại
Cập nhật position, version, ping
Thu thập affected watcherIds từ cả old + new cell (_tempAffectedWatcherIds)
Publish UnitEvent(MOVED, notifyWatcherIds) ra UnitPublisher
PING:
Group không tìm thấy → warning UNIT_NOT_FOUND (tất cả unitIds)
Unit không tìm thấy → warning UNIT_NOT_FOUND (từng unitId)
Version không khớp → warning UNIT_WRONG_VERSION
OK → cập nhật ping timestamp
WATCHER_CHECK:
Mục đích: kiểm tra đồng bộ danh sách unit mà watcher đang theo dõi giữa client và server
Input (UnitEvent):
unitIds + versions: danh sách unit mà client đang biết (theo unitType)
unitType: loại unit cần kiểm tra
watcherCheckId: ID watcher cần kiểm tra
Xử lý:
Không tìm thấy watcher → warning WATCHER_NOT_FOUND
Build bảng client: unitId → version (_tempClientUnitVersions)
Thu thập tất cả unit (theo unitType) từ các cell watcher đang theo dõi (_tempCheckUnitIds)
So sánh 3 chiều:
Thiếu (server có, client không có) → publish ENTER_REGION
Thừa (client có, server không có) → publish EXIT_REGION
Lệch version (cả hai có, version khác nhau) → publish UNIT_UPDATE
Output (UnitEvent qua UnitPublisher):
Mỗi loại sai lệch publish riêng 1 event (chỉ khi có unitIds)
Chứa: unitIds, eventType, unitType, regionId, watcherCheckId
Không chứa: versions, positionX/Y, notifyWatcherIds
MASTER_PING_UNIT
Mục đích: kiểm tra đồng bộ và hoạt động của unit, tương tự Watcher PING nhưng cho hệ thống Server-side unit (không phải client unit)
Group không tìm thấy trong system → MASTER_SURPLUS (gửi toàn bộ unit ping được vào event)
Báo cho master data source có unit thừa trong Region, cần điều chỉnh dữ liệu hoặc loại bỏ unit này
Unit master có, system không có → MASTER_SURPLUS (gửi unitId vào event)
Báo cho master data source có unit không tồn tại trong Region, cần điều chỉnh dữ liệu hoặc loại bỏ unit này
Unit system có, master không có → MASTER_SHORTAGE (gửi unitId vào event)
Báo cho master data source có unit thiếu trong Region, cần điều chỉnh dữ liệu hoặc thêm unit này
Version không khớp → MASTER_VERSION_MISMATCH (gửi unitId vào event)
Báo cho master data source có unit lệch version, cần điều chỉnh dữ liệu hoặc cảnh báo hệ thống upstream
Ping timeout check (CheckPingTimeoutsAsync):
Watcher: scan watcherPingOrder (sorted), nếu timestamp <= cutoff:
Xóa watcher khỏi Region + tất cả cell
Publish warning WATCHER_NOT_RESPONDING
Publish WatcherEvent(UNSUBSCRIBER)
Unit: duyệt tất cả UnitGroup.PingTimestamps, thu thập expired vào _tempExpiredUnits
Xóa unit khỏi UnitGroup + cell + watcher.UnitIdsByUnitType
Publish warning UNIT_NOT_RESPONDING
Cleanup UnitGroup rỗng
Warning publish (3 convenience method, zero-alloc):
PublishWatcherWarningAsync(warningType, watcherId)
Dùng _tempWarnWatcherIds, single watcher
PublishUnitWarningAsync(warningType, unitId, unitType)
Dùng _tempWarnUnitIds, single unit
PublishUnitsWarningAsync(warningType, unitIds, unitType)
Dùng _tempWarnUnitIds, nhiều unit (từ RepeatedField trực tiếp)
Unit publish (2 phương thức):
PublishUnitEventAsync(eventType, srcEvt, notifyWatcherIds)
Gửi đầy đủ: unitIds, versions, positionX/Y, notifyWatcherIds, unitType, regionId
Dùng cho: ADDED→ENTER_REGION, REMOVED→EXIT_REGION, MOVED
PublishWatcherCheckResultAsync(eventType, srcEvt)
Gửi tối giản: unitIds, eventType, unitType, regionId, watcherCheckId
Dùng cho: kết quả WATCHER_CHECK (ENTER_REGION, EXIT_REGION, UNIT_UPDATE)
Cell helpers:
AddUnitToCell(cell, unitType, unitId)
Thêm unitId vào cell.UnitIdsByUnitType[unitType]
RemoveUnitFromCell(cell, unitType, unitId)
Xóa unitId, nếu set rỗng thì xóa key unitType
AddCellUnitsToWatcher(watcher, cell)
Duyệt cell.UnitIdsByUnitType, thêm trực tiếp vào watcher.UnitIdsByUnitType
RemoveCellUnitsFromWatcher(watcher, cell)
Duyệt cell.UnitIdsByUnitType, xóa khỏi watcher.UnitIdsByUnitType
Protobuf messages
File: Protos/pub_sub_c.proto
option csharp_namespace = "PubSubC.Proto"
message WatcherEvent {
int64 watcherId = 1;
EventType eventType = 2;
int32 version = 3;
float positionX = 4;
float positionY = 5;
int32 radius = 6;
int64 regionId = 7;
}
message UnitEvent {
repeated int64 unitIds = 1;
EventType eventType = 2;
repeated int32 versions = 3;
repeated float positionX = 4;
repeated float positionY = 5;
string unitType = 6;
int64 regionId = 7;
repeated int64 notifyWatcherIds = 8;
Danh sách watcherId bị ảnh hưởng bởi sự kiện unit này.
Được thu thập từ cell.WatcherIds khi unit added/removed/moved.
int64 watcherCheckId = 9;
ID watcher cần kiểm tra đồng bộ.
Dùng trong WATCHER_CHECK và các event phản hồi (ENTER_REGION, EXIT_REGION, UNIT_UPDATE).
}
enum EventType {
MOVED = 0; // Unit hoặc Watcher di chuyển. Subscriber + Publisher.
ADDED = 1; // Unit được thêm vào. Subscriber.
REMOVED = 2; // Unit bị xóa. Subscriber.
SUBSCRIBER = 3; // Watcher đăng ký. Subscriber + Publisher.
UNSUBSCRIBER = 4; // Watcher hủy đăng ký. Subscriber + Publisher.
PING = 5; // Ping xác nhận hoạt động. Subscriber.
ENTER_REGION = 6; // Unit vào region. Publisher only.
EXIT_REGION = 7; // Unit rời region. Publisher only.
WATCHER_CHECK = 8; // Kiểm tra đồng bộ unit của watcher. Subscriber.
UNIT_UPDATE = 9; // Unit lệch version khi watcher check. Publisher only.
}
message WarningEvent {
repeated int64 watcherIds = 1;
repeated int64 unitIds = 2;
string unitType = 3;
WarningType warningType = 4;
}
enum WarningType {
WATCHER_NOT_FOUND = 0;
Không tìm thấy Watcher trong Region khi có event liên quan.
UNIT_NOT_FOUND = 1;
Không tìm thấy Unit trong Region khi có event liên quan.
WATCHER_WRONG_VERSION = 2;
Version Watcher không khớp khi Ping.
UNIT_WRONG_VERSION = 3;
Version Unit không khớp khi Ping.
WATCHER_NOT_RESPONDING = 4;
Watcher không Ping quá hạn, bị xóa khỏi Region và tất cả Cell.
UNIT_NOT_RESPONDING = 5;
Unit không Ping quá hạn, bị xóa khỏi UnitGroup và Region.
}
Test
Mục tiêu
Đảm bảo tính đúng đắn của logic theo dõi theo Region/Cell, tính nhất quán dữ liệu theo version,
tính ổn định khi xử lý đồng thời theo Queue, và độ tin cậy của giao tiếp NATS + Protobuf.
Chiến lược test tổng thể
1. Unit Test (logic thuần)
- Suy Position -> Cell ID.
- Tính danh sách Cell trong bán kính của Watcher.
- Cập nhật state Watcher/Unit theo từng EventType.
- Kiểm tra version khi xử lý Ping/Moved.
2. Integration Test (thành phần phối hợp)
- Region + Queue + Publisher/Subscriber.
- Encode/Decode Protobuf round-trip.
- Routing đúng NATS subject theo regionId.
3. Concurrency Test
- Nhiều producer đẩy event vào cùng Region, đảm bảo xử lý tuần tự theo Queue.
- Nhiều Region chạy song song, không rò rỉ state chéo vùng.
4. Failure/Recovery Test
- Watcher/Unit không Ping quá hạn bị loại bỏ và phát WarningEvent.
- Nhận event không hợp lệ hoặc sai version phải được xử lý an toàn.
5. Performance/Soak Test
- Đo throughput event/s theo từng Region.
- Theo dõi GC allocation.
Môi trường test đề xuất
- Framework: xUnit (hoặc NUnit), FluentAssertions.
- Mock/Fake: Fake NATS connection + in-memory publisher/subscriber.
- Tách test theo nhóm: Unit / Integration / Load.
- Thiết lập cố định:
cellSize = 50
pingExpireTime = 10000ms
regionIds = 1,2,3
Test cases
[Unit - Mapping/Geometry]
TC-001: Suy Position dương sang Cell
Tiền điều kiện:
cellSize = 50
Bước thực hiện:
Input position = (120, 70)
Tính cellX = floor(120/50), cellY = floor(70/50)
Kết quả mong đợi:
cellId = "2_1"
TC-002: Suy Position âm sang Cell
Tiền điều kiện:
cellSize = 50
Bước thực hiện:
Input position = (-1, -1)
Áp dụng floor cho số âm
Kết quả mong đợi:
cellId = "-1_-1"
TC-003: Lấy Cell xung quanh Watcher theo radius
Tiền điều kiện:
watcher.position = (100,100), radius = 1, cellSize = 50
Bước thực hiện:
Gọi FillSurroundingCellIds
Kết quả mong đợi:
Có đúng 9 cell (3x3), chứa "2_2" là cell trung tâm
TC-004: Radius = 0 chỉ trả về 1 Cell hiện tại
Tiền điều kiện:
watcher.position = (25,25), radius = 0, cellSize = 50
Bước thực hiện:
Gọi FillSurroundingCellIds
Kết quả mong đợi:
Chỉ có 1 cell: "0_0"
[Unit - State/Version]
TC-005: Watcher SUBSCRIBER tạo mới watcher và cellIds
Tiền điều kiện:
Region chưa có watcherId=101
Bước thực hiện:
Enqueue WatcherEvent(eventType=SUBSCRIBER, watcherId=101, version=1, position=(120,70), radius=1)
ProcessEvents()
Kết quả mong đợi:
Region.Watchers chứa watcherId=101, version=1
watcher.CellIds được khởi tạo đúng theo radius
TC-006: Watcher UNSUBSCRIBER xóa watcher khỏi Region và Cell
Tiền điều kiện:
watcherId=101 đang tồn tại và đã đăng ký nhiều cell
Bước thực hiện:
Enqueue WatcherEvent(eventType=UNSUBSCRIBER, watcherId=101)
ProcessEvents()
Kết quả mong đợi:
watcherId=101 bị xóa khỏi Region.Watchers
watcher không còn xuất hiện trong bất kỳ Cell.WatcherIds nào
TC-007: Ping Watcher sai version phát cảnh báo WATCHER_WRONG_VERSION
Tiền điều kiện:
watcherId=101 tồn tại với version=5
Bước thực hiện:
Enqueue WatcherEvent(eventType=PING, watcherId=101, version=4)
ProcessEvents()
Kết quả mong đợi:
Không cập nhật state watcher
Phát WarningEvent.warningType = WATCHER_WRONG_VERSION
TC-008: Ping Unit không tồn tại phát cảnh báo UNIT_NOT_FOUND
Tiền điều kiện:
UnitGroup loại "car" không có unitId=999
Bước thực hiện:
Enqueue UnitEvent(eventType=PING, unitType="car", unitIds=[999], versions=[1])
ProcessEvents()
Kết quả mong đợi:
Phát WarningEvent.warningType = UNIT_NOT_FOUND
[Unit - WatcherCheck]
TC-017: WATCHER_CHECK phát hiện unit thiếu → ENTER_REGION
Tiền điều kiện:
watcherId=101 theo dõi cell "2_1"
Cell "2_1" có unitType="car" chứa unitIds=[1,2,3]
Bước thực hiện:
Enqueue UnitEvent(eventType=WATCHER_CHECK, unitType="car", unitIds=[1,2], versions=[1,1], watcherCheckId=101)
ProcessEvents()
Kết quả mong đợi:
Publish UnitEvent(ENTER_REGION, unitIds=[3], unitType="car", watcherCheckId=101)
TC-018: WATCHER_CHECK phát hiện unit thừa → EXIT_REGION
Tiền điều kiện:
watcherId=101 theo dõi cell "2_1"
Cell "2_1" có unitType="car" chứa unitIds=[1,2]
Bước thực hiện:
Enqueue UnitEvent(eventType=WATCHER_CHECK, unitType="car", unitIds=[1,2,3], versions=[1,1,1], watcherCheckId=101)
ProcessEvents()
Kết quả mong đợi:
Publish UnitEvent(EXIT_REGION, unitIds=[3], unitType="car", watcherCheckId=101)
TC-019: WATCHER_CHECK phát hiện lệch version → UNIT_UPDATE
Tiền điều kiện:
watcherId=101 theo dõi cell "2_1"
Cell "2_1" có unitType="car" chứa unitId=1 (version=5)
Bước thực hiện:
Enqueue UnitEvent(eventType=WATCHER_CHECK, unitType="car", unitIds=[1], versions=[3], watcherCheckId=101)
ProcessEvents()
Kết quả mong đợi:
Publish UnitEvent(UNIT_UPDATE, unitIds=[1], unitType="car", watcherCheckId=101)
TC-020: WATCHER_CHECK watcher không tồn tại → warning WATCHER_NOT_FOUND
Tiền điều kiện:
Region không có watcherId=999
Bước thực hiện:
Enqueue UnitEvent(eventType=WATCHER_CHECK, unitType="car", watcherCheckId=999)
ProcessEvents()
Kết quả mong đợi:
Phát WarningEvent.warningType = WATCHER_NOT_FOUND
TC-021: WATCHER_CHECK không có sai lệch → không publish event nào
Tiền điều kiện:
watcherId=101 theo dõi cell "2_1"
Cell "2_1" có unitType="car" chứa unitIds=[1,2] (versions=[1,1])
Bước thực hiện:
Enqueue UnitEvent(eventType=WATCHER_CHECK, unitType="car", unitIds=[1,2], versions=[1,1], watcherCheckId=101)
ProcessEvents()
Kết quả mong đợi:
Không publish bất kỳ UnitEvent nào ra UnitPublisher
[Integration - Queue/NATS/Protobuf]
TC-009: Queue xử lý tuần tự đúng thứ tự enqueue
Tiền điều kiện:
Region queue rỗng
Bước thực hiện:
Enqueue lần lượt 3 event cho cùng watcher: SUBSCRIBER(v1), MOVED(v2), PING(v2)
ProcessEvents()
Kết quả mong đợi:
State cuối cùng phản ánh đúng event cuối (position mới, version=2)
Không có cảnh báo sai version
TC-010: Routing đúng subject theo regionId cho Subscriber
Tiền điều kiện:
Có Region 1 và Region 2
Bước thực hiện:
Publish dữ liệu vào PUB_SUB_C.Region.2.WatcherSubscriber
Kết quả mong đợi:
Chỉ Region 2 nhận và xử lý event
Region 1 không đổi state
TC-011: Encode/Decode WatcherEvent bằng Protobuf không sai lệch dữ liệu
Tiền điều kiện:
Có message WatcherEvent mẫu (đủ field)
Bước thực hiện:
ToByteArray() -> ParseFrom()
Kết quả mong đợi:
Các field watcherId, eventType, version, positionX/Y, radius, regionId khớp 100%
TC-012: UnitEvent notifyWatcherIds đúng danh sách watcher bị ảnh hưởng
Tiền điều kiện:
Region có watcher W1 theo dõi cell "2_1", watcher W2 theo dõi cell "3_3"
Bước thực hiện:
Thêm unit vào cell "2_1"
Kết quả mong đợi:
UnitEvent(ENTER_REGION).notifyWatcherIds chứa W1, không chứa W2
[Failure/Recovery]
TC-013: Watcher quá hạn ping bị remove và phát WATCHER_NOT_RESPONDING
Tiền điều kiện:
watcherId=101 tồn tại; ping timestamp cũ hơn pingExpireTime
Bước thực hiện:
Chạy chu kỳ kiểm tra timeout
Kết quả mong đợi:
watcherId=101 bị xóa khỏi Region và các Cell liên quan
Phát WarningEvent.warningType = WATCHER_NOT_RESPONDING
TC-014: Unit quá hạn ping bị remove khỏi UnitGroup và phát UNIT_NOT_RESPONDING
Tiền điều kiện:
unitId=501 tồn tại trong UnitGroup "person"; ping quá hạn
Bước thực hiện:
Chạy chu kỳ kiểm tra timeout
Kết quả mong đợi:
unitId=501 bị xóa khỏi UnitGroup và Region
Nếu UnitGroup rỗng thì UnitGroup bị xóa
Phát WarningEvent.warningType = UNIT_NOT_RESPONDING
[Concurrency]
TC-015: Nhiều producer enqueue đồng thời vào cùng Region
Tiền điều kiện:
10 thread producer, mỗi thread gửi 1000 event
Bước thực hiện:
Chạy đồng thời và chờ ProcessEvents hoàn tất
Kết quả mong đợi:
Không mất event, không race condition
Số lượng version cuối cùng nhất quán theo thứ tự xử lý queue
TC-016: Nhiều Region chạy song song không nhiễm chéo dữ liệu
Tiền điều kiện:
Region 1 và 2 cùng có watcherId giống nhau (101)
Bước thực hiện:
Gửi event khác nhau vào từng region
Kết quả mong đợi:
State watcherId=101 của mỗi region độc lập hoàn toàn
Publisher chỉ phát đúng subject theo region tương ứng
Tiêu chí hoàn thành (Definition of Done cho Test)
- 100% test case P0 (TC-001, 003, 005, 007, 009, 010, 011, 013, 014, 015) phải pass.
- Không có bug mức Critical/High ở logic queue, ping timeout, và routing subject.
- Có thể chạy lặp lại tối thiểu 3 lần và cho kết quả ổn định.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. 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. |
-
net9.0
- Google.Protobuf (>= 3.34.1)
- NATS.Net (>= 2.7.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.