Building Relay
Xây dựng một công ty nền tảng chat thời gian thực từ một thư mục trống — bao gồm đặc tả, mã nguồn, triển khai và giám sát.
Phần 0 — Ý tưởng và trang giấy
0.1 · Từ ứng dụng đến hạ tầng — đi tìm sản phẩm đích thực
Bạn sẽ tạo ra: Một bản tuyên ngôn định vị; một danh sách non-goals
0.2 · Bốn người sẽ phán xét chúng ta
Bạn sẽ tạo ra: Một bộ chân dung người dùng, bao gồm cả người dùng cuối vô hình
0.3 · Hành trình — nơi những sản phẩm gục ngã
Bạn sẽ tạo ra: Bản đồ hành trình; những khoảnh khắc ★
0.4 · Những yêu cầu bạn có thể kiểm chứng
Bạn sẽ tạo ra: Một lát cắt SRS với đầy đủ ID, độ ưu tiên và phương pháp kiểm chứng
0.5 · Quyết định trên giấy trắng mực đen — bản SAD và thói quen viết ADR
Bạn sẽ tạo ra: Bảng động lực thiết kế (drivers); hai bản ADR viết từ con số không
Phần 1 — Đặt nền móng
1.1 · Monorepo và bộ công cụ
Bạn sẽ tạo ra: Một pnpm workspace chạy được — TypeScript, lint, và bộ test xanh
1.2 · Một câu lệnh, cả thế giới
Bạn sẽ tạo ra: Hạ tầng local một câu lệnh — bốn store, có healthcheck và đã kiểm chứng
1.3 · Package protocol
Bạn sẽ tạo ra: Bản giao kèo đường truyền dùng chung — kiểu frame, mã lỗi, và schema biết từ chối dữ liệu hỏng
1.4 · Bộ khung biết đi
Bạn sẽ tạo ra: Hai service bộ khung chạy được — có health check, request ID, log JSON có cấu trúc
Phần 2 — Vòng lặp cốt lõi
2.1 · Schema có xương sống
Bạn sẽ tạo ra: Schema đã migrate và tầng repository khóa theo tenant — rò rỉ giữa các tenant thành điều không thể viết ra
2.2 · Quá trình gửi tin nhắn
Bạn sẽ tạo ra: Gửi tin nhắn: channel row lock và sequence assignment (ADR-03)
2.3 · Gửi hai lần
Bạn sẽ tạo ra: Idempotency keys, unique index một phần (DR-03)
2.4 · Lịch sử biết phân trang
Bạn sẽ tạo ra: Cursor pagination trên (channel_id, seq)
2.5 · Socket
Bạn sẽ tạo ra: Gateway: WS termination, JWT verify, connection registry
2.6 · Hai server, một cuộc trò chuyện
Bạn sẽ tạo ra: Redis fan-out (ADR-07); lập luận về fabric có thể mất frame
2.7 · Đường hầm
Bạn sẽ tạo ra: Resume protocol: cursor, backfill, buffer subscribe-trước-backfill
2.8 · Cột mốc: bài kiểm tra Tuan
Bạn sẽ tạo ra: Suite e2e cho journey 4 — tiêu chí thoát Phase 1
Phần 3 — Vươn mình thành nền tảng
3.1 · Tenant từ trên xuống dưới
Bạn sẽ tạo ra: Org, app, environment; signup bằng OAuth; environment dev tạo tự động
3.2 · Key và token — hai loại credential, một lỗi thường gặp
Bạn sẽ tạo ra: API keys (prefix, hash, rotation); user JWTs; endpoint dev-token
3.3 · Lỗi có trang để xem
Bạn sẽ tạo ra: Một registry khai báo mọi error code nền tảng có thể gửi, một chỗ duy nhất dựng docs_url, và một hàm ném lỗi có kiểu để code gõ sai không compile được
3.4 · Cửa ải cô lập tenant
Bạn sẽ tạo ra: Một bộ kiểm thử cross-tenant tự suy ra danh sách mục tiêu từ router đang chạy, ba dạng tấn công so sánh một CẶP phản hồi thay vì một mã trạng thái, một kiểm tra cấu trúc rằng mọi bảng đều có đường về tenant, và tầng socket bị tấn công từ chính frame union của protocol
3.5 · Outbox
Bạn sẽ tạo ra: Transactional outbox + relay (ADR-06); crash-in-the-gap test
3.6 · JetStream và consumer đầu tiên
Bạn sẽ tạo ra: Cấu hình stream; subject grammar dùng chung; durable pull consumer tự dedupe
3.7 · Commit và publish là hai khoảnh khắc
Bạn sẽ tạo ra: Khép lại lỗi trùng lặp khi resume: một high-water mark sống lâu hơn buffer
3.8 · Các endpoint và các thiết bị đo
Bạn sẽ tạo ra: Hai endpoint công khai mà Part 3 cần, tính đẳng xâm do unique index bảo đảm chứ không phải do bộ nhớ, mọi lỗi validation lần đầu gọi tên field của nó, guard thao tác toàn cục canh chín bảng thay vì năm, và độ phủ nhánh của tầng repository được trả lời bằng một con số
3.9 · Kênh mà khách hàng kiểm soát
Bạn sẽ tạo ra: Một loại kênh private thực sự quyết định điều gì đó ở cả bốn cửa vào, xoá thành viên và phân quyền, lưu trữ kênh để từ chối gửi tin mà không tiết lộ kênh có tồn tại, và một gauntlet tấn công chính tenant của bạn
3.10 · Những gì một người dùng thấy
Bạn sẽ tạo ra: Danh sách channel phân trang bằng cursor và xếp theo hoạt động, số tin chưa đọc suy ra từ chính sequence mà đường ghi vẫn duy trì, profile người dùng được tạo ngầm ở lần xác thực đầu tiên, một người dùng đã xoá mà tin nhắn vẫn còn, và một lệnh ban có hiệu lực cả ở cửa vào lẫn trên đường gửi
3.11 · Người gửi mà một tin nhắn chưa từng có
Bạn sẽ tạo ra: Bot user mang theo description mà cơ sở dữ liệu bắt buộc, một người gửi trên mọi tin nhắn do compiler bảo đảm chứ không phải test, một credential nói với danh nghĩa phần mềm, không phải con người, từ chối mà không hé lộ ai tồn tại, và một bot vẫn tính tiền như active user nhưng miễn trần gửi
3.12 · Tin nhắn chưa từng tới
Bạn sẽ tạo ra: Một tin nhắn gửi qua REST tới được socket đang mở, một thứ tự tách theo transport vì response của request handler CHÍNH LÀ acknowledgement của nó, một publisher sống sót qua broker đã chết trong 2 ms, và một điều khoản P1 được đo là chưa thoả rồi ghi lại thay vì bị thu hẹp
3.13 · Presence, và ai được phép nhìn thấy
Bạn sẽ tạo ra: Một frame nằm trong protocol union từ chương 1.3 nay mới có bên phát, một subject grammar thứ hai giữ nguyên từng byte đường đi message, một key mà sự tồn tại chính là trạng thái, SET … NX chính là bầu chọn, một grace period mà bản vá đầu khiến người dùng kẹt online, và không dòng code lọc nào
3.14 · Membership đổi ngay dưới một socket đang sống
Bạn sẽ tạo ra: Một điều khoản chưa thoả từ chương 2.6, nay đóng bằng cách đảo ngược bài test đã bắt nó; một ngữ pháp subject thứ ba gửi tới principal thay vì channel, bởi một lần thêm thành viên không thể đi nhờ subject mà instance chưa subscribe; và một lệnh ban tới như một thay đổi, ra một frame mỗi channel
3.15 · Cái frame không ai được gửi
Bạn sẽ tạo ra: Frame đầu vào thứ hai đầu tiên sau hai mươi chương, đứng sau một tập hợp có tên được test ghim; một ngữ pháp subject thứ tư được nhận chứ không né, sau khi đếm lại con số ADR-19 và thấy bảy điểm định kiểu chứ không phải ba; và một phán quyết thành thật về điều khoản nền tảng không làm được
3.16 · Kết nối thứ sáu, và con số ấy sống ở đâu
Bạn sẽ tạo ra: FR-RTM-09 đóng lại ở cả hai nửa: một trần năm kết nối mà không instance gateway nào tự tính được, giữ thành năm khoá slot giành bằng `SET NX PX`; một mã đóng thứ sáu, lời từ chối duy nhất trong bộ mà xử lý đúng không phải thử lại; và một trần hỏng mở ồn ào, dòng log là bằng chứng duy nhất
3.17 · Những lời ai đó muốn lấy lại
Bạn sẽ tạo ra: FR-MSG-07, FR-MSG-08 và FR-MSG-10 được dựng, và hai kind cuối của FR-RTM-05 lần đầu có bên phát: một ngữ pháp subject thứ năm mang cả hai loại thay đổi với kind nằm trong payload, bởi một bia mộ không phải `Message` còn một lần sửa thì là; và hai mã lỗi thay cho 403 chung chung
3.18 · Tin nhắn không chỉ có chữ
Bạn sẽ tạo ra: Nửa external-URL của FR-MSG-11 được dựng: một tin nhắn mang attachment, giới hạn mười phần tử và 2.048 ký tự, bị từ chối trừ khi scheme là http hoặc https. Nửa media_id được hoãn tới §4.14 và bị từ chối đích danh, bằng một mã lỗi riêng.
3.19 · Webhook sống sót qua phía khách hàng
Bạn sẽ tạo ra: Một dispatcher service: ký HMAC, lịch retry theo thời điểm tới hạn, dead letter
3.20 · Khi nào thì thôi cố
Bạn sẽ tạo ra: Bản ghi lần thử trên stream analytics, và auto-disable từ hai trigger
3.21 · Email chẳng ai gửi
Bạn sẽ tạo ra: Mẫu outbox lần thứ ba, trên một cột mà chương "Khi nào thì thôi cố" đã viết sẵn — và Mailpit, vì chỉ thư đã nhận mới chứng minh được email không mang bí mật nào
3.22 · Những giới hạn bạn thấy trước
Bạn sẽ tạo ra: Bộ đếm request theo từng environment, các header trên mọi response, và hai bộ giới hạn hỏng theo hai hướng ngược nhau
3.23 · Quota và cái giá của nó
Bạn sẽ tạo ra: Quota theo tháng, hạn mức chi tiêu, và cách suy giảm chỉ chặn gửi mà không ảnh hưởng history
3.24 · Đếm một kết nối
Bạn sẽ tạo ra: Connection-minutes được đo từ một service không sở hữu bảng nào, một cú crash tính thiếu trong một giới hạn đã biết thay vì tính thừa mãi mãi, và close code 4008 lần đầu được gửi kể từ khi chương 1.3 khai báo nó
3.25 · Cột mốc: cửa ải cô lập tenant
Bạn sẽ tạo ra: Ba lần cố ý tái tạo lỗi — một lần vẫn xanh và dạy ta giới hạn của bộ kiểm thử
3.26 · Cột mốc: một người ngoài tích hợp
Bạn sẽ tạo ra: Một package tích hợp bị niêm phong về mặt cơ chế nên không thể import code trong workspace, và một phán quyết cho tiêu chí ra khỏi Phase 2 của SRS kèm những gì đã đo và những gì chỉ được giả định
Phần 4 — Mọi ngả đường dữ liệu đã đi
4.1 · Câu hỏi mà những bộ đếm không trả lời được
Bạn sẽ tạo ra: Một corpus một triệu tin nhắn, câu truy vấn phân tích lần đầu được viết cho Postgres, và bốn con số cho thấy index lẽ ra phải sửa được nó lại tốn thêm 49% dung lượng mà đổi lại chỉ là nhiễu
4.2 · Kho dữ liệu chưa từng lắng nghe
Bạn sẽ tạo ra: Một ClickHouse mà truy vấn chạm tới được, một schema phân tích với sổ ghi của riêng nó, và cùng một câu hỏi được trả lời trong 13,22 ms so với 585,9 ms của Postgres — cùng một ngày trong chín mươi mốt ngày không bao giờ đối soát được
4.3 · Consumer đã được hứa hẹn
Bạn sẽ tạo ra: Bộ nạp cho một stream đã đầy dần từ chương 3.20 mà chưa ai đọc — khử trùng lặp bằng chính khóa của bản ghi, bởi vì khuôn mẫu được xây để ngăn đúng vấn đề này lại ghi vào cơ sở dữ liệu mà đường dẫn này không được phép chạm tới
4.4 · The requests that belong to nobodyhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: An analytical record for every API request, and the measurement that FR-ANL-01's "every request" and FR-ANL-07's "per tenant" are not the same population — 34 of 54 requests resolve to no tenant, and the gap is widest exactly where the traffic is
4.5 · The gateway's first streamhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: FR-ANL-01's last arm without a producer, built in the one service that had never held a broker client — and the measurement that neither a clean stop nor a kill balances the opens against the closes, with the two connection counters wrong in opposite directions
4.6 · The rollup nobody readhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: The rollup DR-10 asks for, after finding that the one already shipped is read by nothing and sits over a table nothing writes — and the measurement that put FR-ANL-09's channel dimension and DR-10's cheap read into separate tables, at 525x the rows and a billing query that touched more of them than the raw events it replaced
4.7 · The job that checks the meterhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: FR-ANL-06's reconciliation job, and the finding that it cannot pass: two operational counters of the same quantity disagree by 0.2630% while 49 of 1,385 tenant-periods breach the bound, and three of the four quantities cannot meet 0.1% for reasons that are not defects — uniq's cliff at 65,536, a retention boundary that moves with the clock, and a connection ledger that counts a different population on each side
4.8 · The log a customer can searchhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: FR-ANL-07's query surface over the request log chapter 4.4 produced — tenant-scoped, paged on a composite cursor, filtered against the router's own route set — and the finding that the clause beside it cannot be built: FR-ANL-10 names a quantity this platform has never defined, over a column with 0 rows and 0 producers, and the function everyone would reach for reads the p99 at half its true value on the only real latency sample there is
4.9 · Milestone: the meter agreeshiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: An integration gate whose colour can change — 54 of 54 suites executing and reported, where a planted drift used to deepen a run that had been red since chapter 4.4 rather than turn it — and FR-ANL-06's agreement measured at 121,057 messages in one tenant-period, the first volume in this project where 0.1% is a threshold a whole number can sit either side of. Plus what the clause still lacks: no daily job runs anywhere, and the alert has no mechanism
4.10 · The upload that never reaches ushiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: An upload slot: one route that signs a fifteen-minute URL in twenty-eight lines of node:crypto, adding no dependency, so a client uploads a photo straight to object storage and the api's own request log shows the slot request and nothing else. Four refusals with four codes, only one of them transient. A storage quota enforced against a sum over the tenant's rows and serialised per tenant, with FR-RTL-05 amended to say why stored bytes are a level and not a monthly flow
4.11 · The half of the union that was refusedhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: The `{ type: "media" }` arm accepting, at all three doors that parse it, with one predicate inside the send's own transaction: the object belongs to this tenant, and for a user token it was uploaded by the sender or by the tenant itself. Three conditions get one answer, because naming which one failed would tell a caller whether somebody else's object exists. Plus the five forwarding readers that had to learn the new arm before the producer shipped — one of them drops a committed message with nothing but a log line — and the database's own refusal published as the evidence that FR-MED-06's `ready` state cannot be reached yet
4.12 · A link that expires, and who may hold ithiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: `GET /v1/media/:mediaId`, which answers a signed one-hour URL to a caller who may read a message referencing the object and the same 404 to everybody else. The authorisation rule is the platform's own `channelVisibleTo` rather than the membership check the clause's words describe — a literal reading would refuse a user the photo in a message whose text they can read. Plus the index that does not help the query it was built for, because the joined form the analysis passes produced cannot use it; the three tenancy predicates no single-mutation probe can see; and the caller-triggered 500 on sixteen shipped routes that this route is the first to escape
4.13 · The only service that reads the byteshiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: A fifth service that sweeps for unverified objects, streams each one through ClamAV, checks the declared size against the store's own count and the declared type against the bytes, and posts a verdict back over the internal seam. The event the architecture document says it consumes has no producer and cannot have one, because the client uploads straight to the store — so the sweep the specification rejected as wasteful turns out to cost 4.2 seconds for the whole backlog, and the clause it satisfies cannot be made contingent on a client choosing to send a notice. Plus ADR-14's delivery gate, which turns ten tests red if it ships a chapter early; a health check that reads the scanner's signature-database date, because a liveness probe passes against one thirteen days old; and three test fixtures that were true statements about the platform until it grew something that reads the bytes
4.14 · Pending, ready, rejectedhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: The state machine's other half: every door that serves a message now serves the state its media object is in right now, read when the message is served rather than stored on it, and a frame goes out to every channel referencing the object when a verdict lands. The clause is two sentences and neither was met — the event everybody quotes, and a first sentence nobody had noticed was also unmet, because one schema was serving both the door a sender writes to and the payload the platform builds. Plus the subject question a review left open two parts ago, answered by arithmetic somebody had already written down; the cheapest shape available, killed by a field that does not exist; and four tests that asserted the absence this chapter fills, under a comment saying it would last until movement VI
4.15 · What a thumbnail costshiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: A thumbnail for every uploaded image, and the answer to what one costs — which is not the number anybody reaches for first. Across a real corpus the ratio to the parent spans 775x while the thumbnail's own size spans 4x, so the ratio is a fact about the parent and publishing it as the cost publishes the wrong variable. Below the bound a thumbnail is 97.3% of the parent and the same pixels, which decides a behaviour rather than a figure. Plus the clause's last five words, which turn out to be the hard part: a derived object is named by no message, so one shipped clause refuses to serve it and another reaps it after a day, both working exactly as written — and nothing in the schema could say that two rows were related. The dependency that had to be argued for without borrowing the previous chapter's argument, the fourth round trip nothing had needed before, and a transaction the plan said was there
4.16 · Storage on the billhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: A tenant's stored bytes, metered daily and counted by kind, and the weekly comparison the clause asks for against what the object store actually holds. The comparison is the chapter: run against real data it says the meter charges 4,436 MB while the bucket holds 30.7 MB, and 99.9% of the difference is slots reserved and never uploaded to — a reservation measured against a delivery, which is FR-MED-01 working as designed rather than a drift. So the verdict has a third outcome, and the term that explains it cannot be computed from either store alone. Plus a column whose name reads as this chapter's subject and counts something else entirely, a materialised view that was counting zeros under a key it had no business creating, and a coverage number that was right about a service running in the wrong process
4.17 · Milestone: an image, end to endhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: One image carried from an upload slot to delivered bytes by the software a deployment runs, with the verdict made by a container the test did not start — the join seven chapters had each built a piece of and nothing had ever checked. Plus a second image the worker refuses, arriving as a marker a reader can tell apart from a deleted message in two fields rather than one. The chapter is also an argument about what a milestone is for: its own premise turned out to be partly wrong, which made it smaller rather than larger, and the thing it found on the way was a comment explaining why an assertion was stable that was false in the job that runs it. What the path costs, with its sample size: every step a client controls is under 20 ms and the one it waits on is three orders of magnitude larger, uniform over a five-second sweep — and one upload in six waits five or six passes for a reason that is neither the timer nor the scanner, published rather than smoothed away
4.18 · The log that cannot be editedhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: A table nothing in the application can change, and the decision that makes it worth having. FR-MOD-03 asks for an immutable audit log of every moderation action, and the clause names a population without saying who is in it — so the chapter's product is a classification of all 24 mutating routes a tenant can reach, each with a reason, checked in both directions against the routes a booted application reports. The set comes out at eight where a reader would predict nine, and the rule that produced it is wrong about two routes in the same direction, which is how the line it actually draws gets named: standing, not data. The word `immutable` costs a mechanism this schema had never used — the obvious one, revoking the privilege, does nothing at all against a superuser, and a unit test had forbidden the one that works for a reason that is the exact opposite of this case. Both ways around the trigger are measured and published beside the claim, because a security sentence with no attack against it is a comment
4.19 · Everything, including what was deletedhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: The text a message held at the moment somebody removed it — which this platform threw away, every time, for twenty-five chapters. FR-MOD-01 asks for a channel's complete history including tombstones and edit history, and both of those nouns were already built: the chapter opens by running the premise and finding four of the clause's five obligations met. The hole is at the join. An edit records the text it REPLACED and a deletion recorded nothing, so a message edited twice and then deleted gives back two of its three texts and a message deleted with no edits gives back none of its one, and nothing anywhere notices. You will build the row that closes it, make the table append-only because FR-MSG-07 has said `immutable` since chapter 3.23 and nothing enforced it, and publish the boundary as two numbers rather than one — because a tombstone that kept its earlier texts looks served and is missing the only text a dispute turns on. Two findings cost more than the feature did: a microsecond column that has only ever held milliseconds, and a required field that reached a strict schema one seam away
4.20 · The messages that expirehiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: A retention policy a customer sets, a sweep that enforces it, and the two sentences the platform refuses to write. FR-MOD-06 asks for configurable retention with expired messages hard-deleted by a scheduled job, and the premise check found NONE of its three obligations met — the inverse of the chapter before it. The column had existed since chapter 2.1 and was set on 0 of 33,051 environments; nothing read it; and the hard deletion the clause names was refused by this platform's own schema on 5,495 messages. You will meet that refusal as a pincer: the foreign key stops the parent, the append-only trigger the previous chapter built stops the children, and `ON DELETE CASCADE` is refused as well — because a cascade issues an ordinary DELETE and a row trigger fires on it, which is the measurement that decides the whole design. The only thing that works unchanged is the bypass the previous chapter published as the limit of its own guarantee. So the products are a narrow auditable exception and a new ADR, and the ADR's first decision is a reading rather than a mechanism: three documents reserve hard deletion for the compliance path and the constitution's own word is PATH where the SRS said ENDPOINT, so the rule hardest to change is the one that already permitted this. What the chapter cannot do is run the sweep: there is no scheduler, this is the fourth clause bounded by its absence, and it is the first one where the absence is a customer telling an auditor that data does not exist
4.21 · Erasure, and every path it must findhiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: An endpoint that erases a person from seven stores, and a receipt that names the one it cannot. FR-MOD-04 asks for permanent erasure of messages, memberships, profile and analytical records — and the chapter opens on a behaviour rather than an error: you delete a user the way the platform already lets you, and find the row, the messages and the billing rows still there, all of it correct. The hard part is the word MESSAGES. FR-USR-05 preserves them and FR-MOD-04 destroys them, both clauses are right, and the reading that resolves it is the one Slack and Microsoft Teams both take: a compliance erasure asks for removal of a person, not of a conversation. Everything else follows from that single decision. Keeping the messages means the author's row can never be deleted, because all five foreign keys refuse it — so erasure leaves a tombstone, and a tombstone is what makes every store that references the user BY KEY stop naming anybody without being touched. That is a new ADR, and it turns what was going to be the chapter's weakest limitation into its argument. You will also watch a tenant-scoped delete statement take 1,113 of 1,116 rows belonging to other tenants, because one value came out of a URL path and went into SQL unbound — and you will see the published payload for that attack fail to reproduce it, which is its own lesson about probes. The store that cannot comply is not an analytical one: it is the audit log, which holds the person's external id on every row naming them and is append-only on purpose. The erasure writes one more. The one place the name survives is the record that the name was erased
4.22 · The identifier the customer gave ithiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: Thirteen routes that take the name your customer already had for a channel, and the one query that makes them. FR-USR-01 says Relay shall not generate end-user identities and ADR-18 says an end user's identity is whatever external_id the customer already had — and yet every route beneath /v1/channels/:channelId wanted a uuid Relay minted, so a customer had to keep a lookup table mapping their order number to our key. The exact table the journey map promises they will not need. You open on the failure: create a channel called order-88412, ask for it back by that name, and get a 500. Not a 404 — an internal error, which tells a developer the platform is broken rather than that they used the wrong key. The cause is three layers down and it is a cast: 'order-88412'::uuid raises in Postgres before the OR beside it can short-circuit, which is simultaneously why the error happens and why one query cannot resolve both forms. So the fix and the repair are a single edit, and the chapter's first product is a reading rather than a mechanism. You will build the resolution as a pipe and find out why the cheaper design cannot work: middleware runs BEFORE guards, so it has no principal, and a resolution with no environment is a cross-tenant read. Then you will write the pipe wrong in the way that looks right — throwing the same 404 every handler throws — and watch the isolation gauntlet catch it, because a pipe answers before every check the handler makes, and a banned user could suddenly tell a real channel from an invented one. You will measure what the resolution costs rather than assert it is cheap, delete the tenancy scope to see which tests notice and find that the suite built for that question notices nothing, and sweep every response the API returns for an identifier no route accepts — which turns up one, on a write route everyone uses, that a published ADR said could not exist
4.23 · The channel a socket nameshiện chỉ có bản tiếng Anh
Bạn sẽ tạo ra: A real-time surface that calls a channel what your customer calls it, and a lesson in counting the right things. Chapter 4.22 made thirteen REST routes take the identifier the customer gave a channel; the gateway did not change and was never asked to, so one platform answered two ways and Journey 3's “zero lookup tables” held on the surface a support tool asks questions with and not on the surface it watches. You open on that: the same channel, named order-88412 over REST and 419320ca-… on the socket, one minute apart. Then you size the work and get it wrong twice. Seven schemas carry a channel field — and connection.ack names channels three more times WITHOUT the word, in two maps keyed by channel and a list of them, so a grep for a field cannot see a map keyed by that field's value. Twenty-one gateway expressions write a channel — and only three build a client frame; eleven are log lines an operator reads against the subject names, six are internal publishes, and the commonest frame on the socket is forwarded from the api and written by no gateway expression at all. So the rename goes at the single send, and that turns out to be a correctness choice rather than a tidy one: the resume buffer holds frames that two internal comparisons index by channel, so translating where a frame is BUILT re-sends a resuming client its whole backlog and flushes a revoked channel's messages anyway, both silently. You will build the map in both directions, find the hole where a channel joined mid-session cannot be named, discover that the sixty-second backstop cannot cover it, and then watch the lane catch a defect the reading missed — the backstop's own additions carried no identity and the new send dropped them. You will measure 19 of 44,574 identifiers that are another channel's key, which is why no shape test can separate the two forms in a resume cursor, and you will write three cross-tenant attacks by hand because a suite that derives its targets from the protocol is green by construction against a change in what a field carries
Chặng đường phía trước
- Phần 5Trải nghiệm lập trình viênsắp ra mắt
- Phần 6Ship sản phẩmsắp ra mắt
- Phần 7Vận hànhsắp ra mắt
- Phần 8Nhìn lại chặng đườngsắp ra mắt