Skip to content

Commit a4412ae

Browse files
committed
docs: add effective-practices supplemental documentation
1 parent 756643e commit a4412ae

File tree

2 files changed

+236
-1
lines changed

2 files changed

+236
-1
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# Effective Practices
2+
3+
This section contains general recommendations from the AWS SDK for JavaScript team.
4+
5+
The code examples are using imports from the AWS SDK S3 client.
6+
7+
```ts
8+
import type { S3ClientConfig, S3ClientResolvedConfig } from "@aws-sdk/client-s3";
9+
import {
10+
CreateBucketCommand,
11+
ListObjectsV2Command,
12+
PutObjectCommand,
13+
GetObjectCommand,
14+
ListDirectoryBucketsCommand,
15+
S3Client,
16+
} from "@aws-sdk/client-s3";
17+
```
18+
19+
## Table of Contents
20+
21+
<!-- TOC start (generated with https://github.com/derlin/bitdowntoc) -->
22+
23+
- [(1) Minimize creating new copies of AWS SDK Clients for multiple operation calls](#1-minimize-creating-new-copies-of-aws-sdk-clients-for-multiple-operation-calls)
24+
- [(2) Avoid reading or mutating the AWS SDK client configuration object after instantiating the client](#2-avoid-reading-or-mutating-the-aws-sdk-client-configuration-object-after-instantiating-the-client)
25+
- [Incompatible write](#incompatible-write)
26+
- [Incompatible read](#incompatible-read)
27+
- [Recommended alternatives](#recommended-alternatives)
28+
- [(3) Always read streaming responses to completion or discard them](#3-always-read-streaming-responses-to-completion-or-discard-them)
29+
30+
<!-- TOC end -->
31+
32+
### (1) Minimize creating new copies of AWS SDK Clients for multiple operation calls
33+
34+
The following example creates a client prior to making a request.
35+
36+
```ts
37+
// ⚠️
38+
for (const item of items) {
39+
const client = new S3Client({
40+
region,
41+
credentials,
42+
});
43+
await client.send(new PutObjectCommand(item));
44+
}
45+
```
46+
47+
In cases where the operations being called are to the same client configuration, i.e. the same region
48+
and with the same credentials, creating a new client is unnecessary. It adds additional work for the calling program,
49+
since the client may need to re-compute credentials, endpoints, and other components needed to make a request.
50+
51+
Our recommended way is to create one client per set of credentials and region, and reuse it for
52+
multiple commands.
53+
54+
```ts
55+
//
56+
const client = new S3Client({
57+
region,
58+
credentials,
59+
});
60+
61+
await client.send(new CreateBucketCommand({ Bucket }));
62+
63+
for (const item of items) {
64+
await client.send(new PutObjectCommand(item));
65+
}
66+
67+
const objects = await client.send(new ListObjectsV2Command({ Bucket }));
68+
```
69+
70+
If you find a need to create a new client because of capacity issues with a single client,
71+
see [parallel workloads in Node.js](./performance/parallel-workloads-node-js.md).
72+
73+
### (2) Avoid reading or mutating the AWS SDK client configuration object after instantiating the client
74+
75+
Although the TypeScript interface of AWS SDK Clients contain a `public readonly config` field,
76+
we discourage making use of this field in any way, including reading and writing values.
77+
78+
For backwards compatibility, we cannot make the field `private` or recursively `readonly`, but we'll explain
79+
below why the field should be ignored.
80+
81+
#### Incompatible write
82+
83+
```ts
84+
// ⚠️
85+
import { ListObjectsV2Command } from "@aws-sdk/client-s3";
86+
87+
const client = new S3Client({
88+
region,
89+
credentials,
90+
});
91+
92+
// ⚠️ incompatible mutation, will cause an error to be thrown later when calling operations.
93+
client.config.region = "us-west-2";
94+
95+
await client.send(new ListObjectsV2Command({ Bucket }));
96+
// ⚠️ Uncaught TypeError: config.region is not a function
97+
```
98+
99+
The `client.config` field is not a direct reference to the object that you pass into the S3Client constructor.
100+
It undergoes a process we call config resolution, in which many input fields are wrapped in normalizing functions.
101+
102+
Whereas the constructor input has the type `S3ClientConfig`, the `client.config` object has the type
103+
`S3ClientResolvedConfig`, which is substantially transformed.
104+
105+
For example, a `region` string of `"us-east-1"` becomes a function, or "provider", in the form of:
106+
107+
```ts
108+
config.region = async () => "us-east-1";
109+
```
110+
111+
Even more complex transforms are applied to config fields such as `credentials` and `signer`. Therefore, many
112+
`config` values which would be valid as constructor inputs cannot be written to the `client.config` object.
113+
114+
#### Incompatible read
115+
116+
Another example is attempting to determine an AWS service endpoint by using a client configured with a region.
117+
118+
```ts
119+
// ⚠️
120+
const client = new S3Client({
121+
region,
122+
credentials,
123+
});
124+
125+
// ⚠️ incompatible reading, will throw an error: Uncaught TypeError: client.config.endpoint is not a function
126+
const endpoint = await client.config.endpoint();
127+
```
128+
129+
This may seem initially reasonable, since each regional AWS service typically has a set of endpoints of the pattern
130+
`{service}.{region}.amazonaws.com`. However, AWS services can configure endpoints that differ based on many factors,
131+
including down to the distinct operation being called and its inputs. Therefore, the canonical endpoint cannot be
132+
accurately given before the operation and operation inputs are known.
133+
134+
For example, the AWS SDK's S3 client uses the bucket name in the hostname, an operation level parameter, and the
135+
DynamoDB client may try to use the account ID in the hostname, a value that is not known until credentials are resolved
136+
during the first request. Endpoint variations are not limited to these examples.
137+
138+
#### Recommended alternatives
139+
140+
If you need to change regions, instantiate additional clients per region. They can share credentials to avoid duplicate
141+
credential resolution calls.
142+
143+
```ts
144+
//
145+
import { fromTemporaryCredentials } from "@aws-sdk/credential-providers";
146+
147+
const credentialProvider = fromTemporaryCredentials();
148+
149+
const s3 = {
150+
east: new S3Client({ region: "us-east-1", credentials: credentialProvider }),
151+
west: new S3Client({ region: "us-west-2", credentials: credentialProvider }),
152+
};
153+
154+
const directoryEast = await s3.east.send(new ListDirectoryBucketsCommand());
155+
const directoryWest = await s3.west.send(new ListDirectoryBucketsCommand());
156+
```
157+
158+
If you want to know the resolved endpoint for an SDK operation, use the following helper function.
159+
You must provide the same Command constructor and input parameters as you would call, since those values are involved in
160+
determining the endpoint.
161+
162+
```ts
163+
import { getEndpointFromInstructions } from "@smithy/middleware-endpoint";
164+
165+
//
166+
const operationParams = {
167+
Bucket,
168+
Key,
169+
};
170+
const config = {
171+
region: "us-west-2",
172+
useDualstackEndpoint: false,
173+
useFipsEndpoint: false,
174+
};
175+
const client = new S3Client(config);
176+
177+
const endpoint = await getEndpointFromInstructions(operationParams, GetObjectCommand, config, {
178+
// logger: console,
179+
});
180+
181+
console.log(endpoint.url.toString());
182+
```
183+
184+
### (3) Always read streaming responses to completion or discard them
185+
186+
Some operations, the most common of which
187+
is [GetObjectCommand](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/command/GetObjectCommand/),
188+
return a byte stream.
189+
190+
Although awaiting such a request will return an HTTP status code and response headers,
191+
192+
```ts
193+
const getObjectResponse = client.send(GetObjectCommand({ Bucket, Key }));
194+
195+
console.log(getObjectResponse.$metadata.httpStatusCode);
196+
// ⚠️ byte stream is unhandled, leaving a socket in use.
197+
```
198+
199+
the request is incomplete. The connection will remain open until the byte stream, or payload, is read or discarded.
200+
Not doing so will leave the connection open, and in Node.js this can lead to a condition we call socket exhaustion. In
201+
the worst cases this can cause your application to slow, leak memory, and/or deadlock.
202+
203+
We cannot automatically handle this for you. Since handling of the byte stream is application-dependent, we cannot infer
204+
your application's intent. In some cases there is an intentional delay in reading the byte stream, so we will not throw
205+
an Error if the stream is not immediately read.
206+
207+
To handle the byte stream, use one of our built-in collection methods, pipe it somewhere such as a file or another S3
208+
destination, or discard the stream.
209+
210+
```ts
211+
// Caution: only do one of the following, because streams can only be read once:
212+
if (case1) {
213+
// ✅ buffer the stream
214+
const bytes = await getObjectResponse.Body.transformToByteArray();
215+
} else if (case2) {
216+
// ✅ pipe the stream elsewhere/c
217+
await s3Client.send(
218+
new PutObjectCommand({
219+
Bucket,
220+
Key,
221+
Body: getObjectResponse.Body,
222+
})
223+
);
224+
} else {
225+
// ✅ discard the stream
226+
// because our stream type varies depending on your runtime platform,
227+
// .destroy() is used for Node.js Readable.
228+
// .cancel() is used for Web Streams' ReadableStream.
229+
await(getObjectResponse.destroy?.() ?? getObjectResponse.cancel?.());
230+
}
231+
```

supplemental-docs/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This folder contains handwritten documentation from the developers of this SDK to supplement the programmatically generated documentation.
44

5-
#### [Clients](./CLIENTS.md)
5+
### [Clients](./CLIENTS.md)
66

77
Information about initializing an SDK client and common configurable constructor parameters.
88

@@ -19,6 +19,10 @@ Best practices for working within AWS Lambda using the AWS SDK for JavaScript (v
1919
Details what steps the AWS SDK team has taken to optimize performance of the SDK,
2020
and includes tips for configuring the SDK to run efficiently.
2121

22+
#### [Effective Practices](./EFFECTIVE_PRACTICES.md)
23+
24+
General recommended practices for working with AWS SDK clients.
25+
2226
#### [TypeScript](./TYPESCRIPT.md)
2327

2428
TypeScript tips & FAQ related to this project.

0 commit comments

Comments
 (0)