talent-unlock-quota

Talent Unlock and Quota Enforcement

In the YouTeacher profile service, teacher (talent) profiles are searchable but their contact details are gated. A hiring-side viewer — a verified employer or an approved recruiter — spends unlock quota to reveal one teacher's contact details. This node describes that gate: the domain rules that decide whether an unlock is allowed, and the flow that records it.

The three domain rules

The decision lives in a single domain service, UnlockService, which validates a viewer against a talent in a fixed order and returns the first failing rule (as a coded result, not an exception). The two viewer types run parallel checks:

  • Standing — an employer must be verified; a recruiter must be approved. A viewer who is neither cannot unlock anyone (employer_not_verified / recruiter_not_approved).
  • Visibility — the talent must be visible to that audience. Employers are checked against school-facing visibility; recruiters against recruiter-facing visibility. A hidden talent cannot be unlocked (talent_not_visible).
  • Quota — the viewer must have remaining unlock quota. When exhausted, the unlock is refused (quota_exceeded).

Only when all three pass does the service report canUnlock: true.

Quota as a value object

Quota is modelled as an immutable value object holding a total and a used count, with remaining derived as total − used (it is never stored separately — a single source of truth). Its invariants are enforced in the constructor: neither count may be negative, and used may never exceed total. Consuming quota does not mutate the object; it returns a new Quota with used incremented, and refuses when there is nothing left to consume. A separate adjust operation resets the total or used count. Because remaining is always computed, the object can never drift into an inconsistent "remaining" value.

The unlock flow

The employer and recruiter application handlers are near-identical. Each execute(userId, talentId) runs the same sequence:

  1. Resolve the viewer's profile from the user id; if there is none, the request fails as not-found.
  2. Resolve the talent by id; if there is none, not-found.
  3. Idempotency check — look up an existing unlock for this (talent, viewer, viewer-type). If one already exists, return it unchanged. A viewer is never charged twice for the same teacher.
  4. Run the three domain rules. A failure surfaces as a forbidden response carrying the rule's message.
  5. Decrement the viewer's quota.
  6. Record the unlock and return it.

An unlock record is a small fact: an id, the talent id, the viewer id, the viewer type (employer or recruiter), and a timestamp.

Persistence and race safety

Unlock records live in their own table, keyed uniquely by the (talent, viewer, viewer-type) triple. The write uses an upsert against that unique key, so two concurrent unlock requests for the same pairing converge on one record rather than creating duplicates. The repository also supports transferring a viewer's unlocks to another viewer — skipping any the target already holds before removing them from the source.

Notes and seams

  • The idempotency check and the quota decrement mean a repeat unlock of an already-revealed teacher is free; the quota is spent once, at first reveal.
  • The quota decrement and the unlock record are two separate persistence steps in the handler; the upsert protects the record itself from duplication, and the idempotency check guards against re-charging.
  • Standing (verification / approval) is decided upstream — the profile service only reads the viewer's current status when applying the rule.

Inside this component

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →