{"id":599739,"date":"2023-01-21T06:49:32","date_gmt":"2023-01-21T12:49:32","guid":{"rendered":"https:\/\/news.sellorbuyhomefast.com\/index.php\/2023\/01\/21\/building-reliable-distributed-systems-in-node-js\/"},"modified":"2023-01-21T06:49:32","modified_gmt":"2023-01-21T12:49:32","slug":"building-reliable-distributed-systems-in-node-js","status":"publish","type":"post","link":"https:\/\/newsycanuse.com\/index.php\/2023\/01\/21\/building-reliable-distributed-systems-in-node-js\/","title":{"rendered":"Building Reliable Distributed Systems in Node.js"},"content":{"rendered":"<div>\n<p>This post introduces the concept of <em>durable execution<\/em>, which is used by Stripe, Netflix, Coinbase, Snap, and many others to solve a wide range of problems in distributed systems. Then it shows how simple it is to write durable code using our TypeScript\/JavaScript SDK.<\/p>\n<h2>Distributed systems<\/h2>\n<p>When building a request-response monolith backed by a single database that supports transactions, we don\u2019t have many distributed systems concerns. We can have simple failure modes and easily maintain accurate state:<\/p>\n<p>If the client can\u2019t reach the server, the client retries.<br \/>\nIf the client reaches the server, but the server can\u2019t reach the database, the server responds with an error, and the client retries.<br \/>\nIf the server reaches the database, but the transaction fails, the server responds with an error, and the client retries.<br \/>\nIf the transaction succeeds but the server goes down before responding to the client, the client retries until the server is back up, and the transaction fails the second time (assuming the transaction has some check\u2013like an idempotency token\u2013to tell whether the update has already been applied), and the server reports to the client that the action has already been performed.<\/p>\n<p>As soon as we introduce a second place for state to live, whether that\u2019s a service with its own database or an external API, handling failures and maintaining consistency (accuracy across all data stores) gets significantly more complex. For example, if our server has to charge a credit card and also update the database, we can no longer write simple code like:<\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>function handleRequest() {\n  paymentAPI.chargeCard()\n  database.insertOrder()\n  return 200\n}\n<\/code><\/pre>\n<p><\/code><\/p>\n<p>If the first step (charging the card) succeeds, but the second step (adding the order to the database) fails, then the system ends up in an inconsistent state; we charged their card, but there\u2019s no record of it in our database. To try to maintain consistency, we might have the second step retry until we can reach the database. However, it\u2019s also possible that the process running our code will fail, in which case we\u2019ll have no knowledge that the first step took place. To fix this, we need to do three things:<\/p>\n<ul>\n<li>Persist the order details<\/li>\n<li>Persist which steps of the program we\u2019ve completed<\/li>\n<li>Run a worker process that checks the database for incomplete orders and continues with the next step<\/li>\n<\/ul>\n<p>That, along with persisting retry state and adding timeouts for each step, is a lot of code to write, and it\u2019s easy to miss certain edge cases or failure modes (see <a href=\"https:\/\/temporal.io\/blog\/workflow-engine-principles\">the full, scalable architecture<\/a>). We could build things faster and more reliably if we didn\u2019t have to write and debug all that code. And we don\u2019t have to, because we can use durable execution.<\/p>\n<h2>Durable execution<\/h2>\n<p>Durable execution systems run our code in a way that persists each step the code takes. If the process or container running the code dies, the code automatically continues running in another process with all state intact, including call stack and local variables.<\/p>\n<p>Durable execution ensures that the code is executed to completion, no matter how reliable the hardware or how long downstream services are offline. Retries and timeouts are performed automatically, and resources are freed up when the code isn\u2019t doing anything (for example while waiting on a <code node=\"[object Object]\">sleep(\u20181 month\u2019)<\/code> statement).<\/p>\n<p>Durable execution makes it trivial or unnecessary to implement distributed systems patterns like event-driven architecture, task queues, <a href=\"https:\/\/microservices.io\/patterns\/data\/saga.html\">sagas<\/a>, <a href=\"https:\/\/microservices.io\/patterns\/reliability\/circuit-breaker.html\">circuit breakers<\/a>, and <a href=\"https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html\">transactional outboxes<\/a>. It\u2019s programming on a higher level of abstraction, where you don\u2019t have to be concerned about transient failures like server crashes or network issues. It opens up new possibilities like:<\/p>\n<ul>\n<li>Storing state in local variables instead of a database, because local variables are automatically stored for us<\/li>\n<li>Writing code that sleeps for a month, because we don\u2019t need to be concerned about the process that started the sleep still being there next month, or resources being tied up for the duration<\/li>\n<li>Functions that can run forever, and that we can interact with (send commands to or query data from)<\/li>\n<\/ul>\n<p>Some examples of durable execution systems are Azure Durable Functions, Amazon SWF, Uber Cadence, Infinitic, and <a href=\"https:\/\/temporal.io\/\">Temporal<\/a> (where I work). At the risk of being less than perfectly objective, I think Temporal is the best of these options ????.<\/p>\n<h2>Durable JavaScript<\/h2>\n<p>Now that we\u2019ve gone over consistency in distributed systems and what durable execution is, let\u2019s look at a practical example. I built this food delivery app to show what durable code looks like and what problems it solves:<\/p>\n<p><a href=\"https:\/\/temporal.menu\/\">temporal.menu<\/a><\/p>\n<p><img decoding=\"async\" src=\"http:\/\/images.ctfassets.net\/0uuz8ydxyd9p\/23viK26Hvfx8jYCam3kC9r\/36321dc6b375fe829a8c3e701b85bb9f\/menu.jpg\" alt=\"Durable Delivery app menu\"><\/p>\n<p><em>Don\u2019t blame me for the logo\u2014that\u2019s just what Stable Diffusion gives you when you ask it for a durable delivery app logo. ????\u200d\u2642\ufe0f????<\/em><\/p>\n<p>The app has four main pieces of functionality:<\/p>\n<ul>\n<li>Create an order and charge the customer<\/li>\n<li>Get order status<\/li>\n<li>Mark an order picked up<\/li>\n<li>Mark an order delivered<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"http:\/\/images.ctfassets.net\/0uuz8ydxyd9p\/5t9ArgfJP8K0UGFpOAzidi\/5b37dc9c647370714bd5b53b692615d0\/delivery-demo.gif\" alt=\"The order process, showing both the menu and driver sites\"><\/p>\n<p>When we order an item from the menu, it appears in the delivery driver site (<a href=\"https:\/\/drive.temporal.menu\">drive.temporal.menu<\/a>), and the driver can mark the order as picked up, and then as delivered.<\/p>\n<p>All of this functionality can be implemented in a single function of durable JavaScript or TypeScript. We\u2019ll be using the latter\u2014I recommend TypeScript and our library is named the TypeScript SDK, but it\u2019s published to npm as JavaScript and can be used in any Node.js project.<\/p>\n<h2>Create an order<\/h2>\n<p>Let\u2019s take a look at the code for this app. We\u2019ll see a few API routes but mostly go over each piece of the single durable function named <code node=\"[object Object]\">order<\/code>. If you\u2019d like to run the app or view the code on your machine, this will download and set up the project:<\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>npx @temporalio\/create@latest --sample food-delivery\n<\/code><\/pre>\n<p><\/code><\/p>\n<p>When the user clicks the order button, the React frontend calls the <code node=\"[object Object]\">createOrder<\/code> mutation defined by the tRPC backend. The <code node=\"[object Object]\">createOrder<\/code> API route handler creates the order by starting a durable <code node=\"[object Object]\">order<\/code> function. Durable functions\u2014called <em>Workflows<\/em>\u2014are started using a Client instance from <code node=\"[object Object]\">@temporalio\/client<\/code>, which has been added to the tRPC context under <code node=\"[object Object]\">ctx.temporal<\/code>. The route handler receives a validated <code node=\"[object Object]\">input<\/code> (an object with a <code node=\"[object Object]\">productId<\/code> number and <code node=\"[object Object]\">orderId<\/code> string) and it calls <code node=\"[object Object]\">ctx.temporal.workflow.start<\/code> to start an <code node=\"[object Object]\">order<\/code> Workflow, providing <code node=\"[object Object]\">input.productId<\/code> as an argument:<\/p>\n<p><a href=\"https:\/\/github.com\/temporalio\/samples-typescript\/blob\/main\/food-delivery\/apps\/menu\/pages\/api\/%5Btrpc%5D.ts\">apps\/menu\/pages\/api\/[trpc].ts<\/a><\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>import { initTRPC } from '@trpc\/server'\nimport { z } from 'zod'\nimport { taskQueue } from 'common'\nimport { Context } from 'common\/trpc-context'\nimport { order } from 'workflows'\n\nconst t = initTRPC.context<Context>().create()\n\nexport const appRouter = t.router({\n  createOrder: t.procedure\n    .input(z.object({ productId: z.number(), orderId: z.string() }))\n    .mutation(async ({ input, ctx }) => {\n      await ctx.temporal.workflow.start(order, {\n        workflowId: input.orderId,\n        args: [input.productId],\n        taskQueue,\n      })\n\n      return 'Order received and persisted!'\n    }),\n<\/code><\/pre>\n<p><\/code><\/p>\n<p>The <code node=\"[object Object]\">order<\/code> function starts out validating the input, setting up the initial state, and charging the customer:<\/p>\n<p><a href=\"https:\/\/github.com\/temporalio\/samples-typescript\/blob\/main\/food-delivery\/packages\/workflows\/order.ts\">packages\/workflows\/order.ts<\/a><\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>type OrderState = 'Charging card' | 'Paid' | 'Picked up' | 'Delivered' | 'Refunding'\n\nexport async function order(productId: number): Promise<void> {\n  const product = getProductById(productId)\n  if (!product) {\n    throw ApplicationFailure.create({ message: `Product ${productId} not found` })\n  }\n\n  let state: OrderState = 'Charging card'\n  let deliveredAt: Date\n\n  try {\n    await chargeCustomer(product)\n  } catch (err) {\n    const message = `Failed to charge customer for ${product.name}. Error: ${errorMessage(err)}`\n    await sendPushNotification(message)\n    throw ApplicationFailure.create({ message })\n  }\n\n  state = 'Paid'\n<\/code><\/pre>\n<p><\/code><\/p>\n<p>Any functions that might fail are automatically retried. In this case, <code node=\"[object Object]\">chargeCustomer<\/code> and <code node=\"[object Object]\">sendPushNotification<\/code> both talk to services that might be down at the moment or might return transient error messages like \u201cTemporarily unavailable.\u201d Temporal will automatically retry running these functions (by default indefinitely with exponential backoff, but that\u2019s configurable). The functions can also throw non-retryable errors like \u201cCard declined,\u201d in which case they won\u2019t be retried. Instead, the error will be thrown out of <code node=\"[object Object]\">chargeCustomer(product)<\/code> and caught by the catch block; the customer receives a notification that their payment method failed, and we throw an <code node=\"[object Object]\">ApplicationFailure<\/code> to fail the <code node=\"[object Object]\">order<\/code> Workflow.<\/p>\n<h2>Get order status<\/h2>\n<p>The next bit of code requires some background: Normal functions can\u2019t run for a long time, because they\u2019ll take up resources while they\u2019re waiting for things to happen, and at some point they\u2019ll die when we deploy new code and the old containers get shut down. Durable functions can run for an arbitrary length of time for two reasons:<\/p>\n<ul>\n<li>They don\u2019t take up resources when they\u2019re waiting on something.<\/li>\n<li>It doesn\u2019t matter if the process running them gets shut down, because execution will seamlessly be continued by another process.<\/li>\n<\/ul>\n<p>So although some durable functions run for a short period of time\u2014like a successful money transfer function\u2014some run longer\u2014like our order function, which ends when the order is delivered, and a customer function that lasts for the lifetime of the customer.<\/p>\n<p>It\u2019s useful to be able to interact with long-running functions, so Temporal provides what we call <em>Signals<\/em> for sending data into the function and <em>Queries<\/em> for getting data out of the function. The driver site shows the status of each order by sending Queries to the order functions through this API route:<\/p>\n<p><a href=\"https:\/\/github.com\/temporalio\/samples-typescript\/blob\/cb617abb3e0f58a6911c66615f9c2c665e0307b0\/food-delivery\/apps\/menu\/pages\/api\/%5Btrpc%5D.ts#L23-L25\">apps\/menu\/pages\/api\/[trpc].ts<\/a><\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>  getOrderStatus: t.procedure\n    .input(z.string())\n    .query(({ input: orderId, ctx }) => ctx.temporal.workflow.getHandle(orderId).query(getStatusQuery)),\n<\/code><\/pre>\n<p><\/code><\/p>\n<p>It gets a handle to the specific instance of the order function (called a <em>Workflow Execution<\/em>), sends the <code node=\"[object Object]\">getStatusQuery<\/code>, and returns the result. The <code node=\"[object Object]\">getStatusQuery<\/code> is defined in the order file and handled in the order function:<\/p>\n<p><a href=\"https:\/\/github.com\/temporalio\/samples-typescript\/blob\/cb617abb3e0f58a6911c66615f9c2c665e0307b0\/food-delivery\/packages\/workflows\/order.ts#L55-L57\">packages\/workflows\/order.ts<\/a><\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>import { defineQuery, setHandler } from '@temporalio\/workflow'\n\nexport const getStatusQuery = defineQuery<OrderStatus>('getStatus')\n\nexport async function order(productId: number): Promise<void> {\n  let state: OrderState = 'Charging card'\n  let deliveredAt: Date\n\n  \/\/ \u2026\n\n  setHandler(getStatusQuery, () => {\n    return { state, deliveredAt, productId }\n  })\n<\/code><\/pre>\n<p><\/code><\/p>\n<p>When the order function receives the <code node=\"[object Object]\">getStatusQuery<\/code>, the function passed to <code node=\"[object Object]\">setHandler<\/code> is called, which returns the values of local variables. After the call to <code node=\"[object Object]\">chargeCustomer<\/code> succeeds, the state is changed to <code node=\"[object Object]\">\u2019Paid\u2019<\/code>, and the driver site, which has been polling <code node=\"[object Object]\">getStatusQuery<\/code>, gets the updated state. It displays the \u201cPick up\u201d button.<\/p>\n<h2>Picking up an order<\/h2>\n<p>When the driver taps the button to mark the order as picked up, the site sends a <code node=\"[object Object]\">pickUp<\/code> mutation to the API server, which sends a <code node=\"[object Object]\">pickedUpSignal<\/code> to the order function:<\/p>\n<p><a href=\"https:\/\/github.com\/temporalio\/samples-typescript\/blob\/main\/food-delivery\/apps\/driver\/pages\/api\/%5Btrpc%5D.ts\">apps\/driver\/pages\/api\/[trpc].ts<\/a><\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>  pickUp: t.procedure\n    .input(z.string())\n    .mutation(async ({ input: orderId, ctx }) => \n      ctx.temporal.workflow.getHandle(orderId).signal(pickedUpSignal)\n    ),\n<\/code><\/pre>\n<p><\/code><\/p>\n<p>The order function handles the Signal by updating the state:<\/p>\n<p><a href=\"https:\/\/github.com\/temporalio\/samples-typescript\/blob\/cb617abb3e0f58a6911c66615f9c2c665e0307b0\/food-delivery\/packages\/workflows\/order.ts#L42-L46\">packages\/workflows\/order.ts<\/a><\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>export const pickedUpSignal = defineSignal('pickedUp')\n\nexport async function order(productId: number): Promise<void> {\n  \/\/ \u2026\n\n  setHandler(pickedUpSignal, () => {\n    if (state === 'Paid') {\n      state = 'Picked up'\n    }\n  })\n<\/code><\/pre>\n<p><\/code><\/p>\n<p>Meanwhile, further down in the function, after the customer was charged, the function has been waiting for the pickup to happen:<\/p>\n<p><a href=\"https:\/\/github.com\/temporalio\/samples-typescript\/blob\/cb617abb3e0f58a6911c66615f9c2c665e0307b0\/food-delivery\/packages\/workflows\/order.ts#L70\">packages\/workflows\/order.ts<\/a><\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>import { condition } from '@temporalio\/workflow'\n\nexport async function order(productId: number): Promise<void> {\n  \/\/ \u2026\n\n  try {\n    await chargeCustomer(product)\n  } catch (err) {\n    \/\/ \u2026\n  }\n\n  state = 'Paid'\n\n  const notPickedUpInTime = !(await condition(() => state === 'Picked up', '1 min'))\n  if (notPickedUpInTime) {\n    state = 'Refunding'\n    await refundAndNotify(\n      product,\n      '\u26a0\ufe0f No drivers were available to pick up your order. Your payment has been refunded.'\n    )\n    throw ApplicationFailure.create({ message: 'Not picked up in time' })\n  }\n<\/code><\/pre>\n<p><\/code><\/p>\n<p><code node=\"[object Object]\">await condition(() => state === 'Picked up', '1 min')<\/code> waits for up to 1 minute for the state to change to <code node=\"[object Object]\">Picked up<\/code>. If a minute goes by without it changing, it returns false, and we refund the customer. (Either we have very high standards for the speed of our chefs and delivery drivers, or we want the users of a demo app to be able to see all the failure modes ????.)<\/p>\n<h2>Delivery<\/h2>\n<p>Similarly, there\u2019s a <code node=\"[object Object]\">deliveredSignal<\/code> sent by the \u201cDeliver\u201d button, and if the driver doesn\u2019t complete delivery within a minute of pickup, the customer is refunded.<\/p>\n<p><a href=\"https:\/\/github.com\/temporalio\/samples-typescript\/blob\/cb617abb3e0f58a6911c66615f9c2c665e0307b0\/food-delivery\/packages\/workflows\/order.ts#L82-L89\">packages\/workflows\/order.ts<\/a><\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>export const deliveredSignal = defineSignal('delivered')\n\nexport async function order(productId: number): Promise<void> {\n  setHandler(deliveredSignal, () => {\n    if (state === 'Picked up') {\n      state = 'Delivered'\n      deliveredAt = new Date()\n    }\n  })\n\n  \/\/ \u2026\n\n  await sendPushNotification('???? Order picked up')\n\n  const notDeliveredInTime = !(await condition(() => state === 'Delivered', '1 min'))\n  if (notDeliveredInTime) {\n    state = 'Refunding'\n    await refundAndNotify(product, '\u26a0\ufe0f Your driver was unable to deliver your order. Your payment has been refunded.')\n    throw ApplicationFailure.create({ message: 'Not delivered in time' })\n  }\n\n  await sendPushNotification('\u2705 Order delivered!')\n<\/code><\/pre>\n<p><\/code><\/p>\n<p>If delivery was successful, the function waits for a minute for the customer to eat their meal and asks them to rate their experience.<\/p>\n<p><code node=\"[object Object]\"><\/p>\n<pre><code>  await sleep('1 min') \/\/ this could also be hours or even months\n\n  await sendPushNotification(`\u270d\ufe0f Rate your meal. How was the ${product.name.toLowerCase()}?`)\n}\n<\/code><\/pre>\n<p><\/code><\/p>\n<p>After the final push notification, the order function\u2019s execution ends, and the Workflow Execution completes successfully. Even though the function has completed, we can still send Queries, since Temporal has the final state of the function saved. And we can test that by refreshing the page a minute after an order has been delivered: the <code node=\"[object Object]\">getStatusQuery<\/code> still works and \u201cDelivered\u201d is shown as the status:<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/images.ctfassets.net\/0uuz8ydxyd9p\/2IAEpuDtPpp8NNCjeswM2h\/b783df7d5c1554a50aab7a4b427b89f3\/delivered.png\" alt=\"Poke order with Status: Delivered\"><\/p>\n<h2>Summary<\/h2>\n<p>We\u2019ve seen how a multi-step order flow can be implemented with a single durable function. The function is guaranteed to complete in the presence of failures, including:<\/p>\n<ul>\n<li>Temporary issues with the network, data stores, or downstream services<\/li>\n<li>The process running the function failing<\/li>\n<li>The underlying Temporal services or database going down<\/li>\n<\/ul>\n<p>This addressed a number of distributed systems concerns for us, and meant that:<\/p>\n<ul>\n<li>We could use local variables instead of saving state to a database.<\/li>\n<li>We didn\u2019t need to set timers in a database for application logic like canceling an order that takes too long or for the built-in functionality of retrying and timing out transient functions like <code node=\"[object Object]\">chargeCustomer<\/code>.<\/li>\n<li>We didn\u2019t need to set up a job queue that workers polled, either for progressing to the next step or picking up unfinished tasks that were dropped by failed processes.<\/li>\n<\/ul>\n<p>In the next post, we\u2019ll look at more of the delivery app\u2019s code and learn how Temporal is able to provide us with durable execution. To get notified when it comes out, you can follow us on <a href=\"https:\/\/twitter.com\/temporalio\">Twitter<\/a> or <a href=\"https:\/\/www.linkedin.com\/company\/temporal-technologies\/posts\/\">LinkedIn<\/a>.<\/p>\n<p>If you have any questions, I would be happy to help! Temporal\u2019s mission is helping developers, and I also personally find joy in it ????. I\u2019m <a href=\"https:\/\/twitter.com\/lorendsr\">@lorendsr<\/a> on Twitter, I answer (and upvote ????) any StackOverflow questions tagged with <a href=\"https:\/\/stackoverflow.com\/questions\/ask?tags=temporal-typescript\"><code node=\"[object Object]\">temporal-typescript<\/code><\/a>, and am @Loren on the <a href=\"https:\/\/t.mp\/slack\">community Slack<\/a> ????.<\/p>\n<h2>Learn more<\/h2>\n<p>To learn more, I recommend these resources:<\/p>\n<ul>\n<li>Video: <a href=\"https:\/\/youtu.be\/6lSuDRRFgyY\">Intro to Temporal and using the TypeScript SDK<\/a><\/li>\n<li>Some <a href=\"https:\/\/temporal.io\/use-cases\">common use cases<\/a><\/li>\n<li>TypeScript SDK docs: <a href=\"https:\/\/t.mp\/ts\">t.mp\/ts<\/a><\/li>\n<li>TypeScript API reference: <a href=\"https:\/\/t.mp\/ts-api\">t.mp\/ts-api<\/a><\/li>\n<li><a href=\"https:\/\/learn.temporal.io\/tutorials\/typescript\/\">TypeScript tutorials<\/a><\/li>\n<\/ul>\n<p>More blog posts about our TypeScript SDK:<\/p>\n<ul>\n<li><a href=\"https:\/\/temporal.io\/blog\/using-temporal-as-a-node-task-queue\">Using Temporal as a Node.js Task Queue<\/a><\/li>\n<li><a href=\"https:\/\/temporal.io\/blog\/caching-api-requests-with-long-lived-workflows\">Caching API Requests with Long-Lived Workflows<\/a><\/li>\n<li><a href=\"https:\/\/temporal.io\/blog\/temporal-rest\">Express middleware that creates a REST API for your Workflows<\/a><\/li>\n<li><a href=\"https:\/\/temporal.io\/blog\/typescript-1-0-0\">1.0.0 release of the TS SDK<\/a><\/li>\n<li><a href=\"https:\/\/temporal.io\/blog\/intro-to-isolated-vm\">How we use V8 isolates to enforce Workflow determinism<\/a><\/li>\n<\/ul>\n<p><em>Thanks to Jessica West, Brian Hogan, Amelia Mango, and Jim Walker for reading drafts of this post.<\/em><\/p>\n<\/div>\n<p><a href=\"https:\/\/temporal.io\/blog\/building-reliable-distributed-systems-in-node\" class=\"button purchase\" rel=\"nofollow noopener\" target=\"_blank\">Read More<\/a><br \/>\n Stephania Kazmierczak<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post introduces the concept of durable execution, which is used by Stripe, Netflix, Coinbase, Snap, and many others to solve a wide range of problems in distributed systems. Then it shows how simple it is to write durable code using our TypeScript\/JavaScript SDK. Distributed systems When building a request-response monolith backed by a single<\/p>\n","protected":false},"author":1,"featured_media":599740,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4299,26175,46],"tags":[],"class_list":{"0":"post-599739","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-building","8":"category-reliable","9":"category-technology"},"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/newsycanuse.com\/index.php\/wp-json\/wp\/v2\/posts\/599739","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/newsycanuse.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/newsycanuse.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/newsycanuse.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/newsycanuse.com\/index.php\/wp-json\/wp\/v2\/comments?post=599739"}],"version-history":[{"count":0,"href":"https:\/\/newsycanuse.com\/index.php\/wp-json\/wp\/v2\/posts\/599739\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/newsycanuse.com\/index.php\/wp-json\/wp\/v2\/media\/599740"}],"wp:attachment":[{"href":"https:\/\/newsycanuse.com\/index.php\/wp-json\/wp\/v2\/media?parent=599739"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/newsycanuse.com\/index.php\/wp-json\/wp\/v2\/categories?post=599739"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/newsycanuse.com\/index.php\/wp-json\/wp\/v2\/tags?post=599739"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}