From 203dc09e5d5bc17cf464aa0f88bacf9deb5e5253 Mon Sep 17 00:00:00 2001 From: Peter Kraft Date: Wed, 19 Jun 2024 10:25:35 -0700 Subject: [PATCH] Document Kafka and HTTP Changes (#138) --- docs/api-reference/decorators.md | 60 ++++++++++++++++++------- docs/tutorials/http-serving-tutorial.md | 30 +++++++++---- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/docs/api-reference/decorators.md b/docs/api-reference/decorators.md index 3b4d133b..32cac11e 100644 --- a/docs/api-reference/decorators.md +++ b/docs/api-reference/decorators.md @@ -155,7 +155,7 @@ export interface CommunicatorConfig { ### HTTP API Registration Decorators #### `@GetApi` -Associates a function with an HTTP URL accessed with GET. +Associates a function with an HTTP URL accessed via GET. ```typescript @GetApi("/hello") @@ -164,33 +164,59 @@ static async hello(_ctx: HandlerContext) { } ``` -The `@GetApi` decorator can be combined with `@Transaction` or `@Workflow` to serve transactions and workflows via HTTP. +The `@GetApi` decorator can be combined with [`@Transaction`](#transaction), [`@Workflow`](#workflow), or [`@Communicator`](#communicator) to serve those operations via HTTP. It can also be used by itself in a [DBOS handler function](../tutorials/http-serving-tutorial.md#handlers). -The first argument to a handler function must be a [`HandlerContext`](contexts.md#handlercontext), which contains more details about the incoming request, and provides the ability to invoke workflows and transactions. +The first argument to a handler function must be a [`HandlerContext`](contexts.md#handlercontext), which contains more details about the incoming request and allows invoking workflows, transactions, and communicators. Endpoint paths may have placeholders, which are parts of the URL mapped to function arguments. These are represented by a section of the path prefixed with a `:`. ```typescript -@GetApi("/post/:id") -static async getPost(ctx: TransactionContext, @ArgSource(ArgSources.URL) id: string) { - ... +@GetApi("/:id") +static async exampleGet(ctxt: TransactionContext, id: string) { + ctxt.logger.info(`${id} is parsed from the URL path parameter`) } ``` #### `@PostApi` -Associates a function with an endpoint name, such as an HTTP URL accessed with POST. +Associates a function with an HTTP URL accessed via POST. Analogous to [`@GetApi`](#getapi), but may parse arguments from a request body. ```typescript -@PostApi("/testpost") - static async testpost(_ctx: HandlerContext, name: string) { - return `hello ${name}`; +@PostApi("/:id") + static async examplePost(ctxt: HandlerContext, id: string, name: string) { + ctxt.logger.info(`${id} is parsed from the URL path parameter, ${name} is parsed from the request body`) } ``` -The `@PostApi` decorator can be combined with `@Transaction` or `@Workflow` to serve transactions and workflows via HTTP. -It can also be used by itself in a [DBOS handler function](../tutorials/http-serving-tutorial.md#handlers). -The first argument to a handler function must be a [`HandlerContext`](contexts.md#handlercontext), which contains more details about the incoming request, and provides the ability to invoke workflows and transactions. +#### `@PutApi` +Associates a function with an HTTP URL accessed via PUT. Analogous to [`@GetApi`](#getapi), but may parse arguments from a request body. + +```typescript +@PutApi("/:id") + static async examplePut(ctxt: HandlerContext, id: string, name: string) { + ctxt.logger.info(`${id} is parsed from the URL path parameter, ${name} is parsed from the request body`) +} +``` + +#### `@PatchApi` +Associates a function with an HTTP URL accessed via PATCH. Analogous to [`@GetApi`](#getapi), but may parse arguments from a request body. + +```typescript +@PatchApi("/:id") + static async examplePatch(ctxt: HandlerContext, id: string, name: string) { + ctxt.logger.info(`${id} is parsed from the URL path parameter, ${name} is parsed from the request body`) +} +``` + +#### `@DeleteApi` +Associates a function with an HTTP URL accessed via DELETE. Analogous to [`@GetApi`](#getapi). + +```typescript +@DeleteApi("/:id") +static async exampleDelete(ctxt: TransactionContext, id: string) { + ctxt.logger.info(`${id} is parsed from the URL path parameter`) +} +``` #### `@ArgSource` Indicates where a function argument is to be sourced, when it could come from more than one place. @@ -207,7 +233,7 @@ static async testWorkflow(wfCtxt: WorkflowContext, @ArgSource(ArgSources.QUERY) ``` The `@ArgSource` decorator takes one of the following values of `ArgSources`: -- `DEFAULT`: The default value. For GET requests, this means searching for query parameters; for POST requestsn, searching the request body. +- `DEFAULT`: The default value. For GET requests, this means searching for query parameters; for POST requests, searching the request body. - `BODY`: Indicates to search the parameter in the request body. - `QUERY`: Indicates to search the parameter in the URL query string. - `URL`: Indicates to search the parameter in the endpoint path (requires a path placeholder). @@ -500,9 +526,9 @@ Class-level decorator defining a Kafka configuration to use in all class methods Takes in a [KafkaJS configuration object](https://kafka.js.org/docs/configuration). -#### `@KafkaConsume(topic: string, consumerConfig?: ConsumerConfig)` {#kafka-consume} -Runs a transaction or workflow exactly-once for each message received on the specified topic. -Takes in a Kafka topic (required) and a [KafkaJS consumer configuration](https://kafka.js.org/docs/consuming#options) (optional). +#### `@KafkaConsume(topic: string | RegExp | Array, consumerConfig?: ConsumerConfig)` {#kafka-consume} +Runs a transaction or workflow exactly-once for each message received on the specified topic(s). +Takes in a Kafka topic or list of Kafka topics (required) and a [KafkaJS consumer configuration](https://kafka.js.org/docs/consuming#options) (optional). Requires class to be decorated with [`@Kafka`](#kafka). The decorated method must take as input a Kafka topic, partition, and message as in the example below: diff --git a/docs/tutorials/http-serving-tutorial.md b/docs/tutorials/http-serving-tutorial.md index 18ed55cc..6a55f1d9 100644 --- a/docs/tutorials/http-serving-tutorial.md +++ b/docs/tutorials/http-serving-tutorial.md @@ -28,7 +28,7 @@ static async clearTransaction(ctxt: TransactionContext, @ArgSource(ArgSour } ``` -DBOS currently supports two endpoint decorators, [`GetApi`](../api-reference/decorators#getapi) (HTTP `GET`) and [`PostApi`](../api-reference/decorators#postapi) (HTTP `POST`). +DBOS provides endpoint decorators for all HTTP verbs used in APIs: [`@GetApi`](../api-reference/decorators#getapi), [`@PostApi`](../api-reference/decorators#postapi), [`@PutApi`](../api-reference/decorators.md#putapi), [`@PatchApi`](../api-reference/decorators.md#patchapi), and [`@DeleteApi`](../api-reference/decorators.md#deleteapi). Each associates a function with an HTTP URL. :::info @@ -44,18 +44,16 @@ When you run an locally app with `npx dbos start`, we manage the HTTP server for A function annotated with an endpoint decorator but no other decorators is called a _handler_ and must take a [`HandlerContext`](../api-reference/contexts#handlercontext) as its first argument, like in the first example above. Handlers can [invoke](../api-reference/contexts#handlerctxtinvoke) other functions and directly access HTTP requests and responses. However, DBOS makes no guarantees about handler execution: if a handler fails, it is not automatically retried. -You should use handlers when you need to access HTTP responses directly or when you are writing a lightweight task that does not need the strong guarantees of transactions and workflows. +You should use handlers when you need to access HTTP requests or responses directly or when you are writing a lightweight task that does not need the strong guarantees of transactions and workflows. ### Inputs and HTTP Requests -Any DBOS method invoked via HTTP request can access the raw request from its `context.request` field. - -When a function has arguments other than its context (e.g., `name: String` in the snippets above), DBOS automatically parses them from the HTTP request, and returns an error to the client if arguments were not provided. +When a function has arguments other than its context (e.g., `name` or `user` in the snippets above), DBOS automatically parses them from the HTTP request, and returns an error to the client if arguments are not provided. Arguments are parsed from three places by default: -1. For GET requests, from a URL query string parameter. -2. For POST requests, from an HTTP body field. +1. For `GET` and `DELETE` requests, from a URL query string parameter. +2. For `POST`, `PUT`, and `PATCH` requests, from an HTTP body field. 3. From a URL path parameter, if there are placeholders specified in the decorated URL. In all cases, the parameter name must match the function argument name (unless [`@ArgName`](../api-reference/decorators#argname) is specified). In the first snippet above, `/clear/:name` matches `name: string`. @@ -65,9 +63,25 @@ For example, in the `greetingEndpoint` snippet above the `@ArgSource(ArgSources. By default, DBOS automatically validates parsed inputs, throwing an error if a function is missing required inputs or if the input received is of a different type than specified in the method signature. Validation can be turned off at the class level using [`@DefaultArgOptional`](../api-reference/decorators#defaultargoptional) or controlled at the parameter level using [`@ArgRequired`](../api-reference/decorators#argrequired) and [`@ArgOptional`](../api-reference/decorators#argoptional). +Additionally, any DBOS method invoked via HTTP request can access request information from its `context.request` field. This returns the following information: + +```typescript +interface HTTPRequest { + readonly headers?: IncomingHttpHeaders; // A node's http.IncomingHttpHeaders object. + readonly rawHeaders?: string[]; // Raw headers. + readonly params?: unknown; // Parsed path parameters from the URL. + readonly body?: unknown; // parsed HTTP body as an object. + readonly rawBody?: string; // Unparsed raw HTTP body string. + readonly query?: ParsedUrlQuery; // Parsed query string. + readonly querystring?: string; // Unparsed raw query string. + readonly url?: string; // Request URL. + readonly ip?: string; // Request remote address. +} +``` + ### Outputs and HTTP Responses -By default, if a function invoked via HTTP request returns successfuly, its return value is sent in the HTTP response body with status code `200` (or `204` if nothing is returned). +By default, if a function invoked via HTTP request returns successfully, its return value is sent in the HTTP response body with status code `200` (or `204` if nothing is returned). If the function throws an exception, the error message is sent in the response body with a `400` or `500` status code. If the error contains a `status` field, the handler uses that status code instead.