Carrot Farmer
Project Overview
Onion Architecture
System Architecture
Microservices
Docker Compose
JWT Authentication (code)
gRPC - Inter-service Communication
Kafka - Async Notifications
FluentValidation
Polly - Resilience
Unity Integration
Offline-First Pattern (Unity)
Summary
677.35K

CarrotFarmer_Presentation

1. Carrot Farmer

MICROSERVICES ARCHITECTURE WITH UNITY INTEGRATION
EDUCATIONAL PROJECT - ASP.NET CORE

2. Project Overview

3 microservices on ASP.NET Core (.NET 8/9)
API Gateway based on YARP
Web application (ASP.NET MVC + Bootstrap 5)
Unity game with backend integration
Async messaging via Kafka
gRPC for inter-service communication
JWT + Refresh Tokens authentication

3. Onion Architecture

Each microservice is divided into 4 layers:
API Layer (Controllers, Middleware)
|
Infrastructure Layer (Repositories, DbContext, gRPC, Kafka)
|
Application Layer (Services, DTOs, Validators)
|
Domain Layer (Entities, Interfaces)

4. System Architecture

CLIENTS
Unity Game
Web MVC (7222)
\
/
API Gateway (YARP:7055)
/
|
\
Auth:7273
Blog:7011
Statistics:7083
|
|
|
SQL:1433
SQL:1434
SQL:1435
Auth <--gRPC-- Blog
Auth <--gRPC-- Statistics
Auth <--Kafka-- Blog (notifications)

5. Microservices

Auth Service (port 7273):
Blog Service (port 7011):
- CRUD articles and comments
- gRPC Client -> Auth
- Kafka Producer (notifications)
- JWT + Refresh Tokens
- Users, Roles, Notifications
- gRPC Server, Kafka Consumer
- S3/MinIO for avatars
Statistics Service (port 7083):
- Carrot statistics
- gRPC Client -> Auth
- Game API for Unity

6. Docker Compose

services:
auth-db:
image: mcr.microsoft.com/mssql/server:2022-latest
ports: ['1433:1433']
zookeeper:
image: confluentinc/cp-zookeeper:7.5.0
blog-db:
image: mcr.microsoft.com/mssql/server:2022-latest
ports: ['1434:1433']
kafka:
image: confluentinc/cp-kafka:7.5.0
ports: ['9092:9092']
statistics-db:
image: mcr.microsoft.com/mssql/server:2022-latest
ports: ['1435:1433']

7. JWT Authentication (code)

// AuthService.cs
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Role, 'User')
}),
Expires = DateTime.UtcNow.AddMinutes(60),
Issuer = 'AuthService',
Audience = 'AuthClient',
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};

8. gRPC - Inter-service Communication

// user.proto
service UserService {
rpc CheckUserExists (UserExistsRequest)
returns (UserExistsResponse);
rpc GetUser (GetUserRequest)
returns (GetUserResponse);
rpc GetUsers (GetUsersRequest)
returns (GetUsersResponse);
// Batch
}
// Usage (Statistics -> Auth)
var response = await _client.CheckUserExistsAsync(
new UserExistsRequest { UserId = id.ToString() }
);
return response.Exists;

9. Kafka - Async Notifications

Blog Service (Producer):
var message = new Message<string, string> {
Key = notification.PostAuthorId.ToString(),
Value = JsonSerializer.Serialize(notification)
};
await _producer.ProduceAsync('notifications', msg);
Auth Service (Consumer - BackgroundService):
consumer.Subscribe('notifications');
while (!stoppingToken.IsCancellationRequested) {
var result = consumer.Consume();
var data = JsonSerializer.Deserialize<...>(result);
await _notificationRepo.AddAsync(data);
}

10. FluentValidation

public class RegisterRequestValidator
: AbstractValidator<RegisterRequest>
{
public RegisterRequestValidator()
{
RuleFor(x => x.Username)
.NotEmpty().WithMessage('Required')
.MinimumLength(3)
.Matches('^[a-zA-Z0-9_]+$');
RuleFor(x => x.Password)
.MinimumLength(6)
.Matches('[A-Z]').WithMessage('Need uppercase')
.Matches('[0-9]').WithMessage('Need digit');
}
}

11. Polly - Resilience

_pipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions {
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential
// 200ms -> 400ms -> 800ms
})
.AddCircuitBreaker(new CircuitBreakerOptions {
FailureRatio = 0.5,
// 50% failures
SamplingDuration = TimeSpan.FromSeconds(30),
BreakDuration = TimeSpan.FromSeconds(30)
})
.Build();
// Usage
await _pipeline.ExecuteAsync(async ct => {
return await _client.GetUserAsync(request, ct);
});

12. Unity Integration

// FormLogin.cs - Authentication
IEnumerator SendLoginRequest(string user, string pass) {
string url = GameConfig.AuthEndpoint +
'?login=' + user + '&password=' + pass;
UnityWebRequest www = UnityWebRequest.Get(url);
// CarrotController.cs - Collect carrot
yield return www.SendWebRequest();
void OnTriggerEnter(Collider other) {
GameDataManager.Instance.AddCarrot();
Destroy(gameObject);
var json = JSON.Parse(www.downloadHandler.text);
if (json['success'].AsBool) {
UserController.Instance.EnterOnlineMode(
json['token'], json['userId'], 0);
}
}
}

13. Offline-First Pattern (Unity)

public void AddCarrot() {
localData.carrots++;
if (IsStatisticsConnected) {
// Online - send to server
// On connection restored - sync queue
StartCoroutine(SendCarrotToServer());
while (pendingCarrotsToSync > 0 && IsConnected) {
} else {
await SendCarrot();
// Offline - save to queue
localData.pendingCarrotsToSync++;
SaveLocalData();
// Try to restore connection
StartCoroutine(QuickConnectionCheck());
}
}
pendingCarrotsToSync--;
}

14. Summary

Patterns Used:
* Microservices Architecture
* Onion Architecture
* API Gateway Pattern (YARP)
* Database per Service
* Event-Driven (Kafka)
* Circuit Breaker + Retry (Polly)
* Offline-First (Unity)
* JWT + Refresh Tokens
English     Русский Правила