Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

README.md

FeedService - Personalized Video Feeds API

A .NET 8 Web API for serving personalized video content to mobile SDK clients with TikTok-style vertical video feeds.

Architecture Overview

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  Client SDK │────▶│    Feed     │────▶│   Ranker    │ (simulated)
│             │     │   Service   │     │   Service   │
└─────────────┘     └─────────────┘     └─────────────┘
                          │
                          ▼
                    ┌───────────┐
                    │   Cache   │  (in-memory)
                    │  (Redis)  │
                    └───────────┘

Project Structure

FeedService/
├── src/
│   ├── FeedService.Api/              # API layer (controllers, Program.cs)
│   ├── FeedService.Application/      # Business logic (MediatR handlers, DTOs)
│   └── FeedService.Infrastructure/   # Data access (mock repositories)
└── tests/
    └── FeedService.Tests/            # Test project

Features

  • Personalized Feeds: Content recommendations based on user category affinities
  • Cold Start Support: Default feed for new users without profiles
  • Prepared Feeds: Cache optimization for pagination
  • Cursor-based Pagination: Efficient feed scrolling
  • Event Ingestion: Batch processing of user interaction events
  • Multi-tenant: Tenant isolation via headers

Getting Started

Prerequisites

  • .NET 8 SDK

Installation

  1. Clone the repository

  2. Restore dependencies:

    cd FeedService
    dotnet restore
  3. Build the solution:

    dotnet build

Running the Service

cd src/FeedService.Api
dotnet run

The API will be available at:

  • HTTP: http://localhost:5000
  • Swagger UI: http://localhost:5000/swagger

API Endpoints

GET /api/v1/feed

Get personalized video feed for a user.

Headers:

  • X-Tenant-Id (required): Tenant identifier
  • X-User-Id (optional): Hashed user ID for personalization
  • If-None-Match (optional): ETag for cache validation

Query Parameters:

  • limit (optional, default=20): Number of items to return (1-100)
  • cursor (optional): Pagination cursor from previous response

Response:

{
  "items": [
    {
      "id": "vid_001",
      "title": "Amazing Football Goal #1",
      "videoUrl": "https://cdn.example.com/v/vid_001.mp4",
      "thumbnailUrl": "https://cdn.example.com/t/vid_001.jpg",
      "durationSeconds": 45,
      "categories": ["football"]
    }
  ],
  "cursor": "eyJvZmZzZXQiOjIwfQ==",
  "hasMore": true,
  "metadata": {
    "isPersonalized": true,
    "feedType": "personalized",
    "source": "pool_based"
  }
}

Response Headers:

  • ETag: Cache validation token
  • Cache-Control: Caching directives
  • X-Feed-Type: Feed type (personalized/cold_start/pool_based)

POST /api/v1/events

Ingest batch of user interaction events.

Headers:

  • X-Tenant-Id (required): Tenant identifier
  • X-User-Id (required): Hashed user ID

Request Body:

{
  "events": [
    {
      "type": "video_watch",
      "videoId": "vid_001",
      "watchDurationSeconds": 38,
      "watchPercentage": 0.84,
      "timestamp": "2024-12-03T14:22:00Z"
    },
    {
      "type": "video_like",
      "videoId": "vid_001",
      "timestamp": "2024-12-03T14:22:30Z"
    }
  ]
}

Response (202 Accepted):

{
  "accepted": true,
  "processedCount": 2
}

Testing the API

Test Users

The service includes three predefined test users:

1. Sports Fan (user_sports_fan)

curl http://localhost:5000/api/v1/feed?limit=20 \
  -H "X-Tenant-Id: tenant-alpha" \
  -H "X-User-Id: user_sports_fan"

Expected: Feed with mainly football and sports content

2. Comedy Lover (user_comedy_lover)

curl http://localhost:5000/api/v1/feed?limit=20 \
  -H "X-Tenant-Id: tenant-alpha" \
  -H "X-User-Id: user_comedy_lover"

Expected: Feed with comedy and entertainment content

3. Balanced User (user_balanced)

curl http://localhost:5000/api/v1/feed?limit=20 \
  -H "X-Tenant-Id: tenant-alpha" \
  -H "X-User-Id: user_balanced"

Expected: Evenly distributed content across tech, news, cooking, and music

4. Cold Start (Anonymous)

curl http://localhost:5000/api/v1/feed?limit=20 \
  -H "X-Tenant-Id: tenant-alpha"

Expected: Default feed with football, sports, and cats content

Pagination Example

# First request
curl http://localhost:5000/api/v1/feed?limit=20 \
  -H "X-Tenant-Id: tenant-alpha" \
  -H "X-User-Id: user_sports_fan"

# Copy cursor from response and use for next page
curl "http://localhost:5000/api/v1/feed?limit=20&cursor=eyJvZmZzZXQiOjIwfQ==" \
  -H "X-Tenant-Id: tenant-alpha" \
  -H "X-User-Id: user_sports_fan"

Submit Events Example

curl -X POST http://localhost:5000/api/v1/events \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: tenant-alpha" \
  -H "X-User-Id: user_sports_fan" \
  -d '{
    "events": [
      {
        "type": "video_watch",
        "videoId": "vid_football_001",
        "watchPercentage": 0.84,
        "watchDurationSeconds": 38,
        "timestamp": "2024-12-08T14:22:00Z"
      },
      {
        "type": "video_like",
        "videoId": "vid_football_001",
        "timestamp": "2024-12-08T14:22:30Z"
      }
    ]
  }'

How It Works

Feed Generation Flow

  1. First Request (offset=0):

    • Get user profile via MediatR query (checks cache → falls back to repository)
    • Calculate category allocation based on affinities
    • Pull videos from category pools
    • Exclude already-seen videos (from cache)
    • Shuffle and return first batch
    • Fire async event to prepare personalized feed
    • Mark returned videos as seen
  2. Pagination (offset>0):

    • Check for prepared feed in cache
    • If exists: return from prepared feed
    • If not: fall back to pool-based approach
  3. Event Processing:

    • Validate batch (max 100 events)
    • Publish to message queue (simulated)
    • Return 202 Accepted immediately

User Profile Caching

User profiles are cached with a two-tier approach:

  1. Cache Layer: 15-minute TTL for fast access
  2. Repository Layer: Mock data store simulating database

The GetUserProfileQuery handler checks cache first, then falls back to repository if needed.

Category Pools

Mock data includes 80 videos per category:

  • football
  • sports
  • comedy
  • entertainment
  • gaming
  • tech
  • news
  • cooking
  • music
  • fitness
  • cats

Videos are sorted by trending score within each pool.

Tech Stack

  • .NET 8: Latest LTS version
  • MediatR: CQRS pattern implementation
  • FluentValidation: Request validation
  • IMemoryCache: In-memory caching (simulates Redis)
  • Swagger/OpenAPI: API documentation

Design Patterns

  • CQRS: Commands and Queries separated via MediatR
  • Repository Pattern: Data access abstraction
  • Dependency Injection: Constructor injection throughout
  • Options Pattern: Configuration management
  • Factory Pattern: Mock data generation

Performance Considerations

  • Async/Await: All I/O operations are asynchronous
  • Cursor Pagination: O(1) skip using prepared feeds
  • Cache-First: User profiles and seen videos cached
  • Fire-and-Forget: Event publishing doesn't block response
  • Batch Processing: Events ingested in batches (max 100)

Future Enhancements

  • Real Redis integration
  • Database persistence (PostgreSQL/MongoDB)
  • Message queue (RabbitMQ/Kafka)
  • Real-time ranking service integration
  • A/B testing framework
  • Analytics and monitoring
  • Rate limiting
  • Authentication/Authorization

License

MIT