Skip to main content

Purpose

The Engagement Service is the sole owner of all user interaction state on MCSP. It is intentionally isolated from the Content Service to prevent high-frequency engagement writes on viral content (thousands of concurrent view count updates, like storms) from contending with content metadata reads and degrading the discovery and playback paths.
Write path is Redis-first. View count increments and like count changes write to Redis immediately (atomic INCR / SET) and flush to Postgres in batches via background jobs. This prevents Postgres write saturation on viral content. Reads during the flush window may return slightly stale counts — this is an accepted and documented eventual consistency tradeoff.

Responsibilities

DomainDetail
Likes / DislikesRecords per-user like/dislike state in Postgres. Aggregated counts cached in Redis with write-through on each change. A user may not simultaneously hold both a like and a dislike on the same content — enforced at the service layer.
Comments & RepliesComment threads stored in Postgres: content_id, user_id, parent_comment_id (for replies), body, timestamp, soft_delete_flag. Pagination via cursor. Creators may delete any comment on their own content.
SharesShare events (platform and external) recorded for analytics purposes. Canonical share URL construction delegates to the Content Service.
View CountsIncrements written to Redis (atomic INCR per contentId). Background job flushes Redis counters to Postgres every 60 seconds and clears the Redis delta.
Watch HistoryPer-user watch history in Postgres: user_id, content_id, last_watched_at, progress_pct. Resume position read by the Playback Service at session start. User-initiated history clear cascades to all records.
PlaylistsCRUD for named playlists. Playlist items stored as an ordered list in Postgres.
Creator SubscriptionsViewer-to-creator subscription relationships in Postgres. Subscription counts cached in Redis. Subscribe/unsubscribe events emitted to Kafka for fan-out to the Notification Service.
ML signal emissionAll engagement events (likes, completions, replays, skips) are emitted to user.engagement.events for the ML Feature Store.
Idempotency is mandatory for all async workers. Before executing any operation, every Kafka consumer checks a Redis-backed idempotency store keyed on {topic}:{partition}:{offset}. Processed offsets expire after 24 hours. Billing operations additionally pass processor-issued idempotency keys to Paystack/Stripe.

API Surface

MethodEndpointAuthDescription
POST/api/v1/content/{contentId}/likeBearerLike a content item
DELETE/api/v1/content/{contentId}/likeBearerRemove like
POST/api/v1/content/{contentId}/dislikeBearerDislike a content item
GET/api/v1/content/{contentId}/commentsBearer or NoneFetch paginated comment thread
POST/api/v1/content/{contentId}/commentsBearerPost a comment or reply
DELETE/api/v1/content/{contentId}/comments/{commentId}BearerDelete own comment (or creator deleting on own content)
GET/api/v1/users/me/historyBearerFetch watch history
DELETE/api/v1/users/me/historyBearerClear watch history
GET/api/v1/users/me/playlistsBearerList playlists
POST/api/v1/users/me/playlistsBearerCreate playlist
POST/api/v1/users/me/playlists/{playlistId}/itemsBearerAdd content to playlist
POST/api/v1/creators/{creatorId}/subscribeBearerSubscribe to creator
DELETE/api/v1/creators/{creatorId}/subscribeBearerUnsubscribe from creator

Data Owned

StoreContent
Postgreslikes, comments, shares, watch_history, playlists, playlist_items, creator_subscriptions
Redisview_count:{contentId} (atomic counters), like_count:{contentId}, sub_count:{creatorId}, like_state:{userId}:{contentId}

Kafka Topics

TopicAction
user.engagement.eventsProduced — all engagement events (like, view, completion, skip, replay)
engagement.subscription.changedProduced — subscribe/unsubscribe events for Notification Service fan-out

Failure Behaviour

FailureBehaviour
Redis unavailableLike/comment writes fall back to direct Postgres writes (higher latency, higher DB load). View count increments queue client-side and retry on reconnect — brief counts may be understated.
Engagement Service unavailablePlayback continues (resume position omitted). Like/comment writes queued client-side for retry. View counts tolerate brief gaps via eventual consistency.
Flush job failureRedis counters accumulate. On recovery, flush job processes all accumulated deltas. No data is lost — Redis is the write buffer, not a cache that can be dropped.

Build docs developers (and LLMs) love