Series MapLesson 04 / 80
Start HereOrdered learning track

Learn Build From Scratch Recommendations System Part 004 Domain Model User Item Context Action Slate

11 min read2129 words
PrevNext
Lesson 0480 lesson track0115 Start Here

title: Build From Scratch Recommendations System - Part 004 description: Domain model inti untuk recommendation system production-grade: user, item, context, action, surface, impression, slate, candidate, exposure, feedback, dan attribution. series: learn-build-from-scratch-recommendations-system seriesTitle: Build From Scratch: Enterprise Recommendations System order: 4 partTitle: Domain Model: User, Item, Context, Action, Slate tags:

  • recommendation-system
  • recsys
  • domain-modeling
  • system-design
  • data-contract
  • java
  • series date: 2026-07-02

Part 004 — Domain Model: User, Item, Context, Action, Slate

Recommendation system yang sulit di-maintain biasanya punya masalah vocabulary.

Satu tim menyebut view, tim lain menyebut impression, tim lain menyebut shown, tim analytics menyebut exposure, dan model training menyebutnya negative example. Akhirnya sistem terlihat jalan, tetapi data, metric, experiment, dan debugging saling tidak konsisten.

Part ini membangun domain model inti. Tujuannya bukan membuat class diagram cantik. Tujuannya membuat bahasa sistem yang stabil.

Kalau vocabulary salah, model bisa benar secara matematis tetapi salah secara produk.


1. Core Function

Secara domain, recommendation system melakukan ini:

Given:
  user
  context
  surface
  objective
  constraints

Return:
  slate of items
  with position, provenance, model metadata, and logging tokens

Output bukan List<Item> polos.

Output harus menyimpan bukti keputusan.

RecommendationResponse
  request_id
  slate_id
  surface
  items[]
    item_id
    position
    candidate_source
    score
    reason
    policy_decision
    model_versions
    logging_token
  fallback_metadata
  experiment_assignments

Kenapa?

Karena tanpa slate_id, position, candidate_source, model_version, dan logging_token, kita tidak bisa menjawab:

  • item ini muncul dari generator mana?
  • kenapa item ini ada di posisi 1?
  • model versi apa yang memberi score?
  • user melihat item ini atau hanya response terkirim?
  • click ini berasal dari slate mana?
  • experiment variant mana yang bertanggung jawab?
  • apakah item ini melewati policy gate?
  • apakah terjadi fallback?

Domain model recommendation system adalah fondasi observability.


2. Entity Map

Ini bukan schema final database. Ini adalah peta konsep.


3. User Bukan Selalu Orang

User sering dianggap sederhana: satu user_id untuk satu orang.

Production jauh lebih rumit.

User identity states:
  anonymous
  pseudonymous
  logged_in
  merged
  household
  enterprise_actor
  service_account

3.1 Anonymous User

Anonymous user biasanya punya device/session identifier, bukan account id.

Masalah:

  • history pendek;
  • identity mudah hilang;
  • consent terbatas;
  • cross-device tidak stabil;
  • tidak boleh diasumsikan sebagai orang yang sama selamanya.

3.2 Logged-In User

Logged-in user punya identifier lebih stabil.

Tetapi tetap ada masalah:

  • account dipakai banyak orang;
  • user punya beberapa persona;
  • preferensi berubah;
  • work account dan personal account berbeda;
  • privacy/consent dapat berubah.

3.3 Household / Shared Account

Untuk streaming atau marketplace keluarga, satu account bisa berisi beberapa manusia.

Jika model menganggap satu account sebagai satu preference vector, rekomendasi bisa kacau.

Mitigasi:

  • profile selector;
  • session intent detection;
  • device/context separation;
  • recent-action weighting;
  • explicit preference reset.

3.4 Enterprise Actor

Dalam sistem enterprise, user bisa berupa:

  • analyst;
  • supervisor;
  • case owner;
  • queue worker;
  • compliance officer;
  • automation bot;
  • external reviewer.

Recommendation tidak hanya memprediksi “suka/tidak suka”, tetapi merekomendasikan next action sesuai role, authority, dan defensibility.


4. User Domain Object

Minimal field konseptual:

public record RecommendationUser(
    String userKey,
    IdentityState identityState,
    ConsentState consentState,
    Set<String> segments,
    Optional<String> profileId,
    Optional<String> householdId,
    Optional<String> tenantId
) {}

enum IdentityState {
    ANONYMOUS,
    PSEUDONYMOUS,
    LOGGED_IN,
    MERGED,
    ENTERPRISE_ACTOR
}

Perhatikan: userKey bukan selalu primary key account. Bisa berupa resolved serving key.

4.1 User Invariants

Invariant U1:
  Recommendation serving tidak boleh memakai feature yang consent-nya tidak valid.

Invariant U2:
  Anonymous profile tidak boleh digabung ke logged-in profile tanpa policy identity stitching.

Invariant U3:
  User deletion/privacy request harus dapat menghapus atau menonaktifkan profile-derived features.

Invariant U4:
  User segment untuk experiment harus stabil selama periode experiment yang ditentukan.

Invariant U5:
  Serving key harus cukup stabil untuk personalization, tetapi tidak melanggar privacy boundary.

5. Item Bukan Sekadar Produk/Konten

Item adalah entity yang dapat direkomendasikan.

Contoh item:

DomainItem
E-commerceproduct, variant, bundle, seller, brand, category
Videovideo, channel, playlist, live stream
Musictrack, album, artist, playlist
News/articlearticle, topic, author, publication
Jobjob posting, company, career path
Socialuser, group, post, event
Enterprisecase action, knowledge article, escalation path, similar case, policy document

Item punya lifecycle.

Recommendation system tidak boleh menganggap semua item selalu eligible.


6. Item Field yang Penting untuk Recommendation

item_id
item_type
catalog_state
availability_state
policy_state
quality_state
freshness_timestamp
metadata_version
creator_id / seller_id / provider_id
category_path
price / margin / cost
language / locale
region eligibility
age restriction
content embeddings
metadata embeddings
popularity features
cold_start_state

6.1 Item State vs Item Feature

Bedakan state dan feature.

State:
  menentukan apakah item boleh muncul.

Feature:
  membantu model menilai seberapa baik item jika boleh muncul.

Contoh:

FieldState atau Feature?Konsekuensi
out_of_stockstatehard filter
item ratingfeaturescore adjustment
policy bannedstatehard filter
new itemfeaturefreshness/exploration
seller qualitybisa keduanyafilter jika sangat buruk, feature jika normal
age restrictedstateeligibility gate

6.2 Item Invariants

Invariant I1:
  Item yang tidak eligible tidak boleh masuk response final.

Invariant I2:
  Item yang berubah menjadi suppressed harus bisa dihapus dari index/cache dengan bounded delay.

Invariant I3:
  Item metadata version yang dipakai scoring harus tercatat untuk debugging.

Invariant I4:
  Item baru harus punya fallback representation agar tidak mati karena cold start.

Invariant I5:
  Item id harus stabil; variant-level dan parent-level id tidak boleh tercampur tanpa mapping eksplisit.

7. Context: Sinyal yang Sering Diremehkan

Context menjawab:

User sedang berada dalam situasi apa ketika rekomendasi diminta?

Contoh context:

request_time
session_id
device_type
app_version
locale
region
surface
page_type
current_item_id
query
referrer
traffic_source
network_quality
timezone
session_actions
intent
experiment_context

Context sering lebih kuat daripada long-term profile.

Contoh:

  • User biasanya membeli laptop, tetapi sekarang sedang melihat stroller.
  • User biasanya menonton konten backend engineering, tetapi sekarang mencari video memasak.
  • User enterprise biasanya menangani case fraud, tetapi sekarang membuka case compliance escalation.

Recommendation yang mengabaikan context akan terasa “benar secara history, salah secara momen”.


8. Intent: Context yang Sudah Ditafsirkan

Intent bukan event mentah. Intent adalah interpretasi.

raw context:
  current_page = product_detail
  current_item_category = running_shoes
  last_3_actions = view shoes, filter size 42, sort by discount

inferred intent:
  looking_for_discounted_running_shoes_size_42

Intent bisa berasal dari:

  • query search;
  • current item;
  • session sequence;
  • filters;
  • referrer;
  • campaign;
  • previous action;
  • explicit onboarding;
  • enterprise case state.

Intent harus diperlakukan hati-hati. Salah intent bisa lebih merusak daripada tidak ada intent.

8.1 Intent Confidence

public record IntentSignal(
    String intentType,
    double confidence,
    String evidence,
    Instant inferredAt
) {}

Jangan menyimpan intent sebagai boolean polos.

Buruk:
  user.intent = "buy_laptop"

Lebih baik:
  intent_type = "buy_laptop"
  confidence = 0.72
  evidence = "search_query + product_views"
  inferred_at = 2026-07-02T10:01:00Z
  expires_at = 2026-07-02T12:01:00Z

9. Surface: Lokasi Keputusan

Surface adalah tempat recommendation muncul.

Surface memengaruhi:

  • objective;
  • layout;
  • candidate source;
  • ranking model;
  • diversity policy;
  • latency budget;
  • number of items;
  • logging semantics;
  • experiment eligibility.

Contoh:

homepage.hero
homepage.feed
product_detail.related_items
product_detail.frequently_bought_together
cart.cross_sell
checkout.last_minute_addon
search.zero_result_recovery
video.watch_next
email.digest
push_notification.reactivation
case.next_best_action

Surface harus menjadi first-class field, bukan string asal-asalan.

public record Surface(
    String surfaceId,
    SurfaceType type,
    String placement,
    String layout,
    String objectiveId,
    int maxItems,
    Duration latencyBudget
) {}

9.1 Surface Invariants

Invariant S1:
  Setiap recommendation request harus punya surface yang valid.

Invariant S2:
  Setiap surface harus punya objective id aktif.

Invariant S3:
  Latency budget harus didefinisikan per surface.

Invariant S4:
  Logging semantics harus konsisten per surface.

Invariant S5:
  Surface tidak boleh diam-diam reuse objective surface lain tanpa approval.

10. Action: Feedback User

Action adalah perilaku user yang terkait dengan exposure, item, atau session.

Taxonomy umum:

Exposure actions:
  impression
  visible_impression
  viewable_impression

Positive actions:
  click
  play
  add_to_cart
  purchase
  save
  like
  share
  apply
  accept_recommendation

Negative actions:
  skip
  hide
  dislike
  report
  not_interested
  unsubscribe
  override_recommendation

Post-action outcomes:
  dwell_time
  watch_completion
  return_refund
  employer_response
  case_resolution_success
  audit_passed

Penting: impression bukan action positif. Impression adalah exposure.

Click bukan bukti final suka. Click adalah sinyal awal.

Purchase bukan selalu satisfaction. Bisa berakhir return/refund.


11. Impression: Denominator Paling Penting

Impression adalah catatan bahwa item ditampilkan kepada user.

Tetapi ada beberapa level:

LevelDefinisiKegunaan
response itemitem dikirim serverdebug serving
rendered itemitem dirender clientUI correctness
visible impressionitem masuk viewportCTR denominator yang lebih baik
viewable impressionitem terlihat cukup lama/area cukup besarads/content quality metric
engaged impressionuser punya kesempatan interaksi nyataadvanced evaluation

Kalau sistem hanya mencatat response item, CTR bisa salah karena item mungkin tidak pernah terlihat.

11.1 Impression Schema

{
  "event_type": "impression",
  "event_id": "evt_123",
  "request_id": "req_456",
  "slate_id": "slt_789",
  "user_key": "usr_or_anon_key",
  "session_id": "ses_001",
  "surface": "homepage.feed",
  "item_id": "item_123",
  "position": 4,
  "viewport_state": "visible",
  "candidate_source": "two_tower_v3",
  "ranker_model_version": "ranker_2026_07_01",
  "reranker_version": "diversity_v2",
  "experiment_assignments": ["exp_home_ranker:B"],
  "logging_token": "opaque_signed_token",
  "occurred_at": "2026-07-02T09:12:00Z",
  "client_time": "2026-07-02T09:12:01Z",
  "server_received_at": "2026-07-02T09:12:02Z"
}

11.2 Impression Invariants

Invariant IMP1:
  Impression harus bisa dikaitkan ke slate.

Invariant IMP2:
  Impression harus punya item position.

Invariant IMP3:
  Impression harus punya surface.

Invariant IMP4:
  Impression harus punya model/experiment metadata atau logging token yang dapat resolve metadata itu.

Invariant IMP5:
  Impression event harus idempotent atau deduplicable.

12. Slate: Output yang Sebenarnya

Slate adalah daftar item yang disusun dan dikirim untuk satu request/surface.

Mengapa slate penting?

Karena user tidak melihat item secara terpisah. User melihat susunan.

Item di posisi 1 memengaruhi item posisi 2. Dua item mirip bersebelahan bisa membuat feed terasa monoton. Satu item sponsored bisa mengubah persepsi seluruh slate.

Slate = ordered decision unit

12.1 Slate Fields

slate_id
request_id
surface
generated_at
user_key/session_id
objective_id
experiment_assignments
items[]
  position
  item_id
  source
  retrieval_score
  ranking_score
  final_score
  reason
  policy_decision
  model_versions
fallback_metadata

12.2 Slate vs Page

Untuk infinite scroll, satu page response bisa satu slate atau bagian dari slate besar.

Pilihan desain:

Option A:
  setiap page request menghasilkan slate baru

Option B:
  satu session feed punya slate besar, page mengambil slice

Option C:
  hybrid: initial slate + continuation reranking

Trade-off:

DesainKelebihanKelemahan
Slate per pagefresh, sederhanaduplicate control sulit
Large precomputed slatekonsisten, mudah attributionkurang responsif ke action terbaru
Hybridseimbangstate management lebih kompleks

12.3 Slate Invariants

Invariant SL1:
  Dalam satu slate, item_id tidak boleh duplicate kecuali surface memang mendukung repetition eksplisit.

Invariant SL2:
  Position harus deterministic dan dimulai dari convention yang jelas, misalnya 0-based atau 1-based.

Invariant SL3:
  Slate harus immutable setelah dikirim.

Invariant SL4:
  Jika ada refresh/rerank, buat slate baru atau slate revision baru.

Invariant SL5:
  Semua action user harus bisa dikaitkan ke slate yang benar.

13. Candidate: Item Sebelum Ranking Final

Candidate adalah item yang dipertimbangkan.

Sumber candidate:

  • global popularity;
  • category popularity;
  • recently trending;
  • item-to-item similarity;
  • user-to-item collaborative filtering;
  • graph walk;
  • two-tower embedding retrieval;
  • search intent retrieval;
  • editorial list;
  • campaign list;
  • sponsored source;
  • fallback list.

Candidate harus membawa provenance.

public record Candidate(
    String itemId,
    String source,
    double retrievalScore,
    Map<String, String> provenance,
    Instant generatedAt
) {}

Tanpa provenance, kita tidak bisa tahu candidate generator mana yang berkontribusi atau merusak kualitas.

13.1 Candidate Lifecycle

Candidate yang ditolak juga penting untuk observability.

Contoh rejection reason:

already_seen
out_of_stock
policy_blocked
region_ineligible
age_restricted
duplicate
low_quality
missing_features
ranker_timeout
below_threshold

14. Exposure, Feedback, Outcome

Recommendation system harus membedakan tiga hal:

Exposure:
  sistem memberi kesempatan user melihat item.

Feedback:
  user bereaksi terhadap exposure.

Outcome:
  hasil lanjutan yang mungkin terjadi jauh setelah feedback awal.

Contoh e-commerce:

Exposure:
  impression product A di homepage posisi 3

Feedback:
  click product A
  add-to-cart product A

Outcome:
  purchase product A
  return/refund product A
  repeat purchase minggu depan

Contoh enterprise:

Exposure:
  next-best-action ditampilkan ke analyst

Feedback:
  analyst accepted/rejected recommendation

Outcome:
  case resolved
  escalation avoided
  audit passed

Training dataset yang mencampur exposure, feedback, dan outcome tanpa window yang jelas akan menghasilkan label yang tidak stabil.


15. Request Context dan Response Contract

15.1 Request

{
  "request_id": "req_456",
  "surface": "homepage.feed",
  "user": {
    "user_key": "usr_123",
    "identity_state": "LOGGED_IN",
    "tenant_id": "default"
  },
  "session": {
    "session_id": "ses_001",
    "recent_actions": [
      {"type": "view", "item_id": "item_10", "occurred_at": "2026-07-02T09:00:00Z"}
    ]
  },
  "context": {
    "locale": "id-ID",
    "region": "ID-JK",
    "device_type": "mobile",
    "request_time": "2026-07-02T09:12:00Z"
  },
  "constraints": {
    "max_items": 20,
    "exclude_item_ids": ["item_10"],
    "only_available": true
  },
  "debug": {
    "enabled": false
  }
}

15.2 Response

{
  "request_id": "req_456",
  "slate_id": "slt_789",
  "surface": "homepage.feed",
  "items": [
    {
      "item_id": "item_123",
      "position": 1,
      "reason": "Because you viewed running shoes",
      "logging_token": "tok_signed_abc",
      "debug": null
    }
  ],
  "fallback": {
    "used": false,
    "reason": null
  }
}

Response publik tidak harus memuat semua detail internal. Tetapi sistem harus bisa resolve logging_token ke metadata internal.


16. Logging Token

Logging token adalah opaque token yang membawa atau menunjuk metadata keputusan.

Isi konseptual:

request_id
slate_id
item_id
position
surface
candidate_source
ranker_model_version
reranker_version
experiment_assignment
score bucket
policy decision
created_at
signature

Token sebaiknya signed agar client tidak bisa memalsukan attribution.

Client receives logging_token.
Client sends token back on impression/click/action event.
Server verifies token.
Event pipeline resolves token into decision metadata.

17. Position: Field Kecil, Dampak Besar

Position memengaruhi click probability. Item posisi atas lebih mungkin diklik karena terlihat duluan.

Maka position wajib dicatat pada:

  • response item;
  • impression;
  • click;
  • conversion attribution;
  • training example;
  • evaluation dataset.

Tanpa position, model bisa salah belajar bahwa item tertentu lebih relevan, padahal hanya lebih sering diletakkan di atas.

Position juga harus jelas:

position_index: 0-based atau 1-based?
slot_type: organic, sponsored, pinned, editorial?
container_position: carousel keberapa?
item_position_in_container: item keberapa dalam carousel?

Untuk layout kompleks, satu angka position tidak cukup.


18. Surface Layout dan Slot

Banyak produk tidak hanya punya list linear.

Contoh homepage:

hero_banner
continue_watching_carousel
recommended_for_you_grid
trending_now_carousel
new_arrivals_carousel
sponsored_slot

Domain model perlu slot.

public record SlateSlot(
    String slotId,
    SlotType slotType,
    int containerPosition,
    int maxItems,
    String objectiveId
) {}

public record SlottedRecommendationItem(
    String itemId,
    String slotId,
    int positionInSlot,
    int globalPosition
) {}

Slot penting untuk:

  • attribution;
  • position bias;
  • experiment;
  • sponsored/organic separation;
  • layout-aware ranking;
  • diversity control.

19. Eligibility: Domain Rule Sebelum Model Score

Eligibility menjawab:

Apakah item boleh direkomendasikan kepada user ini dalam context ini?

Eligibility bukan ranking.

Contoh eligibility:

item active
in stock
region allowed
language compatible
age restriction passed
subscription entitlement valid
not blocked by user
not already purchased/consumed
not policy suppressed
seller allowed
tenant boundary satisfied

Eligibility sebaiknya dieksekusi sebelum ranking mahal jika memungkinkan, dan tetap diverifikasi sebelum response final.

Kenapa dua kali?

Karena state bisa berubah di tengah request atau antara cache dan final response.


20. Domain Model untuk Negative Feedback

Negative feedback sering lebih berharga daripada positive feedback.

Jenis negative feedback:

skip:
  user melewati item tanpa interaksi

hide:
  user eksplisit tidak ingin melihat item

not_interested:
  user tidak tertarik pada item/topik

dislike:
  user memberi sinyal negatif eksplisit

report:
  user menganggap item melanggar/salah/berbahaya

override:
  user menolak recommendation action enterprise

Negative feedback perlu scope.

hide item only
hide similar items
hide creator/seller
hide category/topic
hide for session only
hide permanently

Jangan menyimpan negative=true tanpa scope. Itu terlalu kasar.


21. Domain Model untuk Reasons dan Explanations

Reason bukan sekadar teks UI.

Reason adalah representasi mengapa item dipilih.

reason_type:
  similar_to_recent_item
  popular_in_category
  trending_near_you
  because_you_follow_creator
  frequently_bought_together
  editorial_pick
  required_next_action
  fallback_popular

Reason berguna untuk:

  • user trust;
  • debugging;
  • policy review;
  • analytics;
  • model interpretability;
  • compliance in enterprise systems.

Tetapi reason harus jujur. Jangan menampilkan “Because you liked X” kalau X tidak benar-benar dipakai.

public record RecommendationReason(
    String reasonType,
    String evidenceType,
    Optional<String> evidenceId,
    double confidence,
    boolean userVisible
) {}

22. Multi-Tenant Domain Boundary

Untuk enterprise, tenant boundary sangat penting.

Tenant boundary affects:
  user profile
  item catalog
  policy
  model version
  experiment
  feature store
  logging
  retention
  access control

Invariant:

Invariant T1:
  Candidate dari tenant A tidak boleh muncul untuk tenant B.

Invariant T2:
  Feature aggregate tenant A tidak boleh dipakai training/serving tenant B tanpa explicit shared model policy.

Invariant T3:
  Debug endpoint harus menghormati tenant access control.

Multi-tenant recsys bukan hanya menambahkan tenant_id di table. Tenant harus masuk ke semua key, index, metric, dan policy.


23. Minimal Relational Schema Sketch

Ini bukan schema final, tetapi baseline untuk berpikir.

CREATE TABLE rec_request (
    request_id          TEXT PRIMARY KEY,
    user_key            TEXT,
    session_id          TEXT,
    tenant_id           TEXT,
    surface             TEXT NOT NULL,
    objective_id        TEXT NOT NULL,
    occurred_at         TIMESTAMPTZ NOT NULL,
    context_json        JSONB NOT NULL
);

CREATE TABLE rec_slate (
    slate_id            TEXT PRIMARY KEY,
    request_id          TEXT NOT NULL REFERENCES rec_request(request_id),
    generated_at        TIMESTAMPTZ NOT NULL,
    fallback_used       BOOLEAN NOT NULL,
    fallback_reason     TEXT,
    experiment_json     JSONB NOT NULL
);

CREATE TABLE rec_slate_item (
    slate_id            TEXT NOT NULL REFERENCES rec_slate(slate_id),
    item_id             TEXT NOT NULL,
    position_index      INTEGER NOT NULL,
    slot_id             TEXT,
    candidate_source    TEXT NOT NULL,
    retrieval_score     NUMERIC,
    ranking_score       NUMERIC,
    final_score         NUMERIC,
    reason_type         TEXT,
    model_version       TEXT,
    policy_json         JSONB NOT NULL,
    logging_token_hash  TEXT NOT NULL,
    PRIMARY KEY (slate_id, position_index)
);

CREATE TABLE rec_event (
    event_id            TEXT PRIMARY KEY,
    event_type          TEXT NOT NULL,
    user_key            TEXT,
    session_id          TEXT,
    item_id             TEXT,
    slate_id            TEXT,
    impression_id       TEXT,
    surface             TEXT NOT NULL,
    position_index      INTEGER,
    occurred_at         TIMESTAMPTZ NOT NULL,
    received_at         TIMESTAMPTZ NOT NULL,
    payload_json        JSONB NOT NULL
);

Untuk high-throughput event, table relasional mungkin bukan storage utama. Tetapi schema ini menjelaskan kontrak dan lineage yang tetap perlu ada.


24. Aggregate Boundaries

Dalam domain-driven design, aggregate menjaga invariants.

Untuk recsys, aggregate yang mungkin:

RecommendationRequest aggregate:
  request + context + constraints

Slate aggregate:
  ordered items + metadata + experiment + fallback

UserProfile aggregate:
  preference state + suppression state + consent boundary

ItemSnapshot aggregate:
  metadata + eligibility + policy + quality state

FeedbackEvent aggregate:
  action + attribution + deduplication key

Jangan membuat satu aggregate raksasa Recommendation. Itu akan sulit di-scale dan sulit di-debug.


25. State yang Harus Immutable

Beberapa data harus immutable setelah dibuat.

Immutable:
  request record
  slate record
  slate item position
  model version used
  experiment assignment used
  logging token metadata
  original event payload

Mutable:
  user profile
  item availability
  item policy state
  feature values
  model active version
  suppression preferences

Mengapa slate harus immutable?

Karena historical event harus bisa direproduksi. Jika slate lama berubah, click lama menjadi tidak bisa dijelaskan.


26. Time Semantics

Recommendation domain punya banyak waktu.

request_time:
  kapan server menerima request rekomendasi

generated_at:
  kapan slate dibuat

visible_at:
  kapan item terlihat di client

action_occurred_at:
  kapan user melakukan action

event_received_at:
  kapan server menerima event

feature_as_of:
  feature dihitung berdasarkan data sampai kapan

model_trained_until:
  model dilatih dengan data sampai kapan

Jangan mencampur semua menjadi timestamp.

Time semantics penting untuk:

  • point-in-time correctness;
  • delayed conversion;
  • leakage control;
  • experiment attribution;
  • replay;
  • debugging;
  • audit.

27. IDs dan Idempotency

Recommendation system rentan duplicate event.

Penyebab:

  • client retry;
  • network failure;
  • user refresh;
  • infinite scroll rerender;
  • duplicate consumer processing;
  • mobile offline replay.

Minimal idempotency keys:

request_id:
  satu recommendation request

slate_id:
  satu ordered response

impression_id:
  satu item exposure event

action_id:
  satu user action

logging_token:
  attribution token untuk item dalam slate

Deduplication rule harus eksplisit.

impression dedup key:
  user_key/session_id + slate_id + item_id + position + visibility_bucket

click dedup key:
  user_key/session_id + slate_id + item_id + action_type + short_time_window

Dedup terlalu agresif bisa membuang action valid. Dedup terlalu longgar membuat metric inflated.


28. Debug View: Domain Model Harus Bisa Menjawab “Why?”

Untuk setiap item final, debug view idealnya bisa menunjukkan:

item_id: item_123
surface: homepage.feed
position: 2
candidate_sources:
  - two_tower_v3 score=0.83
  - trending_category score=0.61
eligibility:
  active=true
  in_stock=true
  region_allowed=true
  policy_allowed=true
features:
  user_category_affinity=0.72
  item_quality=0.81
  freshness_hours=5
  price_affinity=0.64
ranking:
  model_version=ranker_2026_07_01
  calibrated_ctr=0.043
  calibrated_cvr=0.008
reranking:
  diversity_penalty=0.03
  fatigue_penalty=0.00
experiments:
  homepage_ranker=B
reason:
  similar_to_recent_item item_10

Kalau domain model tidak menyimpan field-field ini, debugging akan berubah menjadi menebak-nebak.


29. Anti-Pattern Domain Modeling

29.1 List<Item> sebagai Response

Terlalu miskin. Tidak ada attribution, position, model version, source, atau logging token.

29.2 Event Tanpa Slate

Click tanpa slate tidak bisa dipakai untuk ranking analysis yang serius.

29.3 Item State Dicampur dengan Score

Item policy banned tidak boleh hanya diberi score rendah. Ia harus difilter.

Feature tanpa consent boundary adalah risiko privacy.

29.5 Surface Hanya String Bebas

String bebas membuat metric pecah: home_feed, homepage-feed, homePageFeed, feed_home.

29.6 Rewriting Historical Slate

Jika slate lama dimutasi, audit dan training lineage rusak.

29.7 Negative Feedback Tanpa Scope

not_interested=true tidak cukup. Tidak jelas tidak tertarik pada item, creator, category, atau session context.


Untuk implementasi Java production, struktur domain bisa dimulai seperti ini:

com.example.recsys.domain
  user
    RecommendationUser.java
    IdentityState.java
    ConsentState.java
  item
    RecommendableItem.java
    ItemState.java
    EligibilityState.java
  context
    RecommendationContext.java
    Surface.java
    IntentSignal.java
  candidate
    Candidate.java
    CandidateSource.java
    CandidateProvenance.java
  ranking
    RankedItem.java
    ScoreBreakdown.java
    ModelVersion.java
  slate
    Slate.java
    SlateItem.java
    SlateSlot.java
  feedback
    ImpressionEvent.java
    UserActionEvent.java
    FeedbackType.java
  policy
    EligibilityDecision.java
    PolicyDecision.java
  objective
    RecommendationObjective.java
    MetricDefinition.java

Prinsipnya: domain object harus menjelaskan keputusan, bukan hanya memindahkan JSON.


31. End-to-End Domain Flow

Domain model mengikat flow ini dari request sampai feedback.


32. Kesimpulan

Domain model recommendation system harus menjawab lima pertanyaan:

Who?
  user, identity, consent, segment

What?
  item, state, metadata, eligibility

Where?
  surface, slot, layout, position

Why?
  candidate source, model score, reason, objective, experiment

What happened after?
  impression, action, outcome, attribution

Kalau kelima pertanyaan ini tidak bisa dijawab, sistem akan sulit dievaluasi, sulit di-debug, sulit diaudit, dan sulit ditingkatkan.

Part berikutnya akan membahas invariants dan failure modes: aturan apa yang harus selalu benar di recommendation system production-grade, serta bagaimana sistem gagal secara diam-diam walaupun API masih return 200 OK.


References

  • Paul Covington, Jay Adams, Emre Sargin — Deep Neural Networks for YouTube Recommendations, 2016.
  • Dietmar Jannach et al. — literature on implicit feedback and recommender systems.
  • Yuta Saito et al. — Unbiased Recommender Learning from Missing-Not-At-Random Implicit Feedback, 2019.
  • Jiawei Chen et al. — Bias and Debias in Recommender System: A Survey and Future Directions, 2020.
  • Fabio B. P. Maurera et al. — Impression-Aware Recommender Systems, 2025.
Lesson Recap

You just completed lesson 04 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.