diff --git a/.changes/next-release/documentation-b6f64a8421e29dc8eea3139ed59a6440f6fed83d.json b/.changes/next-release/documentation-b6f64a8421e29dc8eea3139ed59a6440f6fed83d.json new file mode 100644 index 00000000000..35307b417ae --- /dev/null +++ b/.changes/next-release/documentation-b6f64a8421e29dc8eea3139ed59a6440f6fed83d.json @@ -0,0 +1,7 @@ +{ + "type": "documentation", + "description": "Added a section to discuss recommendations for implementing Smithy clients. Currently this section only includes information about HTTP interfaces, but it will expand over time to cover more topics related to implementing clients.", + "pull_requests": [ + "[#2868](https://github.com/smithy-lang/smithy/pull/2868)" + ] +} diff --git a/docs/source-2.0/guides/client-guidance/application-protocols/http.md b/docs/source-2.0/guides/client-guidance/application-protocols/http.md new file mode 100644 index 00000000000..ceac08f4a0e --- /dev/null +++ b/docs/source-2.0/guides/client-guidance/application-protocols/http.md @@ -0,0 +1,189 @@ +# HTTP + +HTTP is the most common application protocol used by Smithy clients. This guide +provides advice on how to integrate and expose HTTP clients. + +## Configuration + +Smithy clients should allow their users to configure the HTTP client that the +Smithy client uses to send requests. Users may want to change the client used to +something that has different performance characteristics or support for features +that they need. + +## HTTP interfaces + +Smithy clients should provide interfaces for HTTP clients that standardize how +the Smithy client interacts with the HTTP client, allowing any HTTP client to be +used as long as it implements the interface. + +### Clients + +It is recommended to make HTTP clients implementations of +[`ClientTransport`](#transport-clients). + +```java +public interface HttpClient implements ClientTransport { + HttpResponse send(Context context, HttpRequest request); +} +``` + +#### Context + +HTTP clients don't have many common context parameters, but they should check +the context for a request timeout setting and use it if it's present. + +```java +/** + * This utility class holds shared context key definitions that are useful + * for HTTP implementations. + */ +public final class HttpContext { + public static final Context.Key HTTP_REQUEST_TIMEOUT = Context.key("HTTP.RequestTimeout"); + + // This is a utility class that is not intended to be constructed, so it + // has a private constructor. + private HttpContext() {} +} +``` + +### Requests and Responses + +{rfc}`9110` discusses HTTP requests and responses collectively as "messages", +and it can be useful to encode their shared features in a shared interface. + +```java +public interface HttpMessage { + /** + * Get the headers of the message. + * + * @return headers. + */ + HttpFields headers(); + + /** + * Get the body of the message, or null. + * + * @return the message body or null. + */ + DataStream body(); +} +``` + +Requests introduce the `method` and `uri` properties. + +```java +public interface HttpRequest extends HttpMessage { + /** + * Get the method of the request. + * + * @return the method. + */ + String method(); + + /** + * Get the URI of the request. + * + * @return the request URI. + */ + URI uri(); +} +``` + +Responses introduce a status code. + +```java +public interface HttpResponse extends HttpMessage { + /** + * Get the status code of the response. + * + * @return the status code. + */ + int statusCode(); +} +``` + +### Fields + +Most users who have interacted with HTTP directly are familiar with the concept +of headers. Headers were originally introduced in HTTP/1.0 and, since then, the +concept of key/value pairs has expanded to include trailers and other arbitrary +metadata. As of {rfc}`9110`, these key/value pairs are exclusively referred to +as {rfc}`fields <9110#section-5>`. + +When designing HTTP interfaces for Smithy clients, be careful to understand +field semantics. In particular, it is important to understand that field keys +are case-insensitive and may appear more than once in an HTTP message. Since +field keys may appear more than once, it is recommended that they are +represented as an iterable collection of pairs or as a map whose value type is a +list. This allows protocol implementations to safely handle joining and +splitting. + +It is recommended to have utilities to convert fields to and from maps. Fields +are often conceptualized as maps, so providing these utilities allows users to +access fields in a more comfortable way without sacrificing correctness. + +```java +public interface HttpFields extends Iterable>> { + /** + * Create an HttpFields instance from a map. + * + * @param fields Field map to use as a data source. + * @return the created fields. + */ + static HttpFields of(Map> fields) { + // This constructs a theoretical default implementation of the + // HttpFields interface that creates an unmodifiable copy of the given + // map. + return fields.isEmpty() ? UnmodifiableHttpFields.EMPTY : new UnmodifiableHttpFields(fields); + } + + /** + * Convert the HttpFields to a map. + * + * @return the fields as a map. + */ + Map> toMap(); + + /** + * Check if the given field is case-insensitively present. + * + * @param name Name of the field to check. + * @return true if the field is present. + */ + default boolean containsField(String name) { + return !getAllValues(name).isEmpty(); + } + + /** + * Get the first field value of a specific field by case-insensitive name. + * + * Smithy clients know whether a given field should have a single value or + * a list value. This helper method simplifies usage for fields with a + * single value. + * + * @param name Name of the field to get. + * @return the matching field value, or null if not found. + */ + default String getFirstValue(String name) { + var list = getAllValues(name); + return list.isEmpty() ? null : list.get(0); + } + + /** + * Get the values of a specific field by case-insensitive name. + * + * @param name Name of the field to get the values of. + * @return the values of the field, or an empty list. + */ + List getAllValues(String name); +} +``` + +#### Implementation recommendations + +It is not recommended to automatically attempt to join values for a given field +key at the HTTP layer. {rfc}`9110#section-5` allows field values to be joined +with a comma, but doing so automatically can introduce data corruption if one of +the field values already includes a comma. {rfc}`Section 5.6 <9110#section-5.6>` +includes productions that can help to handle those edge cases, but whether they +are used or not is up to the protocol definition. diff --git a/docs/source-2.0/guides/client-guidance/application-protocols/index.md b/docs/source-2.0/guides/client-guidance/application-protocols/index.md new file mode 100644 index 00000000000..ee83f39b697 --- /dev/null +++ b/docs/source-2.0/guides/client-guidance/application-protocols/index.md @@ -0,0 +1,40 @@ +# Application Protocols + +This section provides guidance on how to implement and integrate different +application protocols into a client. + +Application protocols define how operations are transmitted over a network. The +most commonly used application protocol by Smithy clients is HTTP, but other +protocols like MQTT may also be used by Smithy clients and servers. When +designing a client, be careful to not couple any components to a particular +application protocol unless they interact explicitly with that protocol. For +example, an HTTP request serializer inherently needs to be coupled to HTTP, but +a JSON serializer does not. + +(transport-clients)= +## Transport clients + +Smithy clients and services have a common access pattern regardless of what +application protocol is being used: a client sends requests to a server and +receives responses. This can be represented by a simple interface: + +```java +public interface ClientTransport { + ResponseT send(Context context, RequestT request); +} +``` + +In addition to the request, it is recommended to introduce a context parameter +to the `send` method to allow the client to be configured for each request. It +is recommended to make this a generic context object rather than a type with +fixed properties. Leaving it unrestricted allows context to be passed into +custom `ClientTransport` implementations that may not be relevant to other +implementations. + +## Navigation + +```{toctree} +:maxdepth: 1 + +http +``` \ No newline at end of file diff --git a/docs/source-2.0/guides/client-guidance/index.md b/docs/source-2.0/guides/client-guidance/index.md new file mode 100644 index 00000000000..cb5043ba83e --- /dev/null +++ b/docs/source-2.0/guides/client-guidance/index.md @@ -0,0 +1,61 @@ +# Smithy Client Guidance + +This guide provides advice on how to build clients to interact with Smithy +services. In particular, it provides advice on how to design the generated +client and the components that make it up. + +While topics in this guide may briefly discuss code generation, this guide +does not describe how to build a code generator. To learn how to build a code +generator, see +[Creating a Code Generator](project:../building-codegen/index.rst). + +## Goals of this guide + +- Provide guidance on how to design client components. +- Provide guidance on how to avoid coupling components to particular transport + protocols or to specific features. +- Provide guidance on how to make clients extensible. + +## Non-goals of this guide + +- Provide guidance on how to design and implement code generators. +- Force specific implementation details. This guide is non-normative, feel free + to deviate from its advice. + +## Tenets for Smithy clients + +Smithy clients should follow these tenets: + +1. **Smithy implementations adhere to the spec**. The Smithy spec and model are + the contract between clients, servers, and other implementations. A Smithy + client written in any programming language should be able to connect to a + Smithy server written in any programming language without either having to + care about the programming language used to implement the other. +2. **Smithy clients should be familiar to developers**. Language idioms and + developer experience factor into how developers and companies choose between + Smithy and alternatives. +3. **Components are preferred over monoliths**. Components should be modular and + composable. They should have clear boundaries: a client that uses an AWS + protocol is not required to use AWS credentials, for example. +4. **Smithy client code should prioritize maintainability by limiting public + interfaces**. Smithy clients should limit the dependencies they take on. They + shouldn't expose overly open interfaces that hinder the ability to evolve the + code base. +5. **No implementation stands alone**. Test cases, protocol tests, code fixes, + and missing abstractions have a greater impact if every Smithy implementation + can use them rather than just a single implementation. +6. **Service teams don't need to know the details of every client that exists or + will ever exist**. When modeling a service, service teams only need to + consider if the model is a valid Smithy model; the constraints of any + particular programming language should not be a concern when modeling a + service. Smithy is meant to work with any number of languages, and it is an + untenable task to attempt to bubble up every constraint, reserved word, or + other limitation to modelers. + +## Navigation + +```{toctree} +:maxdepth: 1 + +application-protocols/index +``` \ No newline at end of file diff --git a/docs/source-2.0/guides/index.rst b/docs/source-2.0/guides/index.rst index a4521431168..150249bd11e 100644 --- a/docs/source-2.0/guides/index.rst +++ b/docs/source-2.0/guides/index.rst @@ -15,4 +15,5 @@ Guides style-guide model-translations/index building-codegen/index + client-guidance/index glossary