2 điểm bởi GN⁺ 2023-08-13 | 1 bình luận | Chia sẻ qua WhatsApp
  • tRPC là công cụ chia sẻ khả năng suy luận TypeScript trong các ứng dụng TypeScript full-stack, giúp giảm sai lệch kiểu API giữa server và client
  • Khi thay đổi API phía server, bạn có thể thấy ngay tác động trong code client qua lỗi TypeScript và tự động hoàn thành, nhờ đó giảm gánh nặng refactor
  • Hoạt động mà không cần schema riêng hay bước sinh mã, cho phép xây dựng API type-safe mà không làm tăng quy trình build hay tải runtime
  • Cung cấp adapter có thể gắn vào nhiều môi trường JavaScript như React, Next.js, Express, Fastify, AWS Lambda, Solid, Svelte, v.v.
  • Luồng cơ bản là định nghĩa procedure, tạo HTTP server, rồi kết nối client bằng cách truyền kiểu AppRouter; mục tiêu là mang lại trải nghiệm phát triển dùng code API server như một SDK

Cách tRPC thay đổi việc phát triển API

  • tRPC nâng cao năng suất bằng cách cho phép server và client trong ứng dụng full-stack cùng tận dụng thông tin kiểu TypeScript
  • Trọng tâm là dễ dàng tạo API type-safe từ đầu đến cuối và giảm các công việc lặp lại trong lớp API truyền thống
  • Nếu thay đổi phía server ảnh hưởng đến phần sử dụng ở client, TypeScript sẽ báo lỗi, giúp nhanh chóng phát hiện sai lệch kiểu ở ranh giới client-server

Những đặc điểm chính mà lập trình viên cảm nhận được

  • An toàn kiểu tự động

    • Khi thay đổi phía server ảnh hưởng đến code client, điều đó sẽ lộ ra dưới dạng lỗi TypeScript
  • Trải nghiệm lập trình viên nhanh

    • tRPC không có bước build hay compile
    • Có thể dùng mà không cần sinh mã, không có tải runtime, không cần quy trình build riêng
  • Độc lập framework

    • Có thể sử dụng trên nhiều framework và runtime JavaScript
    • Dễ dàng thêm vào cả các dự án hiện có
  • Tự động hoàn thành

    • Mang lại trải nghiệm xử lý code API server như một SDK
    • Nhận được gợi ý dựa trên kiểu khi sử dụng endpoint
  • Dấu chân client nhỏ

    • tRPC không có dependency và kích thước phía client nhỏ
  • Adapter đi kèm

    • Cung cấp adapter cho React, Next.js, Express, Fastify, AWS Lambda, Solid, Svelte, v.v.

Luồng sử dụng cơ bản

  • API tRPC bắt đầu bằng cách định nghĩa procedure
  • Procedure là các hàm cấu thành backend, có thể kết hợp và có thể được tạo thành query, mutation, subscription
  • Nhiều procedure được gom vào router
  • Định nghĩa procedure

    • Trong ví dụ, một instance tRPC được tạo bằng initTRPC.create(), rồi cấu hình routerpublicProcedure
    • Procedure greeting nhận chuỗi name làm input và trả về chuỗi dạng Hello ${input.name}
    • Phần xác thực input dùng Zod để đảm bảo input từ client khớp chính xác với dạng mà procedure kỳ vọng
    • Ở cuối file, kiểu router được export bằng export type AppRouter = typeof appRouter; để có thể dùng trong code frontend
  • Tạo HTTP server

    • Chạy server tRPC bằng cách truyền appRouter vào createHTTPServer
    • Trong ví dụ, gọi listen(3000) để API lắng nghe trên cổng 3000
    • tRPC cung cấp adapter cho Next.js, Express, môi trường dựa trên Fetch API, Fastify, AWS Lambda và server vanilla Node HTTP
    • Ví dụ về môi trường dựa trên Fetch API gồm Astro, Remix, SvelteKit, Cloudflare Workers
  • Kết nối client và chạy query

    • Khi server đã chạy, có thể tạo client bằng createTRPCClient<AppRouter> và query dữ liệu
    • Client ví dụ dùng httpBatchLink để kết nối tới http://localhost:3000
    • Có thể gọi như trpc.greeting.query({ name: 'John' }) để sử dụng procedure greeting trên server
    • Khi tạo client, truyền kiểu AppRouter sẽ mang lại tự động hoàn thành TypeScript và IntelliSense khớp với API backend mà không cần sinh mã

Template khởi đầu và ý đồ tạo ra

  • Có cung cấp template để tự thử nghiệm: Use this template
  • Tác giả tRPC tạo ra tRPC nhằm giảm nhu cầu về lớp API truyền thống, đồng thời mang lại sự tin tưởng rằng ứng dụng không bị hỏng trong quá trình phát triển lặp nhanh
  • tRPC đang được sử dụng bởi các đội ngũ công nghệ hàng đầu và nhiều công ty Fortune 500

Phản hồi từ lập trình viên

  • Qua nhiều trích dẫn từ lập trình viên, tRPC được đánh giá là công cụ cải thiện chất lượng code, tốc độ bàn giao, mức độ hài lòng của lập trình viên, việc refactor ở ranh giới client-server, xác thực input và trải nghiệm middleware có kiểu
  • Trong monorepo TypeScript, tRPC được xem là lựa chọn đơn giản hơn plain REST hoặc GraphQL nhưng vẫn cung cấp kiểu mạnh
  • Một use case được giới thiệu là ngay cả khi trả về payload Stripe API từ server, các component React vẫn có thể nhận được kiểu dữ liệu phản hồi

1 bình luận

 
GN⁺ 2023-08-13
Ý kiến trên Hacker News
  • Hiện chúng tôi đang tiến hành loại bỏ tRPC khỏi codebase, và đó đúng là ác mộng vì mức độ gắn kết quá chặt
    Nó cũng có mặt khiến các lập trình viên junior không phải suy nghĩ về interface hay mẫu truy cập dữ liệu, và tạo ra một kiểu ánh xạ kéo thẳng từ Prisma tới tận component
    Rất tuyệt cho prototype nhanh, nhưng nếu về sau muốn tách codebase ra thì rất nhanh sẽ rơi vào ngõ cụt

    • Đây chính là lợi thế thường bị bỏ qua của schema. Một schema như GraphQL buộc bạn phải nghĩ về kiểu dữ liệu và contract, đồng thời giúp những người phụ trách các phần khác của code phối hợp với nhau dễ hơn
      Vì nó còn mở rộng sang cả các ngôn ngữ ngoài TypeScript, nên cũng hữu ích khi chuyển backend sang ngôn ngữ khác hoặc tạo client mobile native như Swift, Kotlin
    • Công ty chúng tôi đã áp dụng tRPC, và chỉ cần thiết kế trước một chút là chúng tôi đã tách bạch khá tốt những thứ có thể bị “gắn kết chặt” trong code
      tRPC rất tốt, nhưng rốt cuộc nó chỉ là tầng truyền tải giữa backend và frontend
      Để cấu trúc nội bộ của tRPC len sâu vào business logic cũng tệ chẳng khác gì việc không có một tầng controller hay router định nghĩa rõ input, schema và ranh giới tách biệt
      Vì vậy, ngay cả nếu sau này rời khỏi tRPC thì có lẽ cũng tương đối đơn giản; chẳng hạn, chuyển cả một subsystem sang chạy trên queue có vẻ cũng không khó
    • Vấn đề không hẳn là tRPC, mà gần hơn là việc có những kỹ sư dùng type chỉ để gắn type cho có. Dùng công cụ nào thì cũng sẽ gặp vấn đề tương tự
      Những thứ như vậy có đường cong học tập, và thường bắt đầu từ các type vô dụng, chẳng giải thích được gì như type FunctionIWroteTodayArgs = …
      Qua vài vòng lặp, họ dần nhận ra mục tiêu không phải là sao chép code, mà là mô tả domain và tạo ra các type, interface, API có thể tái sử dụng và hữu ích
      Vì vậy tôi nghĩ nên cùng đội cải thiện phần đó, thay vì gỡ bỏ tRPC
    • Tôi muốn nghe giải thích thêm về việc gắn kết chặt là ác mộng như thế nào
      Tôi chưa rõ vì sao khai báo HTTP client ở phía server rồi tiêu thụ ở phía client lại tệ hơn
      Chúng tôi tạo các service rồi dùng một pattern mà mọi consumer như web interface hay CLI đều dùng; việc những thứ này không bị vỡ là một cải thiện lớn hơn bất kỳ cách nào tôi từng thấy trước đây
    • Ở các team nhỏ, chúng tôi đang có trải nghiệm hoàn toàn ngược lại, và tôi nghĩ nó cũng sẽ hoạt động tốt ở các team lớn trước đây
      Phàn nàn rằng junior không suy nghĩ về interface nhiều khả năng sẽ xuất hiện y hệt dù dùng tRPC hay bất kỳ API nào khác
  • Notion đã dùng một phong cách API tương tự tRPC, và API đó có trước tRPC khoảng 4 năm
    Có thể tự làm những thứ như vậy khá dễ bằng mapped types của TypeScript. Chỉ cần tạo một object type trong đó key là tên API còn value là type { request, response }
    Phía server định nghĩa mỗi API handler là một hàm nhận APIs["addUser"]["request"] và trả về Promise, còn client thì expose dưới dạng hàm bất đồng bộ có cùng tham số và kiểu trả về
    Chúng tôi dùng chiến lược này cho API nội bộ qua HTTPS, API realtime dựa trên WebSocket, IPC giữa Electron và Webview, cũng như IPC webview của OS giữa native iOS/Android và Webview
    Với API native, phía server là Swift hoặc Kotlin nên chúng tôi viết lại thủ công type request/response trong TypeScript. Một ngày nào đó có thể sẽ chuyển sang định dạng nhị phân có IDL riêng, nhưng với một API cross-language đơn lẻ đang lớn dần, chi phí developer experience của những thứ như Protobuf hiện vẫn chưa có vẻ đáng bỏ ra

    • Dự án hiện tại của chúng tôi là frontend TypeScript kết hợp backend Python, dùng OpenAPI schema làm nguồn sự thật và sử dụng openapi-typescript-codegen [0] ở phía client
      Không hoàn hảo, nhưng nó cung cấp một interface API khá tốt với type cho request/response
      Chúng tôi còn tạo một wrapper mock API khoảng 10 dòng có thể gọi kiểu mockApi((request) => response), trong đó mock function được type-check để đảm bảo implement API đúng cách và trả về một hàm trông y hệt hàm API thật
      [0]: https://github.com/ferdikoomen/openapi-typescript-codegen
    • Cách này mới đúng. tRPC thêm nhiều độ phức tạp không cần thiết hơn là chỉ suy luận type đơn thuần
      Có vẻ nó chưa được bàn luận thường xuyên vì vẫn chưa có một thư viện được duy trì tốt và quảng bá tốt áp dụng cách tiếp cận này
    • Tôi tò mò liệu có mẹo nào cho việc xác thực dữ liệu request trong quy trình này không
      Khi dùng tRPC, JSON Schema kèm type generation, hay Zod, giá trị quan trọng chính là phần đó
    • Nhìn sample code, tôi tự hỏi liệu nó có gần giống một wrapper phủ type lên trên lời gọi XHR không
      Thậm chí còn yêu cầu truyền trực tiếp đối số generic khi gọi, điều này khá tệ nếu muốn giữ dependency tree ở trạng thái gọn gàng
    • Cách này thực ra hoạt động khá tốt
      https://github.com/mikew/transmission-material-ui/blob/maste...
      https://github.com/mikew/transmission-material-ui/blob/maste...
  • Rất thích tRPC. Thật đáng kinh ngạc khi nó đẩy trải nghiệm nhà phát triển đến giới hạn trong stack chỉ dành cho TypeScript, và khiến cộng đồng GraphQL nhận ra các giới hạn và đánh đổi của ngôn ngữ truy vấn
    Đồng thời, tRPC đã đi qua chu kỳ thịnh hành rất nhanh, và dường như không có một cuộc dịch chuyển quy mô lớn nào từ REST và GraphQL sang RPC
    Tuy vậy, hiện nay có vẻ vẫn có nhiều sự quan tâm đến RPC, và trong framework BFF mà chúng tôi tạo ra (https://wundergraph.com/), chúng tôi cũng đã tiếp thu một phần ý tưởng của tRPC và NextJS trước đây để kết hợp routing dựa trên file với RPC
    Ngoài tRPC, chúng tôi tự động tạo JSON Schema cho từng thao tác, đồng thời cũng tạo đặc tả OpenAPI cho toàn bộ tập hợp thao tác
    Mọi người thích cách tiếp cận này vì có thể dễ dàng chia sẻ một nhóm endpoint RPC dưới dạng đặc tả OpenAPI hoặc collection Postman. Cũng hầu như không còn tranh luận về HTTP verb; trên thực tế chỉ có truy vấn, thay đổi và đăng ký
    Tôi tò mò hiện nay mọi người đang dùng API kiểu GraphQL, REST, RPC như thế nào, và có bao nhiêu người hoặc bao nhiêu nhóm tham gia vào API

    • Garph giống như tRPC cho GraphQL: https://garph.dev
      Ở phía REST API thì có ts-rest(https://ts-rest.com), zodios(https://www.zodios.org), Hono(https://hono.dev)
      Nếu nhóm dùng nhiều ngôn ngữ, cũng có Fern: https://www.buildwithfern.com
    • Sẽ rất hay nếu bạn giải thích thêm phần “khiến cộng đồng GraphQL nhận ra các giới hạn và đánh đổi của ngôn ngữ truy vấn”
      Chúng tôi tạo các type GraphQL mỗi khi federated schema thay đổi, và tạo type phản hồi cho truy vấn và thay đổi mỗi khi lưu file
    • Điều luôn nổi bật là verb và path chỉ là những chi tiết khá nhỏ nhặt
      Chúng cũng có thể được trừu tượng hóa dễ dàng bằng thư viện tạo consumer cho endpoint, nhưng ngoài chuyện đó ra thì tôi không rõ thực sự thu được thêm gì so với một web API thông thường
      Dù sao thì ở mỗi lời gọi backend, những việc cần làm vẫn phải được thực hiện như cũ
  • Tôi thích tRPC. Đây chắc chắn là trải nghiệm full-stack developer tốt nhất tôi từng thấy, và đặc biệt khi dùng cùng Zod thì API thực sự rất tuyệt
    Tôi xem Zod và tRPC là những dự án quan trọng trong tương lai của TypeScript, và nghĩ rằng trong vài năm tới, trải nghiệm developer lấy cảm hứng từ tRPC sẽ nở rộ mạnh mẽ trên toàn bộ hệ sinh thái TypeScript
    Những dự án đã có DNA của tRPC rất rõ ràng gồm UploadThing của Ping(https://github.com/pingdotgg/uploadthing) nhắm tới use case khác, và Lusat của chúng tôi(https://github.com/lusatai/lusat)

    • Tôi không thích Zod lắm. Các type và tham số generic của nó khó dùng và dài dòng; không thể truyền data type vào object generic mà phải truyền chính schema type của Zod làm thuộc tính, nên khá lộn xộn và không trực quan
      Ngoài ra việc type của lỗi validation thay đổi tùy theo schema nào được kiểm tra cũng gây phiền. Xử lý lỗi trở nên khó dự đoán và luôn có quá nhiều trường hợp ngoại lệ
      Còn nhiều chỗ cần cải thiện
    • Gần đây tôi dùng tRPC và Zod trong một dự án cá nhân, và đồng ý rằng đó thực sự là một trải nghiệm tuyệt vời
      Việc viết unit test cũng trở nên dễ hơn nhiều
    • Tôi tò mò liệu phía MongoDB có công cụ nào trong mảng này không
      Vấn đề lớn dường như là CRUD an toàn kiểu và migration dữ liệu; Mongoose được nhắc đến khá nhiều, nhưng về mặt type safety thì cảm giác như thụt lùi đáng kể so với Zod/TypeScript
    • Zod thực sự cực kỳ tốt
      Giờ tôi hơi muốn nói quá lên một chút: trong một số phong cách lập trình nhất định, việc thêm Zod vào codebase có thể mang lại lợi ích lớn ngang với việc thêm TypeScript
    • Cũng khuyến nghị effect/core và effect/schema
      Pattern bắt đầu từ schema rồi tạo ra service có type rất hợp với những người thiên về functional programming
  • Tôi tò mò tRPC xử lý khác biệt phiên bản và migration như thế nào
    Field có vòng đời của nó. Khi mới được đưa vào, không client hay server nào biết field đó
    Client và server không được khởi động lại tất cả cùng lúc, và cũng không có cùng phiên bản type
    Dữ liệu được ghi bằng type của một phiên bản và được đọc bằng type của phiên bản khác
    Nếu có thể đảm bảo mọi binary, chương trình đang chạy và dữ liệu đều được nâng cấp, và không có dữ liệu bền vững, thì có thể không thành vấn đề
    Nhưng ngay khi có nhiều tổ chức liên quan, rất khó đảm bảo mọi app và server đều được đồng bộ lên phiên bản thư viện mới nhất, được build lại và triển khai lại
    Kiểm tra kiểu tĩnh giả định rằng không có khác biệt phiên bản. Toàn bộ code trong binary là một thế giới đóng, được build bằng cùng một phiên bản thư viện định nghĩa type

    • Gần như là “phải tự làm”
      Zod có thể cho phép các field không biết, sau đó chuyển sang field tùy chọn, rồi khi tình hình ổn định thì biến thành field bắt buộc
      Tất nhiên, nhóm người muốn tRPC làm hộ nhất lại chính là nhóm ít có khả năng nghĩ đến loại vấn đề này nhất
    • Vì vậy nó là công cụ dành cho web app
  • Nếu chỉ nhắm tới một ngôn ngữ thì việc không cần schema hay code generation là điều hiển nhiên

  • Tôi đã dùng tRPC cho hai ứng dụng web khoảng 50 nghìn dòng và rất thích. Trải nghiệm nhà phát triển rất tuyệt
    Tuy nhiên thời kỳ tRPC thịnh hành đã qua từ khá lâu, và cảm giác là trào lưu RSC đã nhanh chóng bắt kịp
    Dạo này dù có một giải pháp ổn định và tốt như tRPC, mọi người chỉ nói về việc có nên dùng RSC hay không
    Tôi không phản đối RSC, nhưng có quá nhiều cuộc thảo luận. Khi xây dựng ứng dụng hiện nay, tRPC là một cách rất thực dụng
    Bổ sung: RSC là React Server Components, và nó mang theo triết lý dữ liệu đọc/ghi riêng

    • Tôi chưa từng nghe đến RSC và tìm kiếm cũng không ra nhiều. Nếu có link thì tốt
    • Tôi không hiểu Royal Shakespeare Company liên quan gì đến RPC
      Những từ viết tắt mà người khác có thể không biết thì nên được định nghĩa hoặc gắn link
  • Tôi đã thử dùng tRPC và Next.js trong vài dự án cá nhân và đó là một trải nghiệm tốt
    Đặc biệt khi dùng cùng các template đã cấu hình sẵn như Create T3 App(https://create.t3.gg/), rất khó để đánh bại được tốc độ lặp lại khi phát triển

    • Tôi tò mò liệu giờ nó đã hoạt động đúng với Next 13 và Server Components chưa
  • Tôi không biết đến nó cho đến khi một lập trình viên từng làm việc cùng hết lời khen tRPC, nhưng sau khi biết rồi thì nó trông rõ ràng là quá tốt
    Chúng tôi đã cùng tạo một ứng dụng T3 (tRPC, Next.js, Tailwind, TypeScript, Prisma), nếu muốn xem thì ở đây: https://github.com/stytchauth/stytch-t3-example
    Khi làm việc với TypeScript, API an toàn kiểu thực sự giúp ích rất nhiều