今日已更新 313 条资讯 | 累计 38170 条内容
关于我们

标签:#r

找到 32524 篇相关文章

AI 资讯

What We Learned Rewriting an Interactive Map Editor: Fabric.js, CORS, and 20,000 Lines of Legacy TypeScript

A story about how migrating an interactive office map editor turned into an engineering investigation involving Fabric.js, tainted canvas , and an architecture that's finally easy to extend. In most software projects, one sentence usually makes every developer nervous: "Let's rewrite this module from scratch." It often means months of development, regression risks, and endless architecture discussions. Our project was no different. We develop, a workspace management platform that allows companies to manage office spaces and book desks. One of its core features is an interactive office map editor, where administrators upload floor plans, place desks and meeting rooms, and publish maps for employees. Over the years, this editor slowly evolved into a real monolith. And the problem wasn't simply the number of lines of code. Where It All Started The editor dated back to the AngularJS era. The main component had gradually grown into a single file responsible for almost everything: loading maps working with Fabric.js CRUD operations keyboard shortcuts dialogs saving event handling The main editor component alone contained nearly 2,270 lines of code . Behind it lived another codebase — the map engine itself. Almost 20,000 lines of TypeScript spread across more than 230 files. One of the biggest architectural issues was an infinite rendering loop. fabric . util . requestAnimFrame (() => this . tick ()); Even when the user wasn't interacting with the editor, rendering continued forever. It worked. But every new feature became more expensive to build. Why We Decided to Rewrite It The motivation wasn't AngularJS itself. The real reason was business requirements. The product needed completely new capabilities: map drafts safe publishing high-quality printing multiple workspace modes easier support for new object types Every new feature pushed harder against the existing architecture. Eventually it became obvious: We weren't fighting individual bugs anymore. We were fighting the

2026-07-08 原文 →
AI 资讯

Logging — Request Logging

Request logging: vì sao mỗi log line phải có request id, và structured log cứu debug thế nào khi có sự cố production Request logging không phải là in console.log('got a request') cho vui. Nó là dấu vết duy nhất còn lại khi một request đã đi qua service, đi qua vài downstream, gặp lỗi, và bị đóng socket. Nếu log không có gì để nối lại các dòng thuộc cùng một request — không có requestId , không có traceId — thì log của một service RPS trung bình biến thành một mớ text xen kẽ giữa hàng chục request đồng thời, và câu hỏi "request X đã đi tới đâu, fail ở service nào" trở thành không trả lời được. Structured log (mỗi dòng là một JSON object với field cố định) + một correlation id truyền qua toàn bộ pipeline chính là cái làm log có thể query được, thay vì grep mù. Hai framework Express và Fastify tiếp cận khác nhau: Fastify tích hợp pino sẵn và tự sinh req.id , Express phải tự dán qua pino-http hoặc middleware tay. Nhưng cả hai đều vỡ theo cùng một kiểu khi correlation id bị đứt giữa các service. Cơ chế hoạt động Ba miếng ghép: (1) một structured logger — thường là pino trong ecosystem Node vì nó ghi NDJSON và có async destination, (2) một cơ chế sinh/nhận requestId , (3) một cách propagate id đó qua async work và qua HTTP call sang service khác. pino là JSON logger tối giản: mỗi lời gọi logger.info({...}, 'message') xuất một dòng NDJSON ra một WritableStream (mặc định là stdout). Log level là số ( trace=10, debug=20, info=30, warn=40, error=50, fatal=60 , theo pino docs), và có child logger — logger.child({ reqId }) tạo một logger mới bind sẵn các field, mọi log line từ child đều có reqId mà không phải truyền tay. Với Fastify , chỉ cần logger: true là có pino và request logging tự động — Fastify sinh request.id (mặc định là monotonic counter, đổi được qua option genReqId ) và log một cặp dòng "incoming request" / "request completed" cho mỗi request. Trong handler, request.log là child logger đã bind reqId : import Fastify from ' fastify ' import crypto from ' node:crypto

2026-07-08 原文 →
AI 资讯

Async Error Handling — Async Route

Async route: vì sao Promise reject trong Express 4 không tới error middleware, và cách Fastify tự bắt lỗi Một async route handler trông giống một handler bình thường, nhưng cơ chế truyền lỗi của nó khác hẳn. Express 4 bọc lời gọi handler bằng try/catch đồng bộ; một hàm async return về một Promise trước khi Promise đó settle, nên try/catch đồng bộ không bắt được rejection. Kết quả: lỗi rơi ra khỏi handler dưới dạng unhandledRejection , không tới error middleware, không được gửi thành 500 JSON , và từ Node 15 mặc định làm process crash. Fastify đi hướng khác — pipeline hook tự await handler, rejection được framework bắt và chuyển tới setErrorHandler . Hiểu điểm khác biệt này giải thích vì sao cùng một dòng code await db.query(...) lại có hai hệ quả production hoàn toàn khác nhau giữa hai framework. Cơ chế hoạt động Trong Express 4, Layer.handle_request gọi handler đồng bộ trong một khối try/catch . Nếu handler ném exception đồng bộ, catch được, forward qua next(err) . Nhưng handler async không ném — nó return về một Promise. Đến khi Promise reject, control đã ra khỏi khối try/catch từ lâu: // Đây là bản giản lược của cách Express 4 gọi handler: try { const ret = handler ( req , res , next ) // với async handler, ret là Promise (đã pending) // handler đã "return" — try/catch không còn hiệu lực với reject xảy ra sau } catch ( err ) { next ( err ) // chỉ chạy khi handler ném đồng bộ } Vì Express 4 không await ret và cũng không ret.catch(next) , một Promise reject không có bên nào bắt. Node emit unhandledRejection ở tầng process. Cách sửa chuẩn là wrapper: // asyncHandler: bọc handler async, forward reject vào next(err) export const asyncHandler = ( fn ) => ( req , res , next ) => Promise . resolve ( fn ( req , res , next )). catch ( next ) app . get ( ' /orders/:id ' , asyncHandler ( async ( req , res ) => { const order = await db . orders . findById ( req . params . id ) if ( ! order ) { res . status ( 404 ). json ({ error : ' not_found ' }); return } res . json ( order

2026-07-08 原文 →
AI 资讯

HTTP Server — Request Lifecycle

Request lifecycle: một HTTP request đi qua đâu, và vì sao "quên gọi next()" làm request treo cho tới khi socket timeout Một request tới một Express hay Fastify server không phải là "gọi handler rồi trả về response". Nó là một chuỗi các bước theo thứ tự cố định: parse HTTP, chạy qua middleware/hook, match route, gọi handler, serialize, gửi response, đóng. Nếu bất kỳ bước nào không chuyển tiếp — Express không gọi next() , Fastify hook không reply.send() hay không return — request treo cho tới khi client hoặc server timeout đóng socket. Đây là loại lỗi không ném exception, không xuất hiện trong error log, chỉ hiện ra qua p99 latency phình lên và số socket ở trạng thái ESTABLISHED tăng dần. Hiểu chính xác lifecycle này là điều kiện tiên quyết để debug những request "biến mất" và để đặt middleware đúng thứ tự. Cơ chế hoạt động Bước dưới cùng giống nhau ở cả hai framework: Node core http.Server nhận TCP connection, parse HTTP request line + headers, phát event request với hai object IncomingMessage (req) và ServerResponse (res). Điểm khác nhau là những gì framework làm giữa lúc nhận request và lúc response ra khỏi socket. Express dựng một chuỗi middleware qua Router . Mỗi lần app.use(fn) hay app.get(path, fn) được gọi, Express bọc fn vào một Layer với path regex (thông qua path-to-regexp ) rồi push vào một stack. Khi request đến, Router.handle duyệt stack tuần tự: với mỗi layer, nếu path match, gọi fn(req, res, next) . next() là closure trỏ vào layer kế tiếp; chỉ khi nó được gọi thì layer sau mới chạy. Error handler được nhận diện bằng arity — hàm 4 tham số (err, req, res, next) — và chỉ được duyệt tới khi next(err) được gọi: import express from ' express ' const app = express () app . use ( express . json ({ limit : ' 1mb ' })) // 1. parse body app . use (( req , _res , next ) => { // 2. request id req . id = crypto . randomUUID () next () }) app . use (( req , _res , next ) => { // 3. auth const token = req . headers . authorization if ( ! token ) return next ( new Erro

2026-07-08 原文 →
AI 资讯

End-to-end Design Walkthrough — Full System Design

Full system design walkthrough: vì sao quy trình 5 bước quan trọng hơn thuật ngữ, và cái bẫy nhảy thẳng vào deep-dive Một buổi phỏng vấn system design 45–60 phút không đánh giá xem thí sinh biết bao nhiêu tên công nghệ, mà đánh giá xem thí sinh có xử lý được một bài toán mơ hồ theo một quy trình có kỷ luật hay không. Cùng đề "thiết kế URL shortener", người thất bại thường bắt đầu bằng "dùng Cassandra vì scale tốt" ngay ở phút thứ hai — chưa hỏi scale bao nhiêu, chưa biết read/write ratio, chưa đồng ý interface. Người pass đi theo một trình tự cứng: requirements → estimation → high-level → deep-dive → tradeoff . Cùng một đề, cùng thời lượng, nhưng người thứ hai dẫn dắt được interviewer thay vì bị dí. Bài này mô tả quy trình đó ở mức thực dụng, các thất bại thường gặp khi bỏ bước, và một hands-on end-to-end trên URL shortener để tự luyện. Cơ chế hoạt động Quy trình chuẩn được nhiều tài liệu tổng hợp (Alex Xu — System Design Interview vol.1; Donne Martin — system-design-primer ; Kleppmann — Designing Data-Intensive Applications ) đều có 4–5 bước gần như trùng nhau. Đây là khung 5 bước dùng thực tế trong phỏng vấn: Requirements clarification (~5 phút). Chốt functional requirement (hệ thống làm gì — API nào, use case nào) và non-functional requirement (availability target, latency target, consistency yêu cầu, security, cost). Không đoán — hỏi lại interviewer. Output: 3–5 gạch đầu dòng functional + 3–5 gạch NFR + danh sách "out of scope" (analytics, billing, ...). Back-of-the-envelope estimation (~5 phút). Tính DAU, QPS peak/avg, read:write ratio, storage growth theo năm, bandwidth. Không cần chính xác — cần đúng order of magnitude để quyết định "cần shard hay không", "cache có ý nghĩa không", "một node đủ hay phải scale ngang". High-level design (~10–15 phút). Vẽ box diagram: client → LB → app tier → cache → primary datastore → async worker → object storage/CDN. Định nghĩa API (endpoint, request/response, status code) và data model (schema chính, index chính). Ở bước này

2026-07-08 原文 →