Files
pos-system/services/mission-service-net/docs/vi/ARCHITECTURE.md

20 KiB

Kiến Trúc Mission Service

Tài liệu kiến trúc kỹ thuật cho Mission Service.

📖 Xem thêm: README - Tổng Quan Dịch Vụ

Kiến Trúc Tổng Quan

%%{init: {'theme':'dark'}}%%
graph TD
    subgraph API["🌐 Tầng API"]
        Controllers[Controllers]
        Commands[Commands]
        Queries[Queries]
    end
    
    subgraph Domain["⚙️ Tầng Domain"]
        Mission[Mission Aggregate]
        Task[Task Aggregate]
        CheckIn[CheckIn Aggregate]
    end
    
    subgraph Infra["💾 Tầng Infrastructure"]
        EF[EF Core]
        Redis[(Redis Cache)]
        RabbitMQ[RabbitMQ]
    end
    
    subgraph Data["🗄️ Lưu Trữ Dữ Liệu"]
        DB[(PostgreSQL)]
    end
    
    API --> Domain
    Domain --> Infra
    EF --> DB
    
    style API fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:3px
    style Domain fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
    style Infra fill:#34495E,color:#ECF0F1,stroke:#2C3E50,stroke-width:2px
    style Data fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px

Các Mẫu Kiến Trúc

CQRS Pattern

%%{init: {'theme':'dark'}}%%
flowchart LR
    subgraph Write["📝 Commands (Ghi)"]
        C1[StartTaskCommand]
        C2[CompleteTaskCommand]
        C3[ClaimRewardCommand]
        C4[CheckInCommand]
        C5[CreateMissionCommand]
    end
    
    subgraph Read["📖 Queries (Đọc)"]
        Q1[GetMissionsQuery]
        Q2[GetUserTasksQuery]
        Q3[GetCheckInStatusQuery]
        Q4[GetAnalyticsQuery]
    end
    
    style Write fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
    style Read fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px

Domain Model

Mission Aggregate

%%{init: {'theme':'dark'}}%%
classDiagram
    class Mission {
        +Guid Id
        +string Code
        +LocalizedString Title
        +LocalizedString Description
        +MissionType Type
        +MissionCategory Category
        +MissionReward Reward
        +FrequencyType Frequency
        +int MaxCompletions
        +DateTime StartDate
        +DateTime EndDate
        +MissionStatus Status
        +int Priority
        +Activate()
        +Pause()
        +ValidateCompletion()
    }
    
    class MissionRule {
        +Guid Id
        +string RuleType
        +string Operator
        +string Value
        +string Metadata
    }
    
    class MissionReward {
        +decimal Points
        +decimal MiningBoost
        +int ExperiencePoints
        +string BadgeId
    }
    
    Mission "1" --> "*" MissionRule : chứa
    Mission --> MissionReward : sử dụng

Task Aggregate

%%{init: {'theme':'dark'}}%%
classDiagram
    class UserTask {
        +Guid Id
        +Guid UserId
        +Guid MissionId
        +TaskStatus Status
        +TaskProgress Progress
        +TaskEvidence Evidence
        +VerificationResult Verification
        +bool RewardClaimed
        +DateTime StartedAt
        +DateTime CompletedAt
        +Start()
        +UpdateProgress()
        +SubmitEvidence()
        +Complete()
        +ClaimReward()
    }
    
    class TaskProgress {
        +int CurrentValue
        +int TargetValue
        +decimal PercentComplete
        +DateTime LastUpdated
    }
    
    class TaskEvidence {
        +string EvidenceType
        +string Data
        +string ScreenshotUrl
        +DateTime CapturedAt
    }
    
    class VerificationResult {
        +bool IsValid
        +string FailureReason
        +VerificationMethod Method
        +Guid VerifiedBy
        +DateTime VerifiedAt
    }
    
    UserTask --> TaskProgress : theo dõi
    UserTask --> TaskEvidence : có
    UserTask --> VerificationResult : xác thực bởi

CheckIn Aggregate

%%{init: {'theme':'dark'}}%%
classDiagram
    class UserCheckIn {
        +Guid Id
        +Guid UserId
        +int CurrentStreak
        +int LongestStreak
        +int TotalCheckIns
        +DateOnly LastCheckInDate
        +CheckIn()
        +CanCheckInToday()
        +GetStreakBonus()
        +GetMonthlyReward()
    }
    
    class CheckInDay {
        +Guid Id
        +DateOnly Date
        +decimal PointsEarned
        +bool IsMilestone
    }
    
    class StreakBonus {
        +int MinDays
        +int MaxDays
        +decimal BonusPercent
        +decimal MilestoneBonus
    }
    
    UserCheckIn "1" --> "*" CheckInDay : ghi nhận
    UserCheckIn --> StreakBonus : áp dụng

Reward Aggregate

%%{init: {'theme':'dark'}}%%
classDiagram
    class UserReward {
        +Guid Id
        +Guid UserId
        +Guid SourceId
        +RewardType Type
        +RewardStatus Status
        +RewardAmount Amount
        +DateTime EarnedAt
        +DateTime ClaimedAt
        +Claim()
        +Expire()
    }
    
    class RewardAmount {
        +decimal Points
        +decimal BonusPoints
        +string Currency
        +TotalPoints()
    }
    
    class RewardType {
        <<enumeration>>
        MISSION_COMPLETE
        CHECKIN_DAILY
        CHECKIN_MILESTONE
        REFERRAL_BONUS
        SOCIAL_ACTION
    }
    
    UserReward --> RewardAmount : has
    UserReward --> RewardType : uses

Value Objects

MissionReward

public record MissionReward : ValueObject
{
    public decimal Points { get; init; }
    public decimal MiningBoostPercent { get; init; }
    public int ExperiencePoints { get; init; }
    public string? BadgeId { get; init; }
    
    public static MissionReward Create(
        decimal points, 
        decimal miningBoost = 0,
        int xp = 0,
        string? badge = null)
    {
        Guard.Against.Negative(points, nameof(points));
        Guard.Against.Negative(miningBoost, nameof(miningBoost));
        
        return new MissionReward
        {
            Points = points,
            MiningBoostPercent = miningBoost,
            ExperiencePoints = xp,
            BadgeId = badge
        };
    }
}

TaskProgress

public record TaskProgress : ValueObject
{
    public int CurrentValue { get; init; }
    public int TargetValue { get; init; }
    public DateTime LastUpdated { get; init; }
    
    public decimal PercentComplete => 
        TargetValue > 0 
            ? Math.Min(100, (CurrentValue * 100m) / TargetValue) 
            : 0;
    
    public bool IsComplete => CurrentValue >= TargetValue;
    
    public TaskProgress UpdateProgress(int newValue)
    {
        return this with 
        { 
            CurrentValue = Math.Min(newValue, TargetValue),
            LastUpdated = DateTime.UtcNow
        };
    }
}

TaskEvidence

public record TaskEvidence : ValueObject
{
    public EvidenceType Type { get; init; }
    public string Data { get; init; } = string.Empty;
    public string? ScreenshotUrl { get; init; }
    public string? VideoUrl { get; init; }
    public DateTime CapturedAt { get; init; }
    
    public enum EvidenceType
    {
        WatchDuration,      // Video xem được bao lâu
        ClickData,          // Thông tin click
        UploadedContent,    // URL nội dung tải lên
        SocialProof,        // Screenshot hành động xã hội
        InviteCode          // Mã mời đã sử dụng
    }
}

Database Schema

ER Diagram

%%{init: {'theme':'dark'}}%%
erDiagram
    Mission ||--o{ MissionRule : có
    Mission ||--o{ UserTask : tạo ra
    UserTask }o--|| User : thuộc về
    User ||--o| UserCheckIn : có
    UserCheckIn ||--o{ CheckInDay : ghi nhận
    
    Mission {
        uuid Id PK
        string Code UK
        json Title
        json Description
        string Type
        string Category
        json Reward
        string Frequency
        int MaxCompletions
        datetime StartDate
        datetime EndDate
        string Status
        int Priority
        datetime CreatedAt
    }
    
    MissionRule {
        uuid Id PK
        uuid MissionId FK
        string RuleType
        string Operator
        string Value
        json Metadata
    }
    
    UserTask {
        uuid Id PK
        uuid UserId FK
        uuid MissionId FK
        string Status
        json Progress
        json Evidence
        json Verification
        bool RewardClaimed
        datetime StartedAt
        datetime CompletedAt
    }
    
    UserCheckIn {
        uuid Id PK
        uuid UserId FK
        int CurrentStreak
        int LongestStreak
        int TotalCheckIns
        date LastCheckInDate
    }
    
    CheckInDay {
        uuid Id PK
        uuid UserCheckInId FK
        date Date
        decimal PointsEarned
        bool IsMilestone
    }

Luồng Nhận Thưởng với Wallet Service

Luồng Nhận Thưởng Task

%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant Client as 📱 Client
    participant API as 🌐 Mission API
    participant Task as ⚙️ TaskAggregate
    participant DB as 💾 PostgreSQL
    participant MQ as 📨 RabbitMQ
    participant Wallet as 💰 Wallet Service
    
    Client->>API: POST /api/v1/tasks/{id}/claim
    API->>Task: ClaimRewardCommand
    Task->>Task: ValidateTaskCompleted()
    Task->>Task: ValidateNotAlreadyClaimed()
    
    alt Task Hợp Lệ
        Task->>Task: MarkRewardClaimed()
        Task->>DB: SaveChanges()
        Task->>MQ: Publish MissionCompletedEvent
        MQ->>Wallet: Consume MissionCompletedEvent
        Wallet->>Wallet: GrantPoints(userId, amount)
        API-->>Client: 200 OK { points_earned }
    else Đã Nhận Thưởng
        API-->>Client: 400 Bad Request
    end

Luồng Thưởng Check-in

%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant Client as 📱 Client
    participant API as 🌐 Mission API
    participant CheckIn as 📅 CheckInAggregate
    participant DB as 💾 PostgreSQL
    participant MQ as 📨 RabbitMQ
    participant Wallet as 💰 Wallet Service
    
    Client->>API: POST /api/v1/checkin
    API->>CheckIn: CheckInCommand
    CheckIn->>CheckIn: CanCheckInToday()
    
    alt Có Thể Check-in
        CheckIn->>CheckIn: IncrementStreak()
        CheckIn->>CheckIn: CalculateStreakBonus()
        CheckIn->>CheckIn: CheckMilestone()
        CheckIn->>DB: SaveCheckInDay()
        CheckIn->>MQ: Publish CheckInCompletedEvent
        MQ->>Wallet: Consume CheckInCompletedEvent
        Wallet->>Wallet: GrantPoints(userId, dailyPoints + bonus)
        API-->>Client: 200 OK { streak, points, milestone }
    else Đã Check-in Hôm Nay
        API-->>Client: 400 Đã điểm danh hôm nay
    end

Integration Events

Các Event Được Xuất Bản

%%{init: {'theme':'dark'}}%%
flowchart LR
    subgraph Mission["📋 Mission Service"]
        M[Events Publisher]
    end
    
    subgraph Events["📨 Integration Events"]
        E1[MissionCompletedEvent]
        E2[CheckInCompletedEvent]
        E3[TaskStartedEvent]
        E4[RewardClaimedEvent]
    end
    
    subgraph Consumers["📥 Người Tiêu Thụ"]
        Wallet[Wallet Service]
        Mining[Mining Service]
    end
    
    M --> E1 --> Wallet
    M --> E2 --> Wallet
    M --> E3
    M --> E4 --> Wallet
    E1 -.-> Mining
    
    style Events fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
    style Wallet fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px

Các Event Được Tiêu Thụ

%%{init: {'theme':'dark'}}%%
flowchart RL
    subgraph Publishers["📤 Nhà Xuất Bản"]
        IAM[IAM Service]
        Mining[Mining Service]
    end
    
    subgraph Events["📨 Integration Events"]
        E1[UserRegisteredEvent]
        E2[UserDeletedEvent]
        E3[ReferralActivatedEvent]
    end
    
    subgraph Mission["📋 Mission Service"]
        M[Events Consumer]
    end
    
    IAM --> E1 --> M
    IAM --> E2 --> M
    Mining --> E3 --> M
    
    style Events fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
    style Mission fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:3px

Event Payloads

// Events Xuất Bản
public record MissionCompletedEvent(
    Guid TaskId,
    Guid UserId,
    Guid MissionId,
    string MissionType,
    decimal PointsEarned,       // Điểm kiếm được
    DateTime CompletedAt
);

public record CheckInCompletedEvent(
    Guid UserId,
    int StreakDays,             // Số ngày streak
    decimal BasePoints,         // Điểm cơ bản
    decimal StreakBonus,        // Thưởng streak
    decimal MilestoneBonus,     // Thưởng mốc
    decimal TotalPoints,        // Tổng điểm
    DateTime CheckedInAt
);

public record RewardClaimedEvent(
    Guid TaskId,
    Guid UserId,
    decimal PointsAmount,
    string RewardType,
    DateTime ClaimedAt
);

// Events Tiêu Thụ từ IAM
public record UserRegisteredEvent(
    Guid UserId,
    string Email,
    string DisplayName,
    DateTime RegisteredAt
);

public record UserDeletedEvent(
    Guid UserId,
    DateTime DeletedAt
);

// Events Tiêu Thụ từ Mining
public record ReferralActivatedEvent(
    Guid InviterId,
    Guid InviteeId,
    string InviteCode,
    DateTime ActivatedAt
);

Tích Hợp IAM Service

Luồng Xác Thực JWT

%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant Client as 📱 Client
    participant API as 🌐 Mission API
    participant Auth as 🔐 JWT Middleware
    participant IAM as 👤 IAM Service
    
    Client->>API: Request + Bearer Token
    API->>Auth: Validate Token
    Auth->>Auth: Verify Signature (public key)
    Auth->>Auth: Check Expiration
    Auth->>Auth: Extract Claims
    
    alt Token Hợp Lệ
        Auth->>API: UserId, Roles, Permissions
        API->>API: Xử lý Request
        API-->>Client: 200 OK Response
    else Token Không Hợp Lệ
        Auth-->>Client: 401 Unauthorized
    else Token Hết Hạn
        Auth-->>Client: 401 Token Expired
    end

Handler UserRegisteredEvent

public class UserRegisteredEventConsumer : IConsumer<UserRegisteredEvent>
{
    private readonly IMissionDbContext _dbContext;
    private readonly ILogger<UserRegisteredEventConsumer> _logger;
    
    public async Task Consume(ConsumeContext<UserRegisteredEvent> context)
    {
        var @event = context.Message;
        
        // Tạo hồ sơ check-in cho user mới
        var userCheckIn = new UserCheckIn(
            userId: @event.UserId,
            currentStreak: 0,
            longestStreak: 0,
            totalCheckIns: 0
        );
        
        _dbContext.UserCheckIns.Add(userCheckIn);
        await _dbContext.SaveChangesAsync();
        
        _logger.LogInformation(
            "Created check-in profile for new user {UserId}", 
            @event.UserId);
    }
}

IAM Service Client

public interface IIamServiceClient
{
    Task<UserInfo?> GetUserInfoAsync(Guid userId, CancellationToken ct);
    Task<List<UserInfo>> GetUsersByIdsAsync(List<Guid> userIds, CancellationToken ct);
}

public record UserInfo(
    Guid Id,
    string Email,
    string DisplayName,
    string? AvatarUrl,
    DateTime CreatedAt
);

Tích Hợp Storage Service

Luồng Upload Nội Dung (Pay Per Upload)

%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant Client as 📱 Client
    participant API as 🌐 Mission API
    participant Storage as 📦 Storage Service
    participant AI as 🤖 Content Moderation
    participant DB as 💾 PostgreSQL
    
    Client->>API: POST /tasks/{id}/upload
    API->>API: Validate Task Status
    
    API->>Storage: POST /api/v1/upload (multipart)
    Storage->>Storage: Validate File (type, size)
    Storage->>Storage: Store File
    Storage-->>API: { fileId, url, metadata }
    
    API->>AI: Moderate Content (async)
    
    API->>DB: Save TaskEvidence { fileUrl }
    API-->>Client: 202 Accepted { evidenceId }
    
    Note over AI,DB: Background Processing
    AI-->>API: Moderation Result
    API->>DB: Update Verification Status

Luồng Lấy Video cho Mission

%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant Client as 📱 Client
    participant API as 🌐 Mission API
    participant Storage as 📦 Storage Service
    
    Client->>API: GET /missions/{id}
    API->>API: Get Mission Details
    
    alt Mission có Video
        API->>Storage: GET /api/v1/files/{fileId}/signed-url
        Storage->>Storage: Generate Signed URL (TTL: 1h)
        Storage-->>API: { signedUrl, expiresAt }
        API-->>Client: Mission { ..., videoUrl }
    else Mission không có Video
        API-->>Client: Mission { ... }
    end

Storage Service Client

public interface IStorageServiceClient
{
    /// <summary>
    /// Upload file lên Storage Service
    /// </summary>
    Task<UploadResult> UploadFileAsync(
        Stream fileStream,
        string fileName,
        string contentType,
        string folder,
        CancellationToken ct);
    
    /// <summary>
    /// Lấy signed URL để truy cập file
    /// </summary>
    Task<SignedUrlResult> GetSignedUrlAsync(
        Guid fileId,
        TimeSpan ttl,
        CancellationToken ct);
    
    /// <summary>
    /// Xóa file
    /// </summary>
    Task DeleteFileAsync(Guid fileId, CancellationToken ct);
}

public record UploadResult(
    Guid FileId,
    string Url,
    string ContentType,
    long SizeBytes,
    DateTime UploadedAt
);

public record SignedUrlResult(
    string SignedUrl,
    DateTime ExpiresAt
);

Cấu Hình Storage

public class StorageServiceOptions
{
    public string BaseUrl { get; set; } = "http://storage-service:8080";
    public int TimeoutSeconds { get; set; } = 30;
    public long MaxFileSizeBytes { get; set; } = 50 * 1024 * 1024; // 50MB
    public string[] AllowedContentTypes { get; set; } = 
    {
        "image/jpeg", "image/png", "image/webp",
        "video/mp4", "video/webm"
    };
}

Phụ Thuộc Dịch Vụ

%%{init: {'theme':'dark'}}%%
graph TD
    subgraph External["🔐 Xác Thực"]
        IAM[IAM Service]
    end
    
    subgraph Core["📋 Mission Service"]
        Mission[Mission Service]
    end
    
    subgraph Integration["🔗 Tích Hợp"]
        Wallet[Wallet Service]
        Mining[Mining Service]
        Social[Social Service]
        Storage[Storage Service]
    end
    
    IAM -->|Xác thực JWT| Mission
    Mission -->|Cấp Điểm| Wallet
    Mission <-->|Đồng bộ Referral| Mining
    Mission <-->|Dữ liệu Bạn bè| Social
    Mission <-->|Upload Media| Storage
    
    style External fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
    style Core fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:3px
    style Integration fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px

Chiến Lược Cache

Key Pattern TTL Mục Đích
mission:{id} 1h Cache chi tiết mission
missions:active 5m Danh sách missions hoạt động
user:{id}:tasks 10m Tasks đang làm của user
user:{id}:checkin 24h Trạng thái check-in
config:missions 30m Cấu hình mission

Bảo Mật

Luồng Chống Gian Lận

%%{init: {'theme':'dark'}}%%
flowchart TD
    Request([🚀 Yêu Cầu]) --> RateLimit{⏱️ Rate Limit}
    RateLimit -->|Vượt Quá| Block[❌ Chặn]
    RateLimit -->|OK| Cooldown{⏳ Cooldown}
    Cooldown -->|Quá Nhanh| Wait[⏸️ Chờ]
    Cooldown -->|OK| Device{🔍 Kiểm Tra Thiết Bị}
    Device -->|Mới/Đáng Ngờ| Flag[⚠️ Đánh Dấu Xem Xét]
    Device -->|OK| Evidence{📝 Bằng Chứng Hợp Lệ?}
    Evidence -->|Không| Reject[❌ Từ Chối]
    Evidence -->|Có| Approve[✅ Duyệt & Thưởng]
    
    style Block fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
    style Reject fill:#E74C3C,color:#ECF0F1,stroke:#C0392B,stroke-width:2px
    style Approve fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px

Health Checks

Endpoint Kiểm Tra
/health/live Service đang chạy
/health/ready DB + Redis + RabbitMQ đã kết nối
/health Trạng thái đầy đủ

Observability

Metrics

  • Số tasks bắt đầu mỗi phút
  • Số tasks hoàn thành mỗi phút
  • Tỷ lệ check-in
  • Số lần nhận thưởng
  • Tỷ lệ xác thực thành công

Logging

  • Logs JSON có cấu trúc (Serilog)
  • Correlation IDs để tracing
  • Ẩn dữ liệu nhạy cảm