17 KiB
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