هویت: کاربر مهمان و کاربر واردشده
چطور تاریخچهٔ کسی که هنوز وارد نشده به حسابش وصل میشود، و چه اتفاقی میافتد وقتی دو نفر روی یک دستگاه وارد میشوند.
بیشتر آدمها پیش از آنکه بدانید کی هستند، چند بار به شما سر میزنند. این صفحه میگوید سگمنتیک با آن مدت چه میکند: چه چیزی وصل میشود، چه چیزی وصل نمیشود، و کدام کار برگشت ندارد.
دو شناسه
anonymous_id | user_id | |
|---|---|---|
| از کجا میآید | SDK میسازدش | سیستم احراز هویت خودتان میدهدش |
| کجا نگه داشته میشود | حافظه محلی مرورگر یا فایل خصوصی اپ | همانجا، بعد از اولین identify |
| کی عوض میشود | فقط با reset | با identify بعدی، یا reset |
| پرونده میسازد | خیر | بله |
دستکم یکی از این دو باید روی هر پیام باشد، وگرنه رویداد با کد missing_identity رد میشود. هر دو حداکثر ۲۵۶ بایتاند و بلندتر از آن id_too_long میگیرد.
هر پیام با کلید هویت به صف رویدادها میرود: اگر user_id خالی نباشد همان، وگرنه anonymous_id. دلیلش ترتیب است: همه پیامهای یک آدم باید روی یک پارتیشن بنشینند تا مصرفکنندههای حالتدار، یعنی بهروزرسانی پرونده و وضعیت سناریو و دوختن نشست، آنها را بهترتیب ببینند بدون هیچ هماهنگی بین پارتیشنها.
نکتهای که کل بقیه این صفحه از آن درمیآید: پرونده فقط با user_id کلید میخورد. ingestor هر رویدادی را که user_id خالی دارد پیش از رسیدن به پروندهها رد میکند. یعنی بازدیدکننده مهمان اصلا پروندهای ندارد.
شناسه مهمان چطور ساخته و نگه داشته میشود
مرورگر. SDK وب کلید segmentic_anonymous_id را در localStorage میخواند و اگر نبود یکی میسازد. ساختش با crypto.randomUUID است، و اگر مرورگر آن را نداشته باشد با crypto.getRandomValues یک UUID نسخه چهار میسازد، و در آخرین حالت برای مرورگرهای خیلی قدیمی از Math.random استفاده میکند.
پیش از همه اینها، localStorage با یک نوشتن واقعی آزمایش میشود، نه با بررسی وجود داشتنش. در حالت ناشناس سافاری، در مرورگرهای سختگیر سازمانی، و وقتی سهمیه دامنه پر شده باشد، localStorage وجود دارد و در نوشتن خطا میدهد. اگر آزمایش شکست بخورد SDK به یک انبار درونحافظهای میافتد: سایت مشتری سالم میماند، ولی شناسه مهمان فقط تا پایان همان صفحه زنده است و هر بارگذاری بعدی یک آدم تازه است.
اندروید و iOS. همان کلید، ولی بهصورت یک فایل به همان نام داخل پوشه segmentic در حافظه خصوصی اپ. ساختش با UUID.randomUUID است. تا وقتی اپ پاک نشود میماند.
سه چیزی که شناسه مهمان نیست:
- شناسه دستگاه نیست. اپل یک شناسه نصب جدا به نام
segmentic_install_idدارد که عمدا نهidentifierForVendorاست (که با حذف آخرین اپ آن سازنده عوض میشود) و نه شناسه تبلیغاتی (که سوالی است که مشتری باید جوابش را بدهد، نه ما). - بین دو مرورگر یا دو دستگاه مشترک نیست. یک آدم روی گوشی و لپتاپ، دو شناسه مهمان دارد.
- بعد از
identifyعوض نمیشود. همان مقدار روی هر پیام بعدی هم مینشیند.
هر پیامی که SDK میسازد همیشه anonymous_id را حمل میکند، و user_id را وقتی که بداند.
identify چه میکند
سمت کلاینت، در هر سه SDK، به همین ترتیب:
- اگر شناسه کاربر خالی باشد، فراخوانی نادیده گرفته میشود و یک هشدار در کنسول میآید.
- شناسه کاربر ذخیره میشود.
- اگر شناسه کاربر با آنچه ذخیره بود فرق داشته باشد و شناسه مهمانی وجود داشته باشد، اول یک پیام
aliasبه صف میرود، باprevious_idبرابر شناسه مهمان فعلی. - بعد پیام
identifyبا ویژگیها به صف میرود. - در وب و اندروید، ویژگیهای ساده بهصورت محلی کش میشوند تا هدفگیری پیام درونبرنامهای بتواند رویشان شرط بگذارد. مرورگر فقط آنچه را دارد که به آن داده شده، نه انبار داده؛ اگر وانمود میکردیم که دارد، هر قاعده ویژگی روی هر کمپین بیصدا نادرست میشد. SDK iOS این کش را ندارد.
anonymous_id عوض نمیشود. هر دو پیام alias و identify همان شناسه مهمان و شناسه کاربر جدید را با هم دارند.
identify را دو بار با یک شناسه صدا بزنید، دقیقا یک alias ساخته میشود. مقایسه با مقدار ذخیرهشده انجام میشود، نه با حافظه همان اجرا، پس بارگذاری دوباره صفحه هم یک alias تکراری نمیسازد.
سمت سرور، identify یک رویداد معمولی به نام identify میشود. ویژگیهای آن روی جدول رویدادها ذخیره نمیشوند؛ فقط به پرونده کاربر میروند. پرونده دو قاعده دارد: هرگز پاک نکن (رویدادی که ویژگیای را نام نبرده باید آن را دستنخورده بگذارد) و هرگز عقب نرو (رویدادها بعد از راهاندازی دوباره مصرفکننده بیترتیب پخش میشوند، پس رویداد قدیمیتر نباید حالت جدیدتر را بازنویسی کند).
import { init, identify } from "@segmentic/web";
init({
writeKey: "wk_seg_...",
apiHost: "https://in.segmentic.net",
});
// After your own sign-in succeeds, and again on every page load
// while the session is still valid.
identify("u_88123", {
phone: "09123456789",
first_name: "حمید",
loyalty_tier: "gold",
});
Segmentic.identify(
userId = "u_88123",
traits = mapOf(
"phone" to "09123456789",
"first_name" to "حمید",
"loyalty_tier" to "gold",
),
)
alias و previous_id
identify خودش alias را میفرستد، پس معمولا لازم نیست خودتان صدایش بزنید. اگر صدا زدید، previous_id اجباری است و بدون آن پیام با کد missing_previous_id رد میشود.
یک نامتقارنی که در جدول حدها پیدا نمیکنید: برخلاف user_id و anonymous_id که در ۲۵۶ بایت رد میشوند، روی previous_id هیچ بررسی طولی نیست. هرچه بفرستید سالم تا identity_map میرود و تنها سقفش همان ۵ مگابایت بدنه است.
آنچه یک پیام alias سمت سرور تولید میکند، دقیقا این است:
CREATE TABLE segmentic.identity_map (
tenant_id UInt32,
anonymous_id String,
user_id String,
linked_at DateTime64(3, 'UTC')
) ENGINE = ReplacingMergeTree(linked_at)
PARTITION BY tenant_id
ORDER BY (tenant_id, anonymous_id);
یک سطر. بهعلاوه خود پیام alias هم مثل هر پیام دیگری بهعنوان رویدادی به نام alias در جدول رویدادها مینشیند.
توجه کنید که کلید مرتبسازی anonymous_id است، نه user_id. تمام بخش دو نفر روی یک دستگاه از همین یک جمله درمیآید.
alias دو user_id را به هم وصل نمیکند. هر مقداری که در previous_id بگذارید در ستون سمت مهمان مینشیند، و هیچچیزی دو پرونده را ادغام نمیکند. هیچ عملیاتی برای ادغام دو پرونده وجود ندارد.
این پیوند به چه کار میآید
identity_map سه مصرفکننده در کل کد دارد: نوشتن سطر، مسیر پاکسازی داده یک شخص، و تست یکپارچگی خودش. همین.
کاری که واقعا با آن انجام میشود پاکسازی است. وقتی کسی میخواهد فراموش شود، ردیفهای رویدادش با user_id پاک میشوند، ولی رویدادهای پیش از ورودش user_id خالی دارند و آن پاس اصلا آنها را نمیبیند. تنها راه رسیدن به آنها، شناسههای مهمانی است که در identity_map نام برده شدهاند:
ALTER TABLE segmentic.events DELETE
WHERE tenant_id = ? AND user_id = '' AND anonymous_id IN (
SELECT anonymous_id FROM segmentic.identity_map
WHERE tenant_id = ? AND user_id = ?
) SETTINGS mutations_sync = 2;
و بعد از آن، نه پیش از آن، خود identity_map پاک میشود. ترتیب کل ماجراست: نسخه اول این کد نقشه را اول پاک میکرد، پس زیرپرسوجو به هیچچیز نمیخورد، حذف چیزی برنمیداشت و موفقیت گزارش میکرد. تاریخچه گشتزنی آن آدم پیش از ورود، که بخش بزرگی از معنی «مرا فراموش کن» است، بیصدا از هر پاکسازی جان سالم به در میبرد. یک تست یکپارچگی این را گرفت.
آنچه اتفاق نمیافتد
این مهمترین بخش این صفحه است و بهعمد صریح نوشته شده، چون اگر خلافش را باور کنید یک بعدازظهر را روی عددی میگذارید که هرگز درست نمیشود.
سطرهای رویداد مهمان هرگز بازنویسی نمیشوند. هیچ کدی events.user_id را برای سطرهایی که شناسه مهمانشان در نقشه هویت آمده بهروز نمیکند. آن سطرها برای همیشه user_id خالی دارند.
تابع ادغام پرونده مهمان با پرونده شناختهشده در کد هست و هیچجا صدا زده نمیشود. جستوجوی کل مخزن برای ApplyAlias تعریفش را در profile/merge.go پیدا میکند و فراخوانیهایی که همهشان در merge_test.go هستند، و هیچچیز دیگر.
بازدیدکننده مهمان اصلا پروندهای ندارد. ingestor هر رویداد با user_id خالی را رد میکند، پس پرونده مهمانی وجود ندارد که ادغام شود. یعنی total_events و total_revenue و first_seen آن آدم از لحظه ورودش شروع میشود.
هیچ پرسوجوی تحلیلی به identity_map جوین نمیزند. کامپایلر سگمنت، گزارش قیف و ماندگاری و مسیر، و تایملاین کاربر، همه مستقیم events.user_id را میخوانند.
سطرهای مهمان از هر دو گزارش تجمیعی روزانه بیروناند، با شرط user_id != ''.
نتیجه عملی، دقیق: قیفی که با یک product_viewed مهمان شروع شود و با یک order_completed واردشده تمام شود، این دو را به هم وصل نمیکند، چون زیرپرسوجوی رویداد در کامپایلر روی user_id گروه میشود و مقدار آن سطر مهمان خالی است.
هرجا گفته شده «تاریخچه ناشناس به کاربر وصل میشود»، معنی دقیقش این است: سطرها نگه داشته میشوند و پیوند ثبت میشود. معنیاش این نیست که گزارشها آن سطرها را به آن کاربر نسبت میدهند. قیف پیش از ورود دوخته نمیشود.
پس چه کنید: هرچه زودتر که مشروع است identify را صدا بزنید. اگر کاربر از نشست قبلی هنوز وارد است، در همان ابتدای بارگذاری صفحه یا راهاندازی اپ و پیش از هر رویداد دیگری identify را صدا بزنید، تا رویدادهای آن نشست از همان پیام اول user_id داشته باشند. رویدادی که با شناسه کاربر فرستاده شود، درست نسبت داده میشود؛ آنچه دوخته نمیشود فقط چیزی است که پیش از آن آمده.
دو دستگاه، یک آدم
دستگاه اول anon_A را میسازد و دستگاه دوم anon_B. هر دو identify("u_1") را صدا میزنند.
- دو پیام alias فرستاده میشود، یکی از هر دستگاه.
- دو سطر در نقشه هویت مینشیند، با کلیدهای
anon_Aوanon_Bو هر دو با مقدارu_1. هر دو میمانند: موتور ReplacingMergeTree فقط سطرهایی را جمع میکند که شناسه مهمانشان یکی باشد. - همه رویدادهایی که
user_idبرابرu_1دارند، از هر دو دستگاه، در یک سطر پرونده جمع میشوند. شمارندهها روی هر دو دستگاه جمع میشوند. - حقایق دستگاه، یعنی
device_typeوos_nameوapp_versionوpush_providerوcityوtimezoneوlanguage، آخرین نشست را توصیف میکنند نه اجتماع دو دستگاه را. فقط وقتی بهروز میشوند که زمان رویداد از آخرین دیدهشدن عقبتر نباشد، و مقدار خالی روی مقدار موجود نمینشیند. رویدادی که از یک نخ پسزمینه بدون زمینه دستگاه فرستاده شود، نباید کاربری را که قابل دسترسی بود از دسترس خارج کند: آن آدم از هر کمپین پوشی میافتاد بیرون و هیچ لاگی دلیلش را نمیگفت. - رویدادهای پیش از ورود هر دو دستگاه، بدون نسبت میمانند.
- پاکسازی این حالت را درست انجام میدهد: رویدادهای مهمان همه شناسههایی را که نقشه به آن کاربر نسبت داده، پاک میکند.
دو نفر، یک دستگاه
دستگاه anon_X را میسازد. نفر اول identify("u_A") را صدا میزند. بعد، بدون اینکه reset صدا زده شود، نفر دوم identify("u_B") را صدا میزند.
- شناسه کاربر ذخیرهشده از
u_Aبهu_Bعوض میشود، پس شرط «فرق کرده» درست است و alias دومی به صف میرود. anonymous_idعوض نشده است، چون فقطresetشناسه مهمان تازه میسازد. پس alias دوم دوبارهprevious_idبرابرanon_Xدارد.- نقشه هویت روی
(tenant_id, anonymous_id)مرتب است، پس سطر دوم جای سطر اول را میگیرد. بعد از ادغام،anon_Xبهu_Bاشاره میکند و این واقعیت که زمانی بهu_Aتعلق داشت، از بین رفته است.
نتیجهاش روی پاکسازی: اگر بعدا u_A بخواهد فراموش شود، جستوجو دیگر anon_X را پیدا نمیکند، پس رویدادهای پیش از ورود u_A پاک نمیشوند. این یک شکاف واقعی است، برگشت ندارد، و هیچ تستی آن را پوشش نمیدهد. تنها چیزی که جلویش را میگیرد، صدا زدن reset هنگام خروج است.
آنچه سالم میماند: u_A و u_B دو پرونده جدا هستند و هرکدام شمارندههای خودش را نگه میدارد. رویدادهای واردشده هر دو هم درست نسبت داده میشوند، چون هر رویداد شناسه کاربری را حمل میکند که در لحظه به صف رفتن جاری بوده است.
reset
هنگام خروج صدایش بزنید. هر بار.
آنچه انجام میدهد:
- شناسه کاربر پاک میشود، هم از حافظه و هم از انبار.
- یک شناسه مهمان تازه ساخته و ذخیره میشود.
- کلید نشست پاک میشود.
- فقط در وب: انتساب کمپین ذخیرهشده پاک میشود. روی یک کامپیوتر مشترک، خرید نفر بعدی نباید به حساب پیامی نوشته شود که نفر قبلی گرفته بود. اندروید بازپخش کمپین سمت کلاینت ندارد، پس چیزی برای پاککردن ندارد.
آنچه انجام نمیدهد، در هر سه SDK:
- صف را خالی نمیکند و چیزی نمیفرستد. پیامهای بافرشده هنوز با شناسه کاربر همان لحظهای که ساخته شدند بیرون میروند، که همان رفتار درست است.
- ویژگیهای کششده محلی را پاک نمیکند. یعنی تا اولین
identifyبعدی، قاعدههای هدفگیری پیام درونبرنامهای هنوز روی ویژگیهای نفر قبلی شرط میگذارند. - پرچم «قبلا اینجا بوده» را پاک نمیکند، عمدا. خروج از حساب کسی را کاربر تازه نمیکند، و کمپین «اولین اجرا» که بعد از هر خروج دوباره ظاهر شود، باگی است که مشتری آن را از زبان کاربرانش میشنود.
- هیچچیزی به سرور نمیفرستد. در سمت سرور چیزی به نام reset وجود ندارد: فقط پنج نوع پیام پذیرفته میشود و هیچ عملیات جدا کردن یا لغو پیوند وجود ندارد.
reset کنترل رضایت نیست. برای توقف جمعآوری optOut هست، که جمعآوری را متوقف میکند و بافر را خالی میکند، چون احترامگذاشتن به انصراف فقط برای رویدادهای آینده، در حالی که آنچه قبلا ضبط شده بیسروصدا تحویل داده میشود، انصراف نیست.
user_hash و اینکه به چه درد میخورد
کلید نوشتن عمومی است. داخل صفحه خود مشتری قرار میگیرد و هرکسی میتواند از سورس صفحه بیرونش بکشد. برای نوشتن این پذیرفتنی است: بدترین کاری که یک غریبه با آن میتواند بکند، اضافهکردن نویز به داده خود مشتری است، که هم دیده میشود و هم قابل ترمیم است.
صندوق پیام درونبرنامهای اولین خواندن است. سطرهایش متن پیام و کد تخفیف شخصیسازیشدهای را دارند که برای یک مشتری مشخص ساخته شده است، و «صندوق کاربر ۹۱۳۷۲ را به من بده» پشت کلیدی که همه میتوانند بخوانند، endpointای است که نمیتواند وجود داشته باشد.
پس دو بررسی جدا انجام میشود، چون به دو سؤال جواب میدهند. کلید نوشتن میگوید داده کدام تنانت در میان است و درباره اینکه چه کسی میپرسد چیزی نمیگوید. هش میگوید بکاند خود مشتری این آدم را احراز هویت کرده است. فقط دومی بین «پیامهای من را نشان بده» و «پیامهای همه را نشان بده» ایستاده است.
فرمول، دقیقا:
user_hash = hex(hmac_sha256(identity_secret, user_id))
identity_secret مال تنانت شماست و هرگز نباید به مرورگر برسد. بکاند شما هنگام ورود هش را حساب میکند و به SDK میدهد، و SDK آن را با هر درخواست صندوق پیام میفرستد.
گرفتنش خودسرویس نیست و این را باید در برنامهریزی حساب کنید: هیچ مسیر HTTPای این رمز را نمینویسد و هیچ صفحهای در پنل برایش وجود ندارد. تنها چیزی که آن را روی تنانت میگذارد ابزار خطفرمان adminctl است، که یعنی یک اپراتور سگمنتیک. تا وقتی آن تماس انجام نشده، صندوق پیام درونبرنامهای برای شما فقط ۴۰۳ برمیگرداند.
import { createHmac } from "node:crypto";
export function userHashFor(userId) {
return createHmac("sha256", process.env.SEGMENTIC_IDENTITY_SECRET)
.update(userId)
.digest("hex");
}
curl -X POST https://in.segmentic.net/v1/inbox \
-H "Content-Type: application/json" \
-H "Authorization: Bearer wk_seg_..." \
-d '{
"user_id": "u_88123",
"user_hash": "3f6c1d0a9b8e4725c0d1e2f3a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7",
"limit": 20
}'
{"status":"ok","messages":[]}
{"status":"error","message":"user identity is not verified"}
چند رفتار دقیق که باید بدانید:
- فقط دو endpoint این هش را میخواهند:
POST /v1/inboxوPOST /v1/inbox/ack. بقیه مسیرهای کلکتور نوشتناند و کلید نوشتن برایشان کافی است. - بسته میماند اگر تنظیم نشده باشد، ولی نه به آن شکلی که انتظار دارید. ثبت این دو مسیر تصمیم کل نصب است نه تصمیم تنانت: روی هر نصبی که صندوق پیام پیکربندی شده باشد، هر دو مسیر برای همه وجود دارند. آنچه یک تنانت بدون رمز هویت میگیرد، ۴۰۳ روی هر درخواست است. حالت «تاییدنشده» وجود ندارد.
- اگر
user_idدر بدنه نباشد، پیش از هر بررسی هویتی کد ۴۰۰ با پیامuser_id is requiredبرمیگردد. - مقایسه زمانثابت است. این endpoint رو به دنیاست و مقایسه بایتبهبایت مقدار درست را برای آدم صبور، حرفبهحرف لو میدهد.
- هش با حروف بزرگ هم پذیرفته میشود؛ پیش از مقایسه فاصلههایش بریده و کوچک میشود.
- هش غلط و هش نیامده، هر دو کد ۴۰۳ با همان یک بدنه میگیرند. جدا کردنشان این را به ابزاری تبدیل میکرد که بشود با آن فهمید کدام شناسههای کاربر وجود دارند.
- چرخاندن رمز، هر هشی را که بکاند شما قبلا داده باطل میکند، یعنی کل اپ شما تا زمان استقرار بعدی از صندوق پیامش بیرون میافتد. کاری نیست که تصادفی انجام شود.
هویت وقتی از سرور خودتان میفرستید
از بکاند خودتان، رویدادها به POST /v1/events روی api.segmentic.net میروند، با یک کلید API که دسترسی profile.write دارد. پاسخ موفق کد ۲۰۲ است، نه ۲۰۰: رویدادها در صف نشستهاند نه ذخیرهشده، و چند ثانیه بعد قابل پرسوجو میشوند. اگر ۲۰۰ میگفتیم، فراخواننده وسوسه میشد بلافاصله آنها را بخواند و نتیجه بگیرد که گم شدهاند.
چرا profile.write و نه یک دسترسی جدید: کاری که این میکند همین است، روی پرونده آدمها و تاریخچه رویدادشان مینویسد، و اختراع اسم دوم برای یک توانایی، به کسی اجازه میداد یکی را بدهد و باور کند دیگری را نگه داشته است.
curl -X POST https://api.segmentic.net/v1/events \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_seg_..." \
-d '{
"events": [
{
"type": "track",
"user_id": "u_88123",
"event": "order_completed",
"timestamp": "2026-08-07T10:02:41.000Z",
"properties": { "order_id": "A-100294", "revenue": 2450000, "currency": "IRR" }
}
]
}'
{"accepted":1}
سه چیز که این مسیر با هویت فرق دارد:
- شما تقریبا همیشه
user_idرا دارید، پس همان را بفرستید و اصلا سراغanonymous_idنروید. - این مسیر بدون نشانی آیپی و بدون User-Agent نرمال میشود، چون تماس سرور به سرور است و نسبتدادن شهر گیرنده از روی نشانی دیتاسنتر شما، همه کاربرانتان را در یک نقطه مینشاند. یعنی روی این مسیر خبری از موقعیت جغرافیایی، مرورگر، پرچم ربات و تشخیص دستگاه نیست.
- سرور شما شناسه مهمان مرورگر را نمیداند و نمیتواند بداند. اگر میخواهید نشست مهمان و رویدادهای سرور به هم برسند، alias باید از سمت مرورگر برود، یعنی از
identifyکه SDK صدا میزند.
و سه چیز که هویت نیستند ولی اگر ندانیدشان همینجا گیر میکنید، چون هر سه بیصدا هستند:
- این مسیر حذف تکراری ندارد.
message_idاینجا از شما محافظت نمیکند. همان دسته را دو بار بفرستید و دو بار نوشته میشود. حذف تکراری ۴۸ ساعته کار کلکتور است و این در، از کنارش رد میشود. - هشدارها حساب میشوند و دور ریخته میشوند. بدنه پاسخ فقط
acceptedو در صورت لزومrejectedدارد. پسinvalid_phoneیاgenerated_message_idرا از این مسیر هرگز نمیبینید، حتی وقتی رخ دادهاند. - پنجره زمانی اینجا ثابت ۳۰ روز است، نه سیاست نگهداری تنانت. هر
timestampقدیمیتر بیصدا به لبه پنجره چسبانده میشود و پاسخ همان ۲۰۲ موفق است. برای همین است که مهاجرت تاریخچه از این در انجام نمیشود.
قاعدههایی که رعایتشان بعدا وقت شما را میخرد
| قاعده | چه چیزی را جلوگیری میکند |
|---|---|
identify را در همان اولین لحظهای که میدانید کاربر کیست صدا بزنید، و در هر بارگذاری صفحه یا راهاندازی اپ برای کاربری که هنوز وارد است | تاریخچه پیش از ورود دوخته نمیشود، پس هرچه دیرتر صدا بزنید، بیشتر از دادهتان بیصاحب میماند |
reset را هنگام خروج صدا بزنید، بدون استثنا | نفر بعدی روی همان دستگاه شناسه مهمان نفر قبلی را به ارث میبرد، و پیوند نفر قبلی برای همیشه پاک میشود |
برای user_id کلید اصلی خودتان را بگذارید، نه ایمیل و نه شماره تلفن | user_id کلید اصلی پرونده است و هیچ عملیات تغییر نامی وجود ندارد. آدمی که ایمیلش را عوض کند، پرونده دوم میگیرد |
| هرگز مقداری بگذارید که در هر نشست عوض شود | به ازای هر نشست یک پرونده ساخته میشود و شمارش کاربران بیمعنی میشود |
پیش از اینکه دو سیستم شروع به نوشتن کنند، سر یک قالب user_id توافق کنید | هیچ عملیاتی برای ادغام دو پرونده وجود ندارد. alias هم این کار را نمیکند |
ایمیل و تلفن را بهعنوان ویژگی پرونده بفرستید، نه بهعنوان user_id | ویژگی جای درست آنهاست و آنجا نرمالسازی میشوند؛ user_id در خروجیها و در نشانی صفحه تایملاین ظاهر میشود |
قدم بعد: تعریف رویداد میگوید چه چیزی بفرستید و با چه نامی، و فرهنگنامه رویدادها فهرست ویژگیهای پرونده را دارد که با identify میفرستید.