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

17 KiB

Mission Service Architecture

Technical architecture documentation for the Mission Service.

📖 See also: README - Service Overview

High-Level Architecture

%%{init: {'theme':'dark'}}%%
graph TD
    subgraph API["🌐 API Layer"]
        Controllers[Controllers]
        Commands[Commands]
        Queries[Queries]
    end
    
    subgraph Domain["⚙️ Domain Layer"]
        Mission[Mission Aggregate]
        Task[Task Aggregate]
        CheckIn[CheckIn Aggregate]
    end
    
    subgraph Infra["💾 Infrastructure Layer"]
        EF[EF Core]
        Redis[(Redis Cache)]
        RabbitMQ[RabbitMQ]
    end
    
    subgraph Data["🗄️ Data Storage"]
        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

Architecture Patterns

CQRS Pattern

%%{init: {'theme':'dark'}}%%
flowchart LR
    subgraph Write["📝 Commands"]
        C1[StartTaskCommand]
        C2[CompleteTaskCommand]
        C3[ClaimRewardCommand]
        C4[CheckInCommand]
        C5[CreateMissionCommand]
    end
    
    subgraph Read["📖 Queries"]
        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 : has
    Mission --> MissionReward : uses

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 : tracks
    UserTask --> TaskEvidence : has
    UserTask --> VerificationResult : verified by

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 : records
    UserCheckIn --> StreakBonus : applies

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 watch duration
        ClickData,          // Click information
        UploadedContent,    // Uploaded content URL
        SocialProof,        // Screenshot of social action
        InviteCode          // Used invite code
    }
}

Database Schema

ER Diagram

%%{init: {'theme':'dark'}}%%
erDiagram
    Mission ||--o{ MissionRule : has
    Mission ||--o{ UserTask : generates
    UserTask }o--|| User : belongs
    User ||--o| UserCheckIn : has
    UserCheckIn ||--o{ CheckInDay : records
    
    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
    }

Reward Flow with Wallet Service

Claim Reward Sequence

%%{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 Valid
        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 Already Claimed
        API-->>Client: 400 Bad Request
    end

CheckIn Reward Flow

%%{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 Can 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 Already Checked In
        API-->>Client: 400 Already checked in today
    end

Integration Events

Events Published

%%{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["📥 Consumers"]
        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

Events Consumed

%%{init: {'theme':'dark'}}%%
flowchart RL
    subgraph Publishers["📤 Publishers"]
        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

// Published Events
public record MissionCompletedEvent(
    Guid TaskId,
    Guid UserId,
    Guid MissionId,
    string MissionType,
    decimal PointsEarned,
    DateTime CompletedAt
);

public record CheckInCompletedEvent(
    Guid UserId,
    int StreakDays,
    decimal BasePoints,
    decimal StreakBonus,
    decimal MilestoneBonus,
    decimal TotalPoints,
    DateTime CheckedInAt
);

// Consumed Events from IAM
public record UserRegisteredEvent(
    Guid UserId,
    string Email,
    string DisplayName,
    DateTime RegisteredAt
);

// Consumed Events from Mining
public record ReferralActivatedEvent(
    Guid InviterId,
    Guid InviteeId,
    string InviteCode,
    DateTime ActivatedAt
);

IAM Service Integration

JWT Authentication Flow

%%{init: {'theme':'dark'}}%%
sequenceDiagram
    participant Client as 📱 Client
    participant API as 🌐 Mission API
    participant Auth as 🔐 JWT Middleware
    
    Client->>API: Request + Bearer Token
    API->>Auth: Validate Token
    Auth->>Auth: Verify Signature
    Auth->>Auth: Check Expiration
    Auth->>Auth: Extract Claims
    
    alt Token Valid
        Auth->>API: UserId, Roles, Permissions
        API->>API: Process Request
        API-->>Client: 200 OK Response
    else Token Invalid
        Auth-->>Client: 401 Unauthorized
    end

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
);

Storage Service Integration

Upload Flow (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
    
    Client->>API: POST /tasks/{id}/upload
    API->>Storage: POST /api/v1/upload
    Storage-->>API: { fileId, url }
    API->>AI: Moderate Content (async)
    API-->>Client: 202 Accepted
    
    Note over AI: Background Processing
    AI-->>API: Moderation Result

Storage Service Client

public interface IStorageServiceClient
{
    Task<UploadResult> UploadFileAsync(
        Stream fileStream,
        string fileName,
        string contentType,
        CancellationToken ct);
    
    Task<SignedUrlResult> GetSignedUrlAsync(
        Guid fileId,
        TimeSpan ttl,
        CancellationToken ct);
}

public record UploadResult(Guid FileId, string Url, long SizeBytes);
public record SignedUrlResult(string SignedUrl, DateTime ExpiresAt);

Service Dependencies

%%{init: {'theme':'dark'}}%%
graph TD
    subgraph External["🔐 Authentication"]
        IAM[IAM Service]
    end
    
    subgraph Core["📋 Mission Service"]
        Mission[Mission Service]
    end
    
    subgraph Integration["🔗 Integrations"]
        Wallet[Wallet Service]
        Mining[Mining Service]
        Social[Social Service]
        Storage[Storage Service]
    end
    
    IAM -->|JWT Validation| Mission
    Mission -->|Grant Points| Wallet
    Mission <-->|Referral Sync| Mining
    Mission <-->|Friend Data| Social
    Mission <-->|Media Upload| 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

Caching Strategy

Key Pattern TTL Purpose
mission:{id} 1h Cache mission details
missions:active 5m Active missions list
user:{id}:tasks 10m User's active tasks
user:{id}:checkin 24h Check-in status
config:missions 30m Mission configurations

Security

Anti-Fraud Flow

%%{init: {'theme':'dark'}}%%
flowchart TD
    Request([🚀 Request]) --> RateLimit{⏱️ Rate Limit}
    RateLimit -->|Exceeded| Block[❌ Block]
    RateLimit -->|OK| Cooldown{⏳ Cooldown}
    Cooldown -->|Too Fast| Wait[⏸️ Wait]
    Cooldown -->|OK| Device{🔍 Device Check}
    Device -->|New/Suspicious| Flag[⚠️ Flag for Review]
    Device -->|OK| Evidence{📝 Evidence Valid?}
    Evidence -->|No| Reject[❌ Reject]
    Evidence -->|Yes| Approve[✅ Approve & Reward]
    
    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 Check
/health/live Service running
/health/ready DB + Redis + RabbitMQ connected
/health Full status

Observability

Metrics

  • Tasks started per minute
  • Tasks completed per minute
  • Check-in rate
  • Reward claims
  • Verification success rate

Logging

  • Structured JSON logs (Serilog)
  • Correlation IDs for tracing
  • Sensitive data masking