282 lines
8.0 KiB
Markdown
282 lines
8.0 KiB
Markdown
# 🌳 Binary Tree Network Registration Guide
|
|
|
|
## 📋 Overview
|
|
|
|
از این پس، هر کاربر جدید که در سیستم ثبت میشود، **همزمان** در دو ساختار قرار میگیرد:
|
|
|
|
1. **Old System**: `User.ParentId` (برای Backward Compatibility)
|
|
2. **New Binary Tree System**: `User.NetworkParentId` + `User.LegPosition` (Left/Right)
|
|
|
|
این تغییر تضمین میکند که:
|
|
- ✅ کاربران جدید بلافاصله در محاسبات Commission شرکت میکنند
|
|
- ✅ نیازی به Migration اضافی نیست
|
|
- ✅ Binary Tree Constraint رعایت میشود (حداکثر 2 فرزند)
|
|
|
|
---
|
|
|
|
## 🔧 Changes in Registration Flow
|
|
|
|
### قبل از تغییر:
|
|
|
|
```csharp
|
|
var entity = request.Adapt<User>();
|
|
entity.ReferralCode = UtilExtensions.Generate(digits: 10);
|
|
await _context.Users.AddAsync(entity, cancellationToken);
|
|
```
|
|
|
|
**مشکل**: فقط `ParentId` Set میشد، `NetworkParentId` و `LegPosition` خالی میماند.
|
|
|
|
---
|
|
|
|
### بعد از تغییر:
|
|
|
|
```csharp
|
|
var entity = request.Adapt<User>();
|
|
entity.ReferralCode = UtilExtensions.Generate(digits: 10);
|
|
|
|
// === محاسبه موقعیت در Binary Tree ===
|
|
if (request.ParentId.HasValue)
|
|
{
|
|
var legPosition = await _networkPlacementService.CalculateLegPositionAsync(
|
|
request.ParentId.Value, cancellationToken);
|
|
|
|
if (legPosition.HasValue)
|
|
{
|
|
entity.NetworkParentId = request.ParentId.Value;
|
|
entity.LegPosition = legPosition.Value; // Left یا Right
|
|
}
|
|
else
|
|
{
|
|
// Parent پر است! Auto-Placement یا Error
|
|
var availableParent = await _networkPlacementService.FindAvailableParentAsync(
|
|
request.ParentId.Value, cancellationToken);
|
|
|
|
// ... Set کردن NetworkParentId و LegPosition با Parent جدید
|
|
}
|
|
}
|
|
|
|
await _context.Users.AddAsync(entity, cancellationToken);
|
|
```
|
|
|
|
**مزایا**:
|
|
- ✅ `NetworkParentId` و `LegPosition` به صورت خودکار محاسبه میشود
|
|
- ✅ Binary Tree Constraint چک میشود
|
|
- ✅ اگر Parent پر باشد، Auto-Placement انجام میشود
|
|
|
|
---
|
|
|
|
## 📐 Binary Tree Logic
|
|
|
|
### قوانین:
|
|
1. هر Parent فقط **2 فرزند** میتواند داشته باشد (Left & Right)
|
|
2. فرزند اول: `LegPosition = Left`
|
|
3. فرزند دوم: `LegPosition = Right`
|
|
4. اگر Parent پر باشد، سیستم به صورت BFS دنبال Parent خالی میگردد
|
|
|
|
### مثال:
|
|
|
|
```
|
|
User1 (Root)
|
|
/ \
|
|
User2 (L) User3 (R)
|
|
/ \
|
|
User4(L) User5(R)
|
|
```
|
|
|
|
- User2 → Parent=User1, Leg=Left
|
|
- User3 → Parent=User1, Leg=Right
|
|
- User4 → Parent=User2, Leg=Left
|
|
- User5 → Parent=User2, Leg=Right
|
|
|
|
اگر کاربر جدید با `ParentId=User1` بیاید:
|
|
- User1 پر است! (دو فرزند دارد)
|
|
- سیستم به User2 میرود (BFS)
|
|
- User2 هم پر است!
|
|
- به User3 میرود → User3 خالی است
|
|
- کاربر جدید → Parent=User3, Leg=Left
|
|
|
|
---
|
|
|
|
## 🛠️ NetworkPlacementService API
|
|
|
|
### 1. CalculateLegPositionAsync
|
|
محاسبه موقعیت (Left/Right) برای کاربر جدید زیر یک Parent مشخص.
|
|
|
|
```csharp
|
|
var legPosition = await _networkPlacementService.CalculateLegPositionAsync(parentId);
|
|
```
|
|
|
|
**Return Values**:
|
|
- `NetworkLeg.Left`: اگر Parent فرزند چپ ندارد
|
|
- `NetworkLeg.Right`: اگر Parent فرزند راست ندارد
|
|
- `null`: اگر Parent پر است (دو فرزند دارد)
|
|
|
|
---
|
|
|
|
### 2. CanAcceptChildAsync
|
|
بررسی اینکه آیا Parent میتواند فرزند جدید بپذیرد.
|
|
|
|
```csharp
|
|
bool canAccept = await _networkPlacementService.CanAcceptChildAsync(parentId);
|
|
```
|
|
|
|
**Return Values**:
|
|
- `true`: اگر Parent کمتر از 2 فرزند دارد
|
|
- `false`: اگر Parent پر است
|
|
|
|
---
|
|
|
|
### 3. FindAvailableParentAsync (Auto-Placement)
|
|
پیدا کردن اولین Parent خالی در Binary Tree با استفاده از BFS.
|
|
|
|
```csharp
|
|
long? availableParentId = await _networkPlacementService.FindAvailableParentAsync(rootParentId);
|
|
```
|
|
|
|
**Use Case**:
|
|
- زمانی که Parent مورد نظر پر است
|
|
- سیستم به صورت خودکار Parent جایگزین پیدا میکند
|
|
- از BFS استفاده میکند (Level-by-Level)
|
|
|
|
**Return Values**:
|
|
- `long`: شناسه Parent مناسب
|
|
- `null`: اگر هیچ Parent خالی پیدا نشد (تمام Binary Tree پر است!)
|
|
|
|
---
|
|
|
|
## ⚠️ Error Handling
|
|
|
|
### Scenario 1: Parent پر است و Auto-Placement موفق
|
|
|
|
```csharp
|
|
// Parent اصلی پر است
|
|
// سیستم Parent جدید پیدا میکند
|
|
_logger.LogWarning("Parent {ParentId} is full. Auto-placing under {NewParentId}");
|
|
```
|
|
|
|
**نتیجه**: کاربر با موفقیت در جای دیگری قرار میگیرد.
|
|
|
|
---
|
|
|
|
### Scenario 2: کل Binary Tree پر است
|
|
|
|
```csharp
|
|
throw new InvalidOperationException(
|
|
$"شبکه Parent با شناسه {parentId} پر است و نمیتواند کاربر جدید بپذیرد.");
|
|
```
|
|
|
|
**نتیجه**: Exception پرتاب میشود، ثبت کاربر انجام نمیشود.
|
|
|
|
**راه حل**:
|
|
- افزایش سطح Binary Tree
|
|
- یا تخصیص دستی Parent
|
|
|
|
---
|
|
|
|
### Scenario 3: Parent وجود ندارد
|
|
|
|
```csharp
|
|
var parentExists = await _context.Users.AnyAsync(u => u.Id == parentId);
|
|
if (!parentExists)
|
|
{
|
|
return null; // Parent نامعتبر
|
|
}
|
|
```
|
|
|
|
**نتیجه**: `null` برگردانده میشود، Exception پرتاب میشود.
|
|
|
|
---
|
|
|
|
## 📊 Logging & Monitoring
|
|
|
|
سیستم Log های زیر را مینویسد:
|
|
|
|
### Success:
|
|
```
|
|
User 123 placed in Binary Tree: Parent=45, Leg=Left
|
|
```
|
|
|
|
### Warning (Auto-Placement):
|
|
```
|
|
Parent 45 has no available leg! Finding alternative parent...
|
|
User 123 auto-placed under alternative Parent=67, Leg=Right
|
|
```
|
|
|
|
### Error (Binary Tree Full):
|
|
```
|
|
No available parent found in network for ParentId=45
|
|
```
|
|
|
|
---
|
|
|
|
## 🧪 Testing Scenarios
|
|
|
|
### Test 1: کاربر اول (Root)
|
|
```csharp
|
|
var command = new CreateNewUserCommand { Mobile = "09121234567" }; // No ParentId
|
|
// Result: ParentId=null, NetworkParentId=null, LegPosition=null
|
|
```
|
|
|
|
---
|
|
|
|
### Test 2: فرزند اول
|
|
```csharp
|
|
var command = new CreateNewUserCommand { Mobile = "09121234568", ParentId = 1 };
|
|
// Result: ParentId=1, NetworkParentId=1, LegPosition=Left
|
|
```
|
|
|
|
---
|
|
|
|
### Test 3: فرزند دوم
|
|
```csharp
|
|
var command = new CreateNewUserCommand { Mobile = "09121234569", ParentId = 1 };
|
|
// Result: ParentId=1, NetworkParentId=1, LegPosition=Right
|
|
```
|
|
|
|
---
|
|
|
|
### Test 4: فرزند سوم (Parent پر است)
|
|
```csharp
|
|
var command = new CreateNewUserCommand { Mobile = "09121234570", ParentId = 1 };
|
|
// Result: Auto-Placement → ParentId=1, NetworkParentId=2 (یا 3), LegPosition=Left
|
|
```
|
|
|
|
---
|
|
|
|
## 🔗 Related Files
|
|
|
|
- **Service Interface**: `CMSMicroservice.Application/Common/Interfaces/INetworkPlacementService.cs`
|
|
- **Service Implementation**: `CMSMicroservice.Infrastructure/Services/NetworkPlacementService.cs`
|
|
- **Handler**: `CMSMicroservice.Application/UserCQ/Commands/CreateNewUser/CreateNewUserCommandHandler.cs`
|
|
- **DI Registration**: `CMSMicroservice.Infrastructure/ConfigureServices.cs` (خط 23)
|
|
|
|
---
|
|
|
|
## ✅ Checklist
|
|
|
|
- [x] `INetworkPlacementService` اضافه شد
|
|
- [x] `NetworkPlacementService` پیادهسازی شد
|
|
- [x] DI Container تنظیم شد
|
|
- [x] `CreateNewUserCommandHandler` اصلاح شد
|
|
- [ ] Unit Tests نوشته شود
|
|
- [ ] Integration Tests انجام شود
|
|
- [ ] Manual Testing با Postman/gRPC Client
|
|
|
|
---
|
|
|
|
## 🚀 Next Steps
|
|
|
|
1. **Test کردن**: ثبت چند کاربر با Parent مشابه و بررسی LegPosition
|
|
2. **Load Testing**: بررسی Performance با 10,000 کاربر
|
|
3. **Edge Cases**: تست Binary Tree Full scenario
|
|
4. **Documentation**: Update کردن API Docs
|
|
|
|
---
|
|
|
|
## 📞 Support
|
|
|
|
اگر مشکلی پیش آمد:
|
|
- Log های `NetworkPlacementService` را بررسی کنید
|
|
- چک کنید که DI به درستی تنظیم شده باشد
|
|
- از `CanAcceptChildAsync` برای Pre-Validation استفاده کنید
|