tRPC与REST和GraphQL 服务对比

1,220 阅读9分钟

全文翻译自:medium.com/codex/trpc-…

本文比较三种流行的API范式,主要基于它们的特点、优势和缺点来评估。

在开发Web应用程序时,最重要的决定之一就是如何创建你的API。对于你的Web应用程序来说,API是至关重要的。

根据需求和偏好,API的设计和实现可以有所不同。本文介绍了三种广泛使用的API创建方法:tRPCRESTGraphQL.。

它回答了一个核心问题:"在创建单一服务API时,你如何在tRPC、REST和GraphQL之间进行选择?"

三大API范式概览

tRPC(TypeScript Remote Procedure Calls)是一个库,它允许你使用TypeScript和远程过程调用(RPC)创建类型安全的API。 RPC协议允许你像本地调用函数一样在远程服务器上调用函数。因此,tRPC通过自动生成类型和验证输入输出来简化这个过程。——又称为轻量级API。

REST(Representational State Transfer)是一种采用HTTP方法(例如GET、POST、PUT和DELETE)在URL标识的资源上执行操作的架构风格。REST被广泛使用,并得到许多工具和框架的支持。然而,对于复杂或嵌套的数据,REST可能会变得冗长、不一致和低效。——又称为经典API。

GraphQL (Graph Query Language)是一种查询语言,它允许你使用查询、变更和订阅来指定从服务器获取的精确数据。GraphQL可以通过允许客户端仅请求所需数据来减少数据的过度获取或不足获取。但GraphQL也有一些缺点,比如复杂性、缓存问题和性能开销。——又称为巨兽级API。

哪个在单一服务API中更好?这取决于实际情况...

无论如何,你应该考虑以下因素:

  1. 数据的复杂性
  2. 数据的结构
  3. 对查询的灵活性和控制需求
  4. 对I/O的类型安全和验证需求
  5. API的性能和可扩展性
  6. API的学习曲线和工具支持

根据你的具体需求和项目特点,选择适合的API范式是最明智的选择。tRPC、REST和GraphQL都有各自的优点和局限性,仔细评估后选择最适合你的API风格。

为什么tRPC可能是一个不错的选择

总的来说,如果你重视类型安全、简洁和轻量级,tRPC是一个不错的选择。

tRPC可以通过自动生成类型和验证输入输出来简化你的流程。如果你使用TypeScript,并且希望为你的输入输出实现端到端的类型安全,那么tRPC非常适合。

你可以将tRPC与不同的后端框架一起使用,比如Next.jsExpress,或者使用一个绑定HTTP请求和tRPC的API 处理程序。tRPC是轻量级的、简单易用的。

总的来说,它可以让你创建类型安全的API,并通过自动生成类型和验证数据来简化这个过程。

tRPC允许您轻松构建和使用完全类型安全的API,无需schemas或代码生成。 — trpc.io/docs

tRPC的优势:

  1. 类型安全:通过tRPC,在编译和运行时,您可以确保输入和输出与您的TypeScript类型匹配。避免因参数改变而导致运行时崩溃是非常重要的。
  2. 简易性:相比GraphQL,您不需要任何schemas或代码生成。您可以像平常一样编写函数,tRPC会顺利地处理其余部分。
  3. 轻量级:tRPC是一个简单轻量的库,不会给您的应用程序增加过多的负担。它还支持以最小的延迟来流式传输数据。

tRPC的弱点:

  1. 流行度:相对于REST或GraphQL,tRPC还比较新,并且不如它们流行。您可能会问自己:“我真的需要最新的技术吗?”因此可能会导致的更少的资源、教程和社区支持。
  2. 兼容性:tRPC仅支持TypeScript(以及JavaScript,但有一些限制)。它不支持其他语言或平台。
  3. 灵活性:您无法自定义客户端查询或订阅。而是必须使用服务器提供的预定义函数。

示例:使用Next.js获取用户ID的查询

以下代码定义了一个带有查询过程的路由,该查询过程接受一个类型为number的输入,并返回一个用户对象。

然后,使用trpcNext.createNextApiHandler将该路由用于Next.js,创建一个API处理程序。

// Define a router with a query procedure  
const appRouter = trpc.router().query('userById', {  
input: z.number(),  
resolve({ input }) {  
return getUserById(input);  
},  
});  
  
// Create an API handler for the router  
export default trpcNext.createNextApiHandler({  
router: appRouter,  
});  
  
// Call the query from the frontend using useQuery hook  
const { data, error } = useQuery(['userById', userId]);

转折在于你可以使用tRPC提供的useQuery钩子从前端调用该查询。

下面的例子展示了如何定义一个包含mutation过程的router,该过程接受一个包含id和title属性的对象作为输入,并返回一个更新后的帖子对象。

然后,使用createFetchHandler创建一个Fetch API的API处理程序。

示例:使用Fetch API用于定义一个mutation过程,该过程用于更新帖子的标题

下面的例子展示了如何定义一个包含mutation过程的router,该过程接受一个包含id和title属性的对象作为输入,并返回一个更新后的帖子对象。

然后,使用createFetchHandler创建一个Fetch API的API处理程序。

// Define a router with a mutation procedure  
const appRouter = trpc.router().mutation('updatePostTitle', {  
    input: z.object({  
    id: z.number(),  
    title: z.string(),  
    }),  
    resolve({ input }) {  
        return updatePostTitle(input.id, input.title);  
    },  
});  
  
// Create an API handler for the router  
export default createFetchHandler({  
    router: appRouter,  
});  
  
// Call the mutation from the frontend using fetch function  
fetch('/api/trpc/updatePostTitle', {  
    method: 'POST',  
    body: JSON.stringify({  
        id: postId,  
        title: newTitle,  
    }),  
});

这个Mutation过程可以通过JavaScript提供的fetch函数从前端调用。

REST可能是一个不错的选择

如果你重视熟悉性、兼容性和简单性。

REST是一种使用HTTP方法(如GET、POST、PUT和DELETE)对URL标识的资源执行操作的架构风格。REST被广泛使用,并且得到许多工具和框架的支持。

然而,对于复杂或嵌套数据,REST可能会变得冗长、不一致和低效。

REST的优势包括:

  1. 熟悉性:你应该很熟悉REST吧?它是一种简单的HTTP协议,已经广为人知很长时间了。它是经典的网页开发协议。
  2. 兼容性:REST可以与支持HTTP请求的任何语言或平台简单地配合使用。它也与现有的工具和框架(如Swagger或Postman)很好地集成。
  3. 简单性:REST不需要任何特殊的语法或库来使用。你只需要遵循一些命名URL和方法的约定即可。

REST的劣势包括:

  1. 容易冗长:你是否曾经处理过复杂或嵌套的数据结构?你可能需要进行多次请求或在网络上发送不必要的数据。
  2. 不一致性:由于它没有标准的方式来定义或文档化API模式,这可能很快变得麻烦。如果你不是独立工作,你的同事可能会使用不同的样式或格式来编写他们的API,导致混乱或错误。
  3. 效率低下:没有方便的过滤、排序或分页功能。你可能需要为每个用例创建新的端点或参数,这可能会增加API的复杂性。

示例:使用内置函数Fetch发送GET请求到一个返回随机笑话的API。

// Specify the URL of the API  
var url = "https://official-joke-api.appspot.com/random_joke";  
  
// Make a GET request with fetch  
fetch(url)  
    // Parse the JSON response  
    .then((response) => response.json())  
    // Log the joke to the console  
    .then((data) => {  
        console.log(data.setup + " " + data.punchline);  
    })  
    // Handle any errors  
    .catch((error) => {  
        console.error(error);  
    });

示例:使用FormData将表单数据发送到服务器以提交表单。

// Create a new FormData object  
var formData = new FormData();  
  
// Append the text and voice parameters to the form data  
formData.append("text", "Hello world");  
formData.append("voice", "en-US");  
  
// Specify the URL of the API  
var url = "https://text-to-speech-demo.ng.bluemix.net/api/v3/synthesize";  
  
// Make a POST request with fetch  
fetch(url, {  
    method: "POST",  
    body: formData,  
})  
    // Get the blob response  
    .then((response) => response.blob())  
    // Create an audio element and play the sound  
    .then((blob) => {  
        var audio = document.createElement("audio");  
        audio.src = URL.createObjectURL(blob);  
        audio.play();  
    })  
    // Handle any errors  
    .catch((error) => {  
        console.error(error);  
    });

GraphQL可能是一个不错的选择,

如果您重视灵活性、高效性和细粒度查询。

GraphQL允许您使用查询、变更和订阅来精确指定从服务器请求的数据。通过允许客户端仅请求所需的数据,GraphQL可以减少过度获取或不足获取的情况。

这听起来好得令人难以置信,是的,这个小巨人也有缺点,例如复杂性、缓存问题和性能开销。

GraphQL的优势包括:

灵活性:GraphQL允许客户端使用查询、变更和订阅来指定从服务器请求的数据。这可以通过允许客户端仅请求所需的数据来减少过度获取或不足获取的情况。

高效性:GraphQL可以使用别名、片段或指令在单个请求中获取复杂或嵌套的数据。这可以改善API的性能和带宽使用。

一致性:GraphQL使用SDL(Schema Definition Language)来定义和记录API的架构,这使得API更易于理解和维护。

GraphQL的缺点包括:

复杂性:与REST或tRPC相比,GraphQL有较陡峭的学习曲线。您需要学习新的语法、新的库(如Apollo)以及新的关于数据模型的思维方式。

缓存问题:GraphQL没有像http之于REST API那样内置缓存机制。您可能需要实现自己的缓存策略或使用第三方解决方案,如Redis。

示例:使用GitHub API的GraphQL查询获取包含作者和评论的帖子列表

该示例演示了如何使用GraphQL从单个端点请求数据。

查询指定了所需的字段,例如React仓库中每个问题的标题、作者和评论。

query {
  repository(owner: "facebook", name: "react") {
    issues(first: 10) {
      nodes {
        title
        author {
          login
        }
        comments(first: 5) {
          nodes {
            body
            author {
              login
            }
          }
        }
      }
    }
  }
}

该查询还使用参数来限制返回的问题和评论数量。

响应将是一个与查询形状相匹配的JSON对象。

示例:使用Shopify API创建一个带有名称和价格的新产品的变更请求。

这个例子展示了如何使用GraphQL向服务器发送数据并修改其状态。 这个mutation指定了创建一个新产品的输入数据,包括标题和带有价格的变种。

mutation {  
productCreate(input: {title: "GraphQL T-Shirt", variants: {price: 19.99}}) {  
    product {  
        id  
        title  
        variants(first: 1) {  
            edges {  
                node {  
                    price  
                }  
            }  
        }  
    }  
    userErrors {  
        field  
        message  
    }  
  }  
}

这个mutation还指定了响应中期望的字段,比如id、title、price和userErrors用于任何验证错误。

响应将是一个JSON对象,其结构与这个mutation相匹配。

结束语

互联网上有很多选项可供您创建API。其中三种最受欢迎的是tRPC、REST和GraphQL。本文对这三种方法进行了比较,展示了它们的优点和缺点,并提供了一些示例供您参考。

希望这些信息能帮助您理解tRPC、REST和GraphQL在单一服务API中的区别,让您能够选择合适的方法。 Identify Code Smells With Ease and Replace Them With Best Practice Patterns. Get the " All Code Smells As One-Liner "-Cheatsheets.

References