From c1bd5613856e34152053324699200f461caa18f6 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Mon, 29 Jul 2024 08:25:18 -0700 Subject: [PATCH 01/16] add doc autobuild and dev target --- docs/Makefile | 6 +++++- docs/requirements.txt | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/Makefile b/docs/Makefile index 0f1ddfa9738..1401abd9f50 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -4,9 +4,10 @@ SPHINXBUILD = python3 -msphinx SPHINXPROJ = Smithy SHELL := /bin/bash -install: +install: requirements.txt python3 -m venv build/venv source build/venv/bin/activate && pip3 install -r requirements.txt . && pip3 install -e . + touch install clean: -rm -rf build/* @@ -28,4 +29,7 @@ merge-versions: openhtml: open "build/html/index.html" +dev: install + @source build/venv/bin/activate && sphinx-autobuild "source-2.0" "build/2.0" $(SPHINXOPTS) $(O) + .PHONY: Makefile clean diff --git a/docs/requirements.txt b/docs/requirements.txt index 9e7b654c021..e50794f6706 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -10,3 +10,4 @@ Sphinx-Substitution-Extensions==2022.2.16 sphinx-tabs>=3.4.4 sphinx_copybutton==0.5.0 +sphinx-autobuild>=2024.4.16 From 6cca9e0d6f0aac287501eb08e53f274cec46b2bd Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Mon, 29 Jul 2024 08:32:25 -0700 Subject: [PATCH 02/16] add draft of e2e tutorial with smithy --- docs/source-2.0/index.rst | 1 + .../tutorials/full-stack-tutorial.rst | 793 ++++++++++++++++++ docs/source-2.0/tutorials/index.rst | 8 + 3 files changed, 802 insertions(+) create mode 100644 docs/source-2.0/tutorials/full-stack-tutorial.rst create mode 100644 docs/source-2.0/tutorials/index.rst diff --git a/docs/source-2.0/index.rst b/docs/source-2.0/index.rst index 7b5aafff260..6820afeb0d7 100644 --- a/docs/source-2.0/index.rst +++ b/docs/source-2.0/index.rst @@ -147,6 +147,7 @@ Read more spec/index trait-index guides/index + tutorials/index Additional specs aws/index ts-ssdk/index diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst new file mode 100644 index 00000000000..57e36dd2ab4 --- /dev/null +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -0,0 +1,793 @@ +====================== +Full Stack Application +====================== + +Overview +======== +In this tutorial, we’ll walk you through using Smithy in a full-stack application. This includes defining a model for +a simple coffee-shop service, generating TypeScript code for both the client and server, and implementing both a +front-end and back-end for the service. We won't assume that you're an expert in Smithy, but it may helpful to work +through the :doc:`../quickstart` before beginning this tutorial. + +Let's get started. + +Setting Up the Project +====================== +This application will consist of a 4 major components: + +1. The model +2. The server +3. The client +4. The web application + +-------------- +Pre-requisites +-------------- +To follow this tutorial, you'll need to install a few tools. + +* :doc:`Smithy CLI <../guides/smithy-cli/cli_installation>` +* `Node.js (>= 16) `_ and `yarn `_ + +------ +Set up +------ +Once you have these installed, the CLI has a useful command to easily bootstrap projects from +`the repository of example smithy projects `_. Execute the following +command to set up the initial project: + +.. code-block:: sh + :caption: ``/bin/sh`` + + smithy init -t full-stack-application + +You will now have the project in the ``full-stack-application`` directory. In this project, you will find a ``README`` +that contains important information about this project, like how it is laid out and how to build and run it. In this +tutorial, we will show exactly which commands to run and when. + +.. TODO: Provide the skeleton template or a git patch? + +Modeling Our Service +==================== +With the basic framework for the project established, let's walk through how to model our coffee service. + +As discussed above, the coffee service should: + +* provide a menu of coffees +* provide the ability to order a coffee +* provide the ability to check the status of an order + +------------------ +Adding a Service +------------------ +The service shape is the entry-point of our API, and is where we define the operations that our service exposes to a +consumer. With that information in mind, let's define the initial service shape without any operations: + +.. code-block:: smithy + :caption: ``main.smithy`` + + $version: "2.0" + + namespace com.example + + use aws.protocols#restJson1 + + /// Allows users to retrieve a menu, create a coffee order, and + /// and to view the status of their orders + @title("Coffee Shop Service") + @restJson1 + service CoffeeShop { + version: "2024-04-04" + } + +We apply the ``@restJson1`` protocol trait to the service to indicate that this service supports the +:doc:`../aws/protocols/aws-restjson1-protocol` created and used extensively by AWS. +Put simply, protocols define the rules and conventions for how data is serialized and deserialized when communicating +between client and server. Protocols are complex and need their own topic, so we will not discuss them at length in +this tutorial. + +--------------- +Defining Shapes +--------------- +Let's create basic representations of our data in Smithy. We can further refine our data model using traits. +Open the file titled ``coffee.smithy``. We will use it to write our definitions of coffee-related structures: + +.. code-block:: smithy + :caption: ``coffee.smithy`` + + $version: "2.0" + + namespace com.example + + /// A enum describing the types of coffees available + enum CoffeeType { + DRIP + POUR_OVER + LATTE + ESPRESSO + } + + /// A structure which defines a coffee item which can be ordered + structure CoffeeItem { + @required + type: CoffeeType + + @required + description: String + } + + /// A list of coffee items + list CoffeeItems { + member: CoffeeItem + } + +------------------- +Defining Operations +------------------- +With the shapes defined above, let's create an operation on our service for returning a menu to the consumer: + +.. code-block:: smithy + :caption: ``main.smithy`` + + ... + service CoffeeShop { + version: "2024-04-04" + operations: [ + GetMenu + ] + } + + /// Retrieve the menu + @http(method: "GET", uri: "/menu") + @readonly + operation GetMenu { + output := { + items: CoffeeItems + } + } + +We've named the operation ``GetMenu``. It does not define an input, and models its output as a structure with a single +member, ``items``, which contains ``CoffeeItems`` (a shape we defined above). With the ``restJson1`` protocol, a +potential response would be serialized like so: +.. TODO: Add info on http trait? + +.. code-block:: json + :caption: ``GetMenuResponse (json)`` + + { + "items": [ + { + "type": "LATTE", + "description": "A creamier, milk-based drink made with espresso" + } + ] + } + +------------------- +Representing Orders +------------------- +At this point, we still need to model the ordering functionality of our service. Let's create a new file, +``order.smithy``, which will hold definitions related to ordering. First, let's consider the following when +modeling an order: + +1. an order needs a unique identifier +2. an order needs to have a status (such as "in-progress" or "completed"), and +3. an order needs to hold the coffee information (``CoffeeType``) + +With these requirements in mind, let's create the underlying data model: + +.. code-block:: smithy + :caption: ``order.smithy`` + + $version: "2.0" + + namespace com.example + + /// A unique identifier to identify an order + @length(min: 1, max: 128) + @pattern("^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$") + string Uuid + + /// An enum describing the status of an order + enum OrderStatus { + IN_PROGRESS + COMPLETED + } + +A universally unique identifier (or `"UUID" `_) should be +more than sufficient for our service. The order status can be either ``IN_PROGRESS`` (when the order is submitted) or +``COMPLETED`` (when the order is ready). The information about what kind of coffee was order can be represented by the +``CoffeeType`` shape we defined earlier. + +Let's compose these shapes together to create our representation of an order: + +.. code-block:: smithy + :caption: ``order.smithy`` + + /// An Order, which has an id, a status, and the type of coffee ordered + structure Order { + id: Uuid, + coffeeType: CoffeeType, + status: OrderStatus + } + +We're making great progress. However, if we think about an order and it's `potential` set of operations +(`creating, reading, updating, deleting `_ an order), +there is tight relationship between the "state" of an order and its operations. Creating an order "begins" its +lifecycle, while deleting an order would "end" it. In Smithy, we can encapsulate that relationship between an entity +and its operations with :ref:`resources `. Instead of the structure above, let's define an order "resource": + +.. code-block:: smithy + :caption: ``order.smithy`` + + /// An Order resource, which has a unique id and describes an order by the type of coffee + /// and the order's status + resource Order { + identifiers: { id: Uuid } + properties: { coffeeType: CoffeeType, status: OrderStatus } + read: GetOrder // <--- we'll create this next! + create: CreateOrder // <--- we'll create this next! + } + +With a resource, we attach an identifier, which uniquely identifies an instance of that resource. Properties are +used for representing the state of an instance. In our case, we will only define a subset of the +:ref:`lifecycle operations ` to keep it simple (``create`` and ``read``). Let's define those now: + +.. code-block:: smithy + :caption: ``order.smithy`` + + /// Create an order + @idempotent + @http(method: "PUT", uri: "/order") + operation CreateOrder { + input := for Order { + @required + $coffeeType + } + + output := for Order { + @required + $id + + @required + $coffeeType + + @required + $status + } + } + + /// Retrieve an order + @readonly + @http(method: "GET", uri: "/order/{id}") + operation GetOrder { + input := for Order { + @httpLabel + @required + $id + } + + output := for Order { + @required + $id + + @required + $coffeeType + + @required + $status + } + + errors: [ + OrderNotFound // <--- we'll create this next! + ] + } + +Since we are defining operations for a resource, we use :ref:`target elision ` by prefixing +members that correspond to the resource with ``$``. This reduces the amount of repetition when defining the input and +output shapes of an operation for a resource. + +When we define an operation that may return an explicit error, we should model it using the +:ref:`error trait `. Additionally, to refine our error, we'll add the +:ref:`httpError trait `, so that a specific HTTP response status code is set when the error +is returned: + +.. code-block:: smithy + :caption: ``order.smithy`` + + /// An error indicating that an order could not be found + @httpError(404) + @error("client") + structure OrderNotFound { + message: String + orderId: Uuid + } + +Now that we've defined an order resource and its operations, we need to attach the resource to the service: + +.. code-block:: smithy + :caption: ``main.smithy`` + + ... + service CoffeeShop { + ... + resources: [ + Order + ] + } + +Finally, you may be asking why we didn't model our coffee or menu as a resource. For our service, we aren't exposing +any functionality related to the *lifecycle* of these entities. However, say for example, a coffee has properties +like origin, roast, and tasting notes. Also, we decide to expose operations for adding, updating, and removing +coffees. In this case, coffee would be a prime candidate for modeling as a resource. + +Building the Model +================== +The model for our coffee service is complete. Before we build the model, let's take a moment and learn how we configure +a build. The :ref:`smithy-build.json configuration file ` is how we instruct Smithy to build the +model. A :ref:`projection ` is a version of a model that is produced based on a set of +:ref:`transformations ` and :ref:`plugins `. For our model, we won't configure any explicit +projections, since Smithy will always build the ``source`` projection. The ``source`` projection is the model as it is +defined, and includes the artifacts of plugins applied at the root. To build the model, run: + +.. code-block:: sh + :caption: ``/bin/sh`` + + smithy build model/ + +Building the model will render artifacts under the ``build/smithy`` directory. Under it, The ``source`` directory +corresponds to the build artifacts of the ``source`` projection. With the current configuration, Smithy will produce the +model in its :ref:`JSON AST representation `, and a ``sources`` directory which contains the model files used +in the build. Additional artifacts can be produced by configuring plugins, and +:doc:`code-generators <../guides/using-code-generation/index>` are prime examples of this. + +Generating the Server SDK +========================= +The server SDK is a code-generated component which provides built-in serialization, request-handling, and +scaffolding (or "stubs") for our service as it is modeled. It facilitates the implementation of the service by +providing these things, and allowing the implementer to focus on the business logic. Let's generate the server SDK +for our service by adding the following build configuration: + +.. code-block:: json + :caption: ``smithy-build.json`` + + { + "version": "1.0", + "maven": { + "dependencies": [ + "software.amazon.smithy:smithy-aws-traits:1.50.0", + "software.amazon.smithy:smithy-validation-model:1.50.0", + "software.amazon.smithy.typescript:smithy-aws-typescript-codegen:0.22.0" + ] + }, + "plugins": { + "typescript-ssdk-codegen": { + "package" : "@com.example/coffee-service-server", + "packageVersion": "0.0.1" + } + } + } + +Run the build: + +.. code-block:: sh + :caption: ``/bin/sh`` + + smithy build model/ + +The will should fail for the following reason: + +.. code-block:: text + :caption: ``failure message`` + + Projection source failed: software.amazon.smithy.codegen.core.CodegenException: + Every operation must have the smithy.framework#ValidationException error attached + unless disableDefaultValidation is set to 'true' in the plugin settings. + Operations without smithy.framework#ValidationException errors attached: + [com.example#CreateOrder, com.example#GetMenu, com.example#GetOrder] + + +The server SDK validates inputs by default, and therefore enforces that each operation has the +``smithy.framework#ValidationException`` attached to it. We can easily add this to our model by attaching the error +to our service, meaning that all operations in the service closure may return it. Let's do this now: + +.. TODO: does this need a better explanation? + +.. code-block:: smithy + :caption: ``main.smithy`` + + use aws.protocols#restJson1 + use smithy.framework#ValidationException + + ... + service CoffeeShop { + ... + errors: [ + ValidationException + ] + } + + +After fixing this and running the build, the TypeScript code-generator plugin will have created a new +artifact under ``build/smithy/source/typescript-ssdk-codegen``. This artifact contains the generated server SDK, and +we are now able to use it in our back-end. + +.. TODO: Not sure if we should, but a brief look at the generated code might be good? + +Implementing the Server +======================= +For this tutorial, we've included a ``Makefile``, which simplifies the process of building and running the +application. To use it, make sure to run ``make`` from the root of the application directory (where the ``Makefile`` +lives). Let's try it now: + +.. code-block:: sh + :caption: ``/bin/sh`` + + make build-server + +This command will run the code-generation for the server SDK, and then build the server implementation (which uses +the server SDK). The server package is simple, and contains only two files under ``src/``: + +* ``index.ts``: entry-point of the backend application, where our server is initialized +* ``CoffeeShop.ts``: implementation of a `CoffeeShopService` from the generated server SDK + +The ``ssdk/`` directory is a link to our generated server SDK that is an output of the smithy build. This is where +the server imports the generated code from. Let's take a look at the core of the coffee shop implementation: + +.. code-block:: TypeScript + :caption: ``CoffeeShop.ts`` + + // An implementation of the service from the SSDK + export class CoffeeShop implements CoffeeShopService { + ... + + CreateOrder = async (input: CreateOrderServerInput): Promise => { + console.log("received an order request...") + return; + } + + GetMenu = async (input: GetMenuServerInput): Promise => { + console.log("getting menu...") + return; + } + + GetOrder = async (input: GetOrderServerInput): Promise => { + console.log(`getting an order (${input.id})...`) + return; + } + + ... + } + +These three methods are how we implement the business logic of the service, and are exposed by the +``CoffeeShopService`` interface exported by the server SDK. This file already contains some of the underlying logic +for how our implementation will run: there is an orders queue, an orders map, and a order-handling procedure +(``handleOrders``). We will use these to implement the operations for our service. Let's start with the simplest +operation, ``GetMenu``: + +.. code-block:: TypeScript + :caption: ``CoffeeShop.ts`` + + GetMenu = async (input: GetMenuServerInput): Promise => { + console.log("getting menu...") + return { + items: [ + { + type: CoffeeType.DRIP, + description: "A clean-bodied, rounder, and more simplistic flavour profile.\n" + + "Often praised for mellow and less intense notes.\n" + + "Far less concentrated than espresso." + }, + { + type: CoffeeType.POUR_OVER, + description: "Similar to drip coffee, but with a process that brings out more subtle nuances in flavor.\n" + + "More concentrated than drip, but less than espresso." + }, + { + type: CoffeeType.LATTE, + description: "A creamier, milk-based drink made with espresso.\n" + + "A subtle coffee taste, with smooth texture.\n" + + "High milk-to-coffee ratio." + }, + { + type: CoffeeType.ESPRESSO, + description: "A highly concentrated form of coffee, brewed under high pressure.\n" + + "Syrupy, thick liquid in a small serving size.\n" + + "Firm, full-bodies, and intensly aromatic." + } + ] + } + } + +For our menu, we've added a distinct item for each of our coffee enumerations (``CoffeeType``), as well as a +description. With our menu complete, let's implement order submission, ``CreateOrder``: + +.. code-block:: TypeScript + :caption: ``CoffeeShop.ts`` + + CreateOrder = async (input: CreateOrderServerInput): Promise => { + console.log("received an order request...") + const order = { + orderId: randomUUID(), + coffeeType: input.coffeeType, + status: OrderStatus.IN_PROGRESS + } + + this.orders.set(order.orderId, order) + this.queue.push(order) + + console.log(`created order: ${JSON.stringify(order)}`) + return { + id: order.orderId, + coffeeType: order.coffeeType, + status: order.status + } + } + +For ordering, we will maintain an orders map to simulate a database where historical order information is stored, +and an orders queue to keep track of in-flight orders. The ``handleOrders`` method will process in-flight orders +and update this queue. Once an order is submitted, it should be able to be retrieved, so let's implement ``GetOrder``: + +.. code-block:: TypeScript + :caption: ``CoffeeShop.ts`` + + GetOrder = async (input: GetOrderServerInput): Promise => { + console.log(`getting an order (${input.id})...`) + if (this.orders.has(input.id)) { + const order = this.orders.get(input.id) + return { + id: order.orderId, + coffeeType: order.coffeeType, + status: order.status + } + } else { + console.log(`order (${input.id}) does not exist.`) + throw new OrderNotFound({ + message: `order ${input.id} not found.` + }) + } + } +.. TODO: above snippet may need to be updated +.. TODO: add instruction on using the dev* targets + +With these operations implemented, our server is fully implemented. Let's build and run it: + +.. code-block:: sh + :caption: ``/bin/sh`` + + make run-server + +This command will build and run the server. You should see the following output: + +.. code-block:: text + :caption: output + + Started server on port 3001... + handling orders... + +The server is now running, so let's test it out. Open a new terminal and send a request to the ``/menu`` route +using ``cURL``: + +.. code-block:: sh + :caption: ``/bin/sh`` + + curl localhost:3001/menu + +You should see the output of the ``GetMenu`` operation that we implemented. You may stop the server by terminating it +in the terminal where it is running with ``CTRL + C``. With the server implemented, we will now move on to the client. + +Generating the Client +===================== + +To run the code-generation for a client, we'll add another plugin to the ``smithy-build.json`` configuration file: + +.. code-block:: json + :caption: ``smithy-build.json`` + + { + // ... + "plugins": { + // ... + "typescript-client-codegen": { + "package": "@com.example/coffee-service-client", + "packageVersion": "0.0.1" + } + } + } + +Run the build: + +.. code-block:: sh + :caption: ``/bin/sh`` + + smithy build model/ + +Similar to the server SDK, the TypeScript client artifacts will be written to the +``build/smithy/source/typescript-client-codegen`` directory. We can now use this client to make calls to our backend +service. + +Using the Client +================ +Like with the server, there is a make target for generating and building the TypeScript client. Let's try it now: + +.. code-block:: sh + :caption: ``/bin/sh`` + + make build-client + +This command will code-generate the client with Smithy, and then build the generated TypeScript package. The client +will be then be linked in the project root under ``client/sdk``. To use the client ad-hoc, run the following command: + +.. code-block:: sh + :caption: ``/bin/sh`` + + make repl-client + +This command launches a TypeScript `REPL `_ with +the generated client installed. Before we use the generated client, we should run the server, so that the client can +connect to it. In another terminal, launch the server: + +.. code-block:: sh + :caption: ``/bin/sh`` + + make run-server + +With the server running, we can instantiate and use the client. In the terminal running the REPL, insert and run the +following: + +.. code-block:: TypeScript + :caption: ``repl`` + + import { CoffeeShop } from '@com.example/coffee-service-client' + + const client = new CoffeeShop({ endpoint: { protocol: 'http', hostname: 'localhost', port: 3001, path: '/' } }) + + await client.getMenu() + +Like when we tested the server with ``cURL``, you should see the output of the ``GetMenu`` operation we implemented. +Let's try submitting an order: + +.. code-block:: TypeScript + :caption: ``repl`` + + await client.createOrder({ coffeeType: "DRIP" }) + +After creating the order, you should get response like: + +.. code-block:: typescript + :caption: response + + { + '$metadata': { + // metadata, such as response code, added by the client + }, + coffeeType: 'DRIP', // <--- the type of coffee we ordered + id: 'ee97e900-d8dd-4770-904c-3d175cda90c3', // <--- the order id + status: 'IN_PROGRESS' // <--- the order status + } + +The order should be ready by the time you submit this next command. Let's retrieve the order: + +.. code-block:: TypeScript + :caption: ``repl`` + + await client.getOrder({ id: '' }) // <--- make sure to replace with your id + +Once you execute the command, you should now see your order information: + +.. code-block:: typescript + :caption: response + + { + '$metadata': { + // ... + }, + coffeeType: 'DRIP', // <--- the type of coffee we ordered + id: 'ee97e900-d8dd-4770-904c-3d175cda90c3', // <--- the order id + status: 'COMPLETED' // <--- the order status, which should be 'COMPLETED' + } + +With that, you may now terminate the REPL and the server (with ``CTRL + C`` in the respective terminals). We have +tested each operation that we implemented in the server using the generated client, and verified that both the client +and server can communicate with each other. + +Running the Application +======================= +Now that we know how to generate and use a client and server, let's put it all together to use with a web application +that runs in the browser. The web application exists under the ``app/`` directory, and can be built using the +``build-app`` make target. The application will run when using the ``run-app`` target. Since this application uses +the generated client to make requests, the server must be ran alongside the app. For convenience, you may run both +the web-application and the server in the same terminal: + +.. code-block:: sh + :caption: ``/bin/sh`` + + make run + +While running the application in this way is convenient, it will intertwine the output of the web-app and server. If +you would like to keep them separate, you should run the other targets (`run-server` and `run-app`). Using the method +of your choice, launch the server and the application. + +.. TODO: Add snippets for how we are calling the service with the client + +While this application is incredibly simple, it shows how easily you can integrate a smithy-generated client into an +application that runs in the browser. +.. TODO: maybe another sentence on takeaways + +Making a Change (Optional) +========================== +Now, say we would like to add a new coffee to our menu. The new menu item should have the following details: + +* type: COLD_BREW +* description: A high-extraction and chilled form of coffee that has been cold-pressed. + Different flavor profile than other hot methods of brewing. + Smooth and slightly more caffeinated as a result of its concentration. + +.. note:: Before you proceed to the solution, try making the changes needed by yourself. + +.. raw:: html + +
+ Solution + +To add a new coffee, we will first make a change to our model. We need to add a new value for the ``CoffeeType`` enum: + +.. code-block:: smithy + :caption: ``coffee.smithy`` + + /// A enum describing the types of coffees available + enum CoffeeType { + DRIP + POUR_OVER + LATTE + ESPRESSO + COLD_BREW + } + +Next, we need to update the server code to add a new item to the menu. First, we should build the model and run the +code-generation for the server SDK, so that we have the new generated type. Run ``make build-ssdk``. With the server +SDK re-generated, we can make the change to our implementation of ``GetMenu``. We'll use the new enum value and +format the description given above to add a new item: + +.. code-block:: TypeScript + :caption: ``CoffeeShop.ts`` + + GetMenu = async (input: GetMenuServerInput): Promise => { + console.log("getting menu...") + return { + items: [ + ... + { + type: CoffeeType.COLD_BREW, + description: "A high-extraction and chilled form of coffee that has been cold-pressed..\n" + + "Different flavor profile than other hot methods of brewing.\n" + + "Smooth and slightly more caffeinated as a result of its concentration." + } + ] + } + } + +Finally, we can now run the whole application to see our change (``make run``). After you run it, you should now see +the new menu item in the web application, and should be able to order it. + +.. raw:: html + +
+ +Wrapping Up +=========== +In this tutorial, we learned how to use Smithy in a full-stack application for a simple coffee shop. We wrote a Smithy +model for a service based on a list of requirements. Afterwards, we configured builds using the ``smithy-build.json`` +configuration, which we set up to code-generate a TypeScript server SDK and a TypeScript client. We implemented the +service using the generated server SDK, and then made requests to it using the generated client. Finally, we used +the client in a web application to make requests from within the browser to our service. + +.. TOOO: what else? + +--------- +What now? +--------- +While this tutorial went over several topics, there is still so much that wasn't covered. Please check out the +following resources: + +* `awesome-smithy `_: A list of projects based in the smithy ecosystem +* `smithy-examples `_: A repository of example smithy projects \ No newline at end of file diff --git a/docs/source-2.0/tutorials/index.rst b/docs/source-2.0/tutorials/index.rst new file mode 100644 index 00000000000..7d5d75da103 --- /dev/null +++ b/docs/source-2.0/tutorials/index.rst @@ -0,0 +1,8 @@ +========= +Tutorials +========= + +.. toctree:: + :maxdepth: 1 + + full-stack-tutorial From a79a7e3251fb8aee128c89ce85a166cbad2d5aba Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Fri, 23 Aug 2024 08:06:43 -0700 Subject: [PATCH 03/16] Address comments --- docs/Makefile | 2 +- .../tutorials/full-stack-tutorial.rst | 156 ++++++++---------- docs/source-2.0/tutorials/index.rst | 2 + 3 files changed, 71 insertions(+), 89 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 1401abd9f50..48d1df58859 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -10,7 +10,7 @@ install: requirements.txt touch install clean: - -rm -rf build/* + -rm -rf build/* install html1: @source build/venv/bin/activate && $(SPHINXBUILD) -M html "source-1.0" "build/1.0" $(SPHINXOPTS) -W --keep-going -n $(O) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index 57e36dd2ab4..8e0a8b501c4 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -4,12 +4,13 @@ Full Stack Application Overview ======== -In this tutorial, we’ll walk you through using Smithy in a full-stack application. This includes defining a model for -a simple coffee-shop service, generating TypeScript code for both the client and server, and implementing both a -front-end and back-end for the service. We won't assume that you're an expert in Smithy, but it may helpful to work -through the :doc:`../quickstart` before beginning this tutorial. +In this tutorial, imagine we own a coffee shop. We would like to create a website for our customers to place an order online, and be able to grab their coffee on the go. This application should show the available coffees, and allow the customer to order a coffee. -Let's get started. +To build this application, we will walk you through using Smithy to define a model for the coffee service, generate code for a client and a server, and implement a front-end and back-end for the service. + +.. tip:: + This tutorial does not assume you are an expert in Smithy, but you may find it helpful to work through the + :doc:`../quickstart` before beginning this tutorial. Setting Up the Project ====================== @@ -23,7 +24,7 @@ This application will consist of a 4 major components: -------------- Pre-requisites -------------- -To follow this tutorial, you'll need to install a few tools. +To follow this tutorial, you will need to install a few tools: * :doc:`Smithy CLI <../guides/smithy-cli/cli_installation>` * `Node.js (>= 16) `_ and `yarn `_ @@ -40,9 +41,7 @@ command to set up the initial project: smithy init -t full-stack-application -You will now have the project in the ``full-stack-application`` directory. In this project, you will find a ``README`` -that contains important information about this project, like how it is laid out and how to build and run it. In this -tutorial, we will show exactly which commands to run and when. +After running this command, you should have the project in the ``full-stack-application`` directory. You will find a ``README`` containing important information about the project, like how it is laid out and how to build or run it. In this tutorial, we will show you which commands to run and when. .. TODO: Provide the skeleton template or a git patch? @@ -59,8 +58,8 @@ As discussed above, the coffee service should: ------------------ Adding a Service ------------------ -The service shape is the entry-point of our API, and is where we define the operations that our service exposes to a -consumer. With that information in mind, let's define the initial service shape without any operations: +The service shape is the entry-point of our API, and is where we define the operations our service exposes to a +consumer. First and foremost, let's define the initial service shape without any operations: .. code-block:: smithy :caption: ``main.smithy`` @@ -79,17 +78,16 @@ consumer. With that information in mind, let's define the initial service shape version: "2024-04-04" } -We apply the ``@restJson1`` protocol trait to the service to indicate that this service supports the -:doc:`../aws/protocols/aws-restjson1-protocol` created and used extensively by AWS. -Put simply, protocols define the rules and conventions for how data is serialized and deserialized when communicating -between client and server. Protocols are complex and need their own topic, so we will not discuss them at length in -this tutorial. +We apply the ``@restJson1`` protocol trait to the service to indicate the service supports the +:doc:`../aws/protocols/aws-restjson1-protocol`. Protocols define the rules and conventions for how data is serialized and deserialized when communicating between client and server. Protocols are a highly complex topic, so they will not be discussed any further in this tutorial. + +------------- +Modeling Data +------------- +Let's create basic representations of our data in Smithy. We will further refine our data model using +:ref:`traits `. Open the file titled ``coffee.smithy``. We will use it to write our definitions of coffee-related structures: ---------------- -Defining Shapes ---------------- -Let's create basic representations of our data in Smithy. We can further refine our data model using traits. -Open the file titled ``coffee.smithy``. We will use it to write our definitions of coffee-related structures: +.. _full-stack-tutorial-operations: .. code-block:: smithy :caption: ``coffee.smithy`` @@ -121,12 +119,12 @@ Open the file titled ``coffee.smithy``. We will use it to write our definitions } ------------------- -Defining Operations +Modeling Operations ------------------- With the shapes defined above, let's create an operation on our service for returning a menu to the consumer: .. code-block:: smithy - :caption: ``main.smithy`` + :caption: ``main.smithy`` ... service CoffeeShop { @@ -145,9 +143,9 @@ With the shapes defined above, let's create an operation on our service for retu } } -We've named the operation ``GetMenu``. It does not define an input, and models its output as a structure with a single -member, ``items``, which contains ``CoffeeItems`` (a shape we defined above). With the ``restJson1`` protocol, a -potential response would be serialized like so: +The operation is named ``GetMenu``. It does not define an input, and models its output as a structure with a single +member, ``items``, which contains ``CoffeeItems`` (a shape we defined :ref:`above `). With the ``restJson1`` protocol, a potential response would be serialized like so: + .. TODO: Add info on http trait? .. code-block:: json @@ -194,9 +192,8 @@ With these requirements in mind, let's create the underlying data model: } A universally unique identifier (or `"UUID" `_) should be -more than sufficient for our service. The order status can be either ``IN_PROGRESS`` (when the order is submitted) or -``COMPLETED`` (when the order is ready). The information about what kind of coffee was order can be represented by the -``CoffeeType`` shape we defined earlier. +more than sufficient for our service. The order status is ``IN_PROGRESS`` (when the order is submitted) or +``COMPLETED`` (when the order is ready). The information about what kind of coffee was ordered will be represented by the ``CoffeeType`` shape we defined earlier. Let's compose these shapes together to create our representation of an order: @@ -210,10 +207,10 @@ Let's compose these shapes together to create our representation of an order: status: OrderStatus } -We're making great progress. However, if we think about an order and it's `potential` set of operations +We're making great progress. However, if we think about an order and its `potential` set of operations (`creating, reading, updating, deleting `_ an order), there is tight relationship between the "state" of an order and its operations. Creating an order "begins" its -lifecycle, while deleting an order would "end" it. In Smithy, we can encapsulate that relationship between an entity +lifecycle, while deleting an order would "end" it. In Smithy, we encapsulate the relationship between an entity and its operations with :ref:`resources `. Instead of the structure above, let's define an order "resource": .. code-block:: smithy @@ -224,11 +221,11 @@ and its operations with :ref:`resources `. Instead of the structure ab resource Order { identifiers: { id: Uuid } properties: { coffeeType: CoffeeType, status: OrderStatus } - read: GetOrder // <--- we'll create this next! - create: CreateOrder // <--- we'll create this next! + read: GetOrder // <--- we will create this next! + create: CreateOrder // <--- we will create this next! } -With a resource, we attach an identifier, which uniquely identifies an instance of that resource. Properties are +With a resource, we attach an identifier, which uniquely identifies an instance of the resource. Properties are used for representing the state of an instance. In our case, we will only define a subset of the :ref:`lifecycle operations ` to keep it simple (``create`` and ``read``). Let's define those now: @@ -278,23 +275,23 @@ used for representing the state of an instance. In our case, we will only define } errors: [ - OrderNotFound // <--- we'll create this next! + OrderNotFound // <--- we will create this next! ] } Since we are defining operations for a resource, we use :ref:`target elision ` by prefixing -members that correspond to the resource with ``$``. This reduces the amount of repetition when defining the input and +members corresponding to the resource with ``$``. This reduces the amount of repetition when defining the input and output shapes of an operation for a resource. -When we define an operation that may return an explicit error, we should model it using the -:ref:`error trait `. Additionally, to refine our error, we'll add the -:ref:`httpError trait `, so that a specific HTTP response status code is set when the error +When we define an operation which may return an explicit error, we should model it using the +:ref:`error trait `. Additionally, to refine our error, we will add the +:ref:`httpError trait ` to set a specific HTTP response status code is set when the error is returned: .. code-block:: smithy :caption: ``order.smithy`` - /// An error indicating that an order could not be found + /// An error indicating an order could not be found @httpError(404) @error("client") structure OrderNotFound { @@ -302,7 +299,7 @@ is returned: orderId: Uuid } -Now that we've defined an order resource and its operations, we need to attach the resource to the service: +Now that we have defined an order resource and its operations, we need to attach the resource to the service: .. code-block:: smithy :caption: ``main.smithy`` @@ -315,19 +312,14 @@ Now that we've defined an order resource and its operations, we need to attach t ] } -Finally, you may be asking why we didn't model our coffee or menu as a resource. For our service, we aren't exposing -any functionality related to the *lifecycle* of these entities. However, say for example, a coffee has properties -like origin, roast, and tasting notes. Also, we decide to expose operations for adding, updating, and removing -coffees. In this case, coffee would be a prime candidate for modeling as a resource. +Finally, you might be wondering why we didn't model our coffee or menu as a resource. For our service, we are not exposing any functionality related to the *lifecycle* of these entities. However, let's describe a hypothetical example. +We decide a coffee has properties like origin, roast, and tasting notes. Also, we choose to expose operations for adding, updating, and removing coffees. In this case, coffee would be a prime candidate for modeling as a resource. Building the Model ================== The model for our coffee service is complete. Before we build the model, let's take a moment and learn how we configure a build. The :ref:`smithy-build.json configuration file ` is how we instruct Smithy to build the -model. A :ref:`projection ` is a version of a model that is produced based on a set of -:ref:`transformations ` and :ref:`plugins `. For our model, we won't configure any explicit -projections, since Smithy will always build the ``source`` projection. The ``source`` projection is the model as it is -defined, and includes the artifacts of plugins applied at the root. To build the model, run: +model. A :ref:`projection ` is a version of a model based on a set of :ref:`transformations ` and :ref:`plugins `. For our model, we won't configure any explicit projections, since Smithy will always build the ``source`` projection. The ``source`` projection is the model as it is defined, and includes the artifacts of plugins applied at the root. To build the model, run: .. code-block:: sh :caption: ``/bin/sh`` @@ -337,7 +329,7 @@ defined, and includes the artifacts of plugins applied at the root. To build the Building the model will render artifacts under the ``build/smithy`` directory. Under it, The ``source`` directory corresponds to the build artifacts of the ``source`` projection. With the current configuration, Smithy will produce the model in its :ref:`JSON AST representation `, and a ``sources`` directory which contains the model files used -in the build. Additional artifacts can be produced by configuring plugins, and +in the build. Additional artifacts are produced by configuring plugins, and :doc:`code-generators <../guides/using-code-generation/index>` are prime examples of this. Generating the Server SDK @@ -386,9 +378,8 @@ The will should fail for the following reason: [com.example#CreateOrder, com.example#GetMenu, com.example#GetOrder] -The server SDK validates inputs by default, and therefore enforces that each operation has the -``smithy.framework#ValidationException`` attached to it. We can easily add this to our model by attaching the error -to our service, meaning that all operations in the service closure may return it. Let's do this now: +The server SDK validates inputs by default, and enforces each operation has the ``smithy.framework#ValidationException`` attached to it. We will fix this issue by attaching the error +to our service, meaning all operations in the service may return it. Let's do this now: .. TODO: does this need a better explanation? @@ -408,14 +399,13 @@ to our service, meaning that all operations in the service closure may return it After fixing this and running the build, the TypeScript code-generator plugin will have created a new -artifact under ``build/smithy/source/typescript-ssdk-codegen``. This artifact contains the generated server SDK, and -we are now able to use it in our back-end. +artifact under ``build/smithy/source/typescript-ssdk-codegen``. This artifact contains the generated server SDK (SSDK), which we will use in our back-end. .. TODO: Not sure if we should, but a brief look at the generated code might be good? Implementing the Server ======================= -For this tutorial, we've included a ``Makefile``, which simplifies the process of building and running the +For this tutorial, we have included a ``Makefile``, which simplifies the process of building and running the application. To use it, make sure to run ``make`` from the root of the application directory (where the ``Makefile`` lives). Let's try it now: @@ -430,7 +420,7 @@ the server SDK). The server package is simple, and contains only two files under * ``index.ts``: entry-point of the backend application, where our server is initialized * ``CoffeeShop.ts``: implementation of a `CoffeeShopService` from the generated server SDK -The ``ssdk/`` directory is a link to our generated server SDK that is an output of the smithy build. This is where +The ``ssdk/`` directory is a link to our generated server SDK, which is an output of the smithy build. This is where the server imports the generated code from. Let's take a look at the core of the coffee shop implementation: .. code-block:: TypeScript @@ -492,13 +482,13 @@ operation, ``GetMenu``: type: CoffeeType.ESPRESSO, description: "A highly concentrated form of coffee, brewed under high pressure.\n" + "Syrupy, thick liquid in a small serving size.\n" + - "Firm, full-bodies, and intensly aromatic." + "Full bodied and intensly aromatic." } ] } } -For our menu, we've added a distinct item for each of our coffee enumerations (``CoffeeType``), as well as a +For our menu, we have added a distinct item for each of our coffee enumerations (``CoffeeType``), as well as a description. With our menu complete, let's implement order submission, ``CreateOrder``: .. code-block:: TypeScript @@ -523,7 +513,7 @@ description. With our menu complete, let's implement order submission, ``CreateO } } -For ordering, we will maintain an orders map to simulate a database where historical order information is stored, +For ordering, we will maintain an ``orders`` map to simulate a database where historical order information is stored, and an orders queue to keep track of in-flight orders. The ``handleOrders`` method will process in-flight orders and update this queue. Once an order is submitted, it should be able to be retrieved, so let's implement ``GetOrder``: @@ -564,21 +554,20 @@ This command will build and run the server. You should see the following output: Started server on port 3001... handling orders... -The server is now running, so let's test it out. Open a new terminal and send a request to the ``/menu`` route -using ``cURL``: +With the server running, let's test it by sending it requests. Open a new terminal and send a request to the ``/menu`` route using ``cURL``: .. code-block:: sh :caption: ``/bin/sh`` curl localhost:3001/menu -You should see the output of the ``GetMenu`` operation that we implemented. You may stop the server by terminating it -in the terminal where it is running with ``CTRL + C``. With the server implemented, we will now move on to the client. +You should see the output of the ``GetMenu`` operation we implemented. You may stop the server by terminating it +in the terminal where it is running with ``CTRL + C``. With the server implemented, we will move on to the client. Generating the Client ===================== -To run the code-generation for a client, we'll add another plugin to the ``smithy-build.json`` configuration file: +To run the code-generation for a client, we will add another plugin to the ``smithy-build.json`` configuration file: .. code-block:: json :caption: ``smithy-build.json`` @@ -602,7 +591,7 @@ Run the build: smithy build model/ Similar to the server SDK, the TypeScript client artifacts will be written to the -``build/smithy/source/typescript-client-codegen`` directory. We can now use this client to make calls to our backend +``build/smithy/source/typescript-client-codegen`` directory. We will use this client to make calls to our backend service. Using the Client @@ -623,15 +612,14 @@ will be then be linked in the project root under ``client/sdk``. To use the clie make repl-client This command launches a TypeScript `REPL `_ with -the generated client installed. Before we use the generated client, we should run the server, so that the client can -connect to it. In another terminal, launch the server: +the generated client installed. Before we use the generated client, we must run the server. Without the server running, the client will not be able to connect. In another terminal, launch the server with the following command: .. code-block:: sh :caption: ``/bin/sh`` make run-server -With the server running, we can instantiate and use the client. In the terminal running the REPL, insert and run the +With the server running, we will instantiate and use the client. In the terminal running the REPL, insert and run the following: .. code-block:: TypeScript @@ -672,7 +660,7 @@ The order should be ready by the time you submit this next command. Let's retrie await client.getOrder({ id: '' }) // <--- make sure to replace with your id -Once you execute the command, you should now see your order information: +Once you execute the command, you should see your order information: .. code-block:: typescript :caption: response @@ -686,16 +674,13 @@ Once you execute the command, you should now see your order information: status: 'COMPLETED' // <--- the order status, which should be 'COMPLETED' } -With that, you may now terminate the REPL and the server (with ``CTRL + C`` in the respective terminals). We have -tested each operation that we implemented in the server using the generated client, and verified that both the client -and server can communicate with each other. +You may terminate the REPL and the server (with ``CTRL + C`` in the respective terminals). We have +tested each operation we implemented in the server using the generated client, and verified both the client +and server communicate with each other. Running the Application ======================= -Now that we know how to generate and use a client and server, let's put it all together to use with a web application -that runs in the browser. The web application exists under the ``app/`` directory, and can be built using the -``build-app`` make target. The application will run when using the ``run-app`` target. Since this application uses -the generated client to make requests, the server must be ran alongside the app. For convenience, you may run both +Since we know how to generate and use the client and server, let's put it all together to use with a browser-based web application. The web application exists under the ``app/`` directory, and is built using the ``build-app`` make target. The application will run when using the ``run-app`` target. Since this application uses the generated client to make requests, the server must be ran alongside the app. For convenience, you may run both the web-application and the server in the same terminal: .. code-block:: sh @@ -709,8 +694,8 @@ of your choice, launch the server and the application. .. TODO: Add snippets for how we are calling the service with the client -While this application is incredibly simple, it shows how easily you can integrate a smithy-generated client into an -application that runs in the browser. +While this application is incredibly simple, it shows how to integrate a smithy-generated client with an +application running in the browser. .. TODO: maybe another sentence on takeaways Making a Change (Optional) @@ -744,9 +729,7 @@ To add a new coffee, we will first make a change to our model. We need to add a } Next, we need to update the server code to add a new item to the menu. First, we should build the model and run the -code-generation for the server SDK, so that we have the new generated type. Run ``make build-ssdk``. With the server -SDK re-generated, we can make the change to our implementation of ``GetMenu``. We'll use the new enum value and -format the description given above to add a new item: +code-generation for the server SDK, so the new types are generated. Run ``make build-ssdk``. After re-generating the server SDK, we will make the change to our implementation of ``GetMenu``. We will use the new enum value and format the description from above to add a new item: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` @@ -766,7 +749,7 @@ format the description given above to add a new item: } } -Finally, we can now run the whole application to see our change (``make run``). After you run it, you should now see +Finally, we will run the whole application to see our change (``make run``). After you run it, you should see the new menu item in the web application, and should be able to order it. .. raw:: html @@ -775,10 +758,7 @@ the new menu item in the web application, and should be able to order it. Wrapping Up =========== -In this tutorial, we learned how to use Smithy in a full-stack application for a simple coffee shop. We wrote a Smithy -model for a service based on a list of requirements. Afterwards, we configured builds using the ``smithy-build.json`` -configuration, which we set up to code-generate a TypeScript server SDK and a TypeScript client. We implemented the -service using the generated server SDK, and then made requests to it using the generated client. Finally, we used +In this tutorial, you used Smithy to build a full-stack application for a simple coffee shop. You wrote a Smithy model for a service based on a list of requirements. Afterwards, you configured builds using the ``smithy-build.json`` configuration, which you set up to code-generate a TypeScript server SDK and a TypeScript client. You implemented the service using the generated server SDK, and then made requests to it using the generated client. Finally, you used the client in a web application to make requests from within the browser to our service. .. TOOO: what else? @@ -786,7 +766,7 @@ the client in a web application to make requests from within the browser to our --------- What now? --------- -While this tutorial went over several topics, there is still so much that wasn't covered. Please check out the +We covered several topics in this tutorial, there is still so much to learn! Please check out the following resources: * `awesome-smithy `_: A list of projects based in the smithy ecosystem diff --git a/docs/source-2.0/tutorials/index.rst b/docs/source-2.0/tutorials/index.rst index 7d5d75da103..8323d6067a0 100644 --- a/docs/source-2.0/tutorials/index.rst +++ b/docs/source-2.0/tutorials/index.rst @@ -2,6 +2,8 @@ Tutorials ========= +The goal of these pages is to help you learn by doing. While tutorials will have explicit objectives (e.g. "build a full-stack application"), the true intention is that you learn how to use Smithy holistically. You will gain an understanding of various tools, concepts, interactions, and how they fit together in this ecosystem. + .. toctree:: :maxdepth: 1 From 8b82947d93d5f16aa6c389773a3daba7a01ce162 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Fri, 23 Aug 2024 15:15:41 -0700 Subject: [PATCH 04/16] edits, grammar --- .../tutorials/full-stack-tutorial.rst | 202 +++++++++++------- 1 file changed, 119 insertions(+), 83 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index 8e0a8b501c4..7288e921afd 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -4,22 +4,25 @@ Full Stack Application Overview ======== -In this tutorial, imagine we own a coffee shop. We would like to create a website for our customers to place an order online, and be able to grab their coffee on the go. This application should show the available coffees, and allow the customer to order a coffee. +In this tutorial, imagine we own a coffee shop. We would like to create a website for our customers to place an +order online, and be able to grab their coffee on the go. This application should show the available coffees, and +allow the customer to order a coffee. -To build this application, we will walk you through using Smithy to define a model for the coffee service, generate code for a client and a server, and implement a front-end and back-end for the service. +To build this application, we will walk you through using Smithy to define a model for the coffee service, generate +code for a client and a server, and implement a front-end and back-end for the service. .. tip:: This tutorial does not assume you are an expert in Smithy, but you may find it helpful to work through the :doc:`../quickstart` before beginning this tutorial. -Setting Up the Project +Setting up the project ====================== This application will consist of a 4 major components: -1. The model -2. The server -3. The client -4. The web application +1. A model, which defines the service +2. A server, which handles requests for coffee +3. A client, which is generated from the model +4. A web application, which uses the client to make requests to the server -------------- Pre-requisites @@ -41,22 +44,40 @@ command to set up the initial project: smithy init -t full-stack-application -After running this command, you should have the project in the ``full-stack-application`` directory. You will find a ``README`` containing important information about the project, like how it is laid out and how to build or run it. In this tutorial, we will show you which commands to run and when. +After running this command, you should have the project in the ``full-stack-application`` directory. +The directory tree should look like: + +.. code-block:: sh + :caption: ``/bin/sh`` + + full-stack-application + ├── Makefile + ├── README.md + ├── app + │ ├── ... + ├── client + │ ├── ... + ├── server + │ ├── ... + └── smithy + ├── ... + +The ``README.md`` file contains important information about the project, like its directory structure and how to build or +run it. However, for this tutorial, we will show you which commands to run, when to run them, and the expected outputs. .. TODO: Provide the skeleton template or a git patch? -Modeling Our Service +Modeling the service ==================== With the basic framework for the project established, let's walk through how to model our coffee service. - -As discussed above, the coffee service should: +The service should provide a few capabilities: * provide a menu of coffees * provide the ability to order a coffee * provide the ability to check the status of an order ------------------ -Adding a Service +Adding the service ------------------ The service shape is the entry-point of our API, and is where we define the operations our service exposes to a consumer. First and foremost, let's define the initial service shape without any operations: @@ -79,13 +100,16 @@ consumer. First and foremost, let's define the initial service shape without any } We apply the ``@restJson1`` protocol trait to the service to indicate the service supports the -:doc:`../aws/protocols/aws-restjson1-protocol`. Protocols define the rules and conventions for how data is serialized and deserialized when communicating between client and server. Protocols are a highly complex topic, so they will not be discussed any further in this tutorial. +:doc:`../aws/protocols/aws-restjson1-protocol`. Protocols define the rules and conventions for serializing and +de-serializing data when communicating between client and server. Protocols are a highly complex topic, so we will not +discuss them further in this tutorial. ------------- -Modeling Data +Modeling data ------------- Let's create basic representations of our data in Smithy. We will further refine our data model using -:ref:`traits `. Open the file titled ``coffee.smithy``. We will use it to write our definitions of coffee-related structures: +:ref:`traits `. Open the file titled ``coffee.smithy``. We will use it to write our definitions +of coffee-related structures: .. _full-stack-tutorial-operations: @@ -119,7 +143,7 @@ Let's create basic representations of our data in Smithy. We will further refine } ------------------- -Modeling Operations +Modeling operations ------------------- With the shapes defined above, let's create an operation on our service for returning a menu to the consumer: @@ -143,10 +167,9 @@ With the shapes defined above, let's create an operation on our service for retu } } -The operation is named ``GetMenu``. It does not define an input, and models its output as a structure with a single -member, ``items``, which contains ``CoffeeItems`` (a shape we defined :ref:`above `). With the ``restJson1`` protocol, a potential response would be serialized like so: - -.. TODO: Add info on http trait? +We have named the operation ``GetMenu``. It does not define an input, and models its output as a structure with a single +member, ``items``, which contains ``CoffeeItems``, a shape we defined :ref:`above `. +With the ``restJson1`` protocol, the serialized response might look like the below: .. code-block:: json :caption: ``GetMenuResponse (json)`` @@ -161,14 +184,14 @@ member, ``items``, which contains ``CoffeeItems`` (a shape we defined :ref:`abov } ------------------- -Representing Orders +Representing orders ------------------- At this point, we still need to model the ordering functionality of our service. Let's create a new file, ``order.smithy``, which will hold definitions related to ordering. First, let's consider the following when modeling an order: 1. an order needs a unique identifier -2. an order needs to have a status (such as "in-progress" or "completed"), and +2. an order needs to have a status, such as "in-progress" or "completed" 3. an order needs to hold the coffee information (``CoffeeType``) With these requirements in mind, let's create the underlying data model: @@ -192,8 +215,9 @@ With these requirements in mind, let's create the underlying data model: } A universally unique identifier (or `"UUID" `_) should be -more than sufficient for our service. The order status is ``IN_PROGRESS`` (when the order is submitted) or -``COMPLETED`` (when the order is ready). The information about what kind of coffee was ordered will be represented by the ``CoffeeType`` shape we defined earlier. +more than sufficient for our service. The order status is ``IN_PROGRESS`` (after submitting the order) or +``COMPLETED`` (when the order is ready). We will represent the coffee order information with the ``CoffeeType`` shape +we defined earlier. Let's compose these shapes together to create our representation of an order: @@ -211,7 +235,7 @@ We're making great progress. However, if we think about an order and its `potent (`creating, reading, updating, deleting `_ an order), there is tight relationship between the "state" of an order and its operations. Creating an order "begins" its lifecycle, while deleting an order would "end" it. In Smithy, we encapsulate the relationship between an entity -and its operations with :ref:`resources `. Instead of the structure above, let's define an order "resource": +and its operations with :ref:`resources `. Instead of the above structure, let's define an order "resource": .. code-block:: smithy :caption: ``order.smithy`` @@ -225,8 +249,8 @@ and its operations with :ref:`resources `. Instead of the structure ab create: CreateOrder // <--- we will create this next! } -With a resource, we attach an identifier, which uniquely identifies an instance of the resource. Properties are -used for representing the state of an instance. In our case, we will only define a subset of the +With a resource, we attach an identifier, which uniquely identifies an instance of the resource. We use properties to +represent the state of an instance. In this case, we will only define a subset of the :ref:`lifecycle operations ` to keep it simple (``create`` and ``read``). Let's define those now: .. code-block:: smithy @@ -285,8 +309,7 @@ output shapes of an operation for a resource. When we define an operation which may return an explicit error, we should model it using the :ref:`error trait `. Additionally, to refine our error, we will add the -:ref:`httpError trait ` to set a specific HTTP response status code is set when the error -is returned: +:ref:`httpError trait ` to set a specific HTTP response status code when the service returns the error: .. code-block:: smithy :caption: ``order.smithy`` @@ -312,14 +335,19 @@ Now that we have defined an order resource and its operations, we need to attach ] } -Finally, you might be wondering why we didn't model our coffee or menu as a resource. For our service, we are not exposing any functionality related to the *lifecycle* of these entities. However, let's describe a hypothetical example. -We decide a coffee has properties like origin, roast, and tasting notes. Also, we choose to expose operations for adding, updating, and removing coffees. In this case, coffee would be a prime candidate for modeling as a resource. +Finally, you might be wondering why we did not model our coffee or menu as a resource. For our service, we are not +exposing any functionality related to the *lifecycle* of these entities. However, let's describe a hypothetical +example. We decide a coffee has properties like origin, roast, and tasting notes. Also, we choose to expose operations +for adding, updating, and removing coffees. In this case, coffee would be a prime candidate for modeling as a resource. -Building the Model +Building the model ================== The model for our coffee service is complete. Before we build the model, let's take a moment and learn how we configure a build. The :ref:`smithy-build.json configuration file ` is how we instruct Smithy to build the -model. A :ref:`projection ` is a version of a model based on a set of :ref:`transformations ` and :ref:`plugins `. For our model, we won't configure any explicit projections, since Smithy will always build the ``source`` projection. The ``source`` projection is the model as it is defined, and includes the artifacts of plugins applied at the root. To build the model, run: +model. A :ref:`projection ` is a version of a model based on a set of :ref:`transformations ` +and :ref:`plugins `. For our model, we will not configure any explicit projections, since Smithy will always +build the ``source`` projection. The ``source`` projection does not have any transformations applied, and its output +includes the artifacts of plugins applied at the root. To build the model, run: .. code-block:: sh :caption: ``/bin/sh`` @@ -327,15 +355,15 @@ model. A :ref:`projection ` is a version of a model based on a set smithy build model/ Building the model will render artifacts under the ``build/smithy`` directory. Under it, The ``source`` directory -corresponds to the build artifacts of the ``source`` projection. With the current configuration, Smithy will produce the -model in its :ref:`JSON AST representation `, and a ``sources`` directory which contains the model files used -in the build. Additional artifacts are produced by configuring plugins, and +corresponds to the output (or "build artifacts") of the ``source`` projection. With the current configuration, Smithy +will produce the model in its :ref:`JSON AST representation `, and a ``sources`` directory which contains the +model files used in the build. Additional artifacts are produced by configuring plugins, and :doc:`code-generators <../guides/using-code-generation/index>` are prime examples of this. -Generating the Server SDK +Generating the server SDK ========================= The server SDK is a code-generated component which provides built-in serialization, request-handling, and -scaffolding (or "stubs") for our service as it is modeled. It facilitates the implementation of the service by +scaffolding (or "stubs") for our service. It facilitates the implementation of the service by providing these things, and allowing the implementer to focus on the business logic. Let's generate the server SDK for our service by adding the following build configuration: @@ -378,11 +406,10 @@ The will should fail for the following reason: [com.example#CreateOrder, com.example#GetMenu, com.example#GetOrder] -The server SDK validates inputs by default, and enforces each operation has the ``smithy.framework#ValidationException`` attached to it. We will fix this issue by attaching the error +The server SDK validates inputs by default, and enforces each operation has +the ``smithy.framework#ValidationException`` attached to it. We will fix this issue by attaching the error to our service, meaning all operations in the service may return it. Let's do this now: -.. TODO: does this need a better explanation? - .. code-block:: smithy :caption: ``main.smithy`` @@ -399,11 +426,10 @@ to our service, meaning all operations in the service may return it. Let's do th After fixing this and running the build, the TypeScript code-generator plugin will have created a new -artifact under ``build/smithy/source/typescript-ssdk-codegen``. This artifact contains the generated server SDK (SSDK), which we will use in our back-end. +artifact under ``build/smithy/source/typescript-ssdk-codegen``. This artifact contains the generated +server SDK (SSDK), which we will use in our back-end. -.. TODO: Not sure if we should, but a brief look at the generated code might be good? - -Implementing the Server +Implementing the server ======================= For this tutorial, we have included a ``Makefile``, which simplifies the process of building and running the application. To use it, make sure to run ``make`` from the root of the application directory (where the ``Makefile`` @@ -417,7 +443,7 @@ lives). Let's try it now: This command will run the code-generation for the server SDK, and then build the server implementation (which uses the server SDK). The server package is simple, and contains only two files under ``src/``: -* ``index.ts``: entry-point of the backend application, where our server is initialized +* ``index.ts``: entry-point of the backend application, and where we initialize our service * ``CoffeeShop.ts``: implementation of a `CoffeeShopService` from the generated server SDK The ``ssdk/`` directory is a link to our generated server SDK, which is an output of the smithy build. This is where @@ -427,7 +453,7 @@ the server imports the generated code from. Let's take a look at the core of the :caption: ``CoffeeShop.ts`` // An implementation of the service from the SSDK - export class CoffeeShop implements CoffeeShopService { + export class CoffeeShop implements CoffeeShopService<{}> { ... CreateOrder = async (input: CreateOrderServerInput): Promise => { @@ -448,11 +474,12 @@ the server imports the generated code from. Let's take a look at the core of the ... } -These three methods are how we implement the business logic of the service, and are exposed by the +These three methods are how we implement the core business logic of the service. They are exposed by the ``CoffeeShopService`` interface exported by the server SDK. This file already contains some of the underlying logic for how our implementation will run: there is an orders queue, an orders map, and a order-handling procedure (``handleOrders``). We will use these to implement the operations for our service. Let's start with the simplest -operation, ``GetMenu``: +operation, ``GetMenu``. We will modify the operation to return a menu containing one coffee item for +each type of coffee: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` @@ -488,8 +515,8 @@ operation, ``GetMenu``: } } -For our menu, we have added a distinct item for each of our coffee enumerations (``CoffeeType``), as well as a -description. With our menu complete, let's implement order submission, ``CreateOrder``: +For our menu, we have added a distinct item and description for each of our coffee enumerations (``CoffeeType``). +With our menu complete, let's implement order submission, ``CreateOrder``: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` @@ -513,9 +540,10 @@ description. With our menu complete, let's implement order submission, ``CreateO } } -For ordering, we will maintain an ``orders`` map to simulate a database where historical order information is stored, -and an orders queue to keep track of in-flight orders. The ``handleOrders`` method will process in-flight orders -and update this queue. Once an order is submitted, it should be able to be retrieved, so let's implement ``GetOrder``: +For ordering, we will maintain an order map to simulate a database that stores historical order information, +and an order queue to keep track of in-flight orders. The ``handleOrders`` method processes in-flight orders +and updates this queue. After submitting an order, ``GetOrder`` can retrieve its information. +Let's implement ``GetOrder``: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` @@ -536,8 +564,7 @@ and update this queue. Once an order is submitted, it should be able to be retri }) } } -.. TODO: above snippet may need to be updated -.. TODO: add instruction on using the dev* targets +.. TODO: above snippet may need to be updated, verify errors With these operations implemented, our server is fully implemented. Let's build and run it: @@ -554,17 +581,18 @@ This command will build and run the server. You should see the following output: Started server on port 3001... handling orders... -With the server running, let's test it by sending it requests. Open a new terminal and send a request to the ``/menu`` route using ``cURL``: +With the server running, let's test it by sending it requests. Open a new terminal and send a request to the ``/menu`` +route using ``cURL``: .. code-block:: sh :caption: ``/bin/sh`` curl localhost:3001/menu -You should see the output of the ``GetMenu`` operation we implemented. You may stop the server by terminating it -in the terminal where it is running with ``CTRL + C``. With the server implemented, we will move on to the client. +You should see the output of the ``GetMenu`` operation we implemented. You may stop the server with ``CTRL + C`` in the +terminal where it is running. With the server implemented, we will move on to the client. -Generating the Client +Generating the client ===================== To run the code-generation for a client, we will add another plugin to the ``smithy-build.json`` configuration file: @@ -590,11 +618,11 @@ Run the build: smithy build model/ -Similar to the server SDK, the TypeScript client artifacts will be written to the +Similar to the server SDK, Smithy will generate the TypeScript client artifacts under the ``build/smithy/source/typescript-client-codegen`` directory. We will use this client to make calls to our backend service. -Using the Client +Using the client ================ Like with the server, there is a make target for generating and building the TypeScript client. Let's try it now: @@ -603,8 +631,8 @@ Like with the server, there is a make target for generating and building the Typ make build-client -This command will code-generate the client with Smithy, and then build the generated TypeScript package. The client -will be then be linked in the project root under ``client/sdk``. To use the client ad-hoc, run the following command: +This command will code-generate the client with Smithy, and then build the generated TypeScript package. The command +will link the client in the project root under ``client/sdk``. To use the client ad-hoc, run the following command: .. code-block:: sh :caption: ``/bin/sh`` @@ -612,14 +640,15 @@ will be then be linked in the project root under ``client/sdk``. To use the clie make repl-client This command launches a TypeScript `REPL `_ with -the generated client installed. Before we use the generated client, we must run the server. Without the server running, the client will not be able to connect. In another terminal, launch the server with the following command: +the generated client installed. Before we use the generated client, we must run the server. Without the server running, +the client will not be able to connect. In another terminal, launch the server with the following command: .. code-block:: sh :caption: ``/bin/sh`` make run-server -With the server running, we will instantiate and use the client. In the terminal running the REPL, insert and run the +With the server running, we will instantiate and use the client. In the terminal running the REPL, run the following: .. code-block:: TypeScript @@ -674,31 +703,32 @@ Once you execute the command, you should see your order information: status: 'COMPLETED' // <--- the order status, which should be 'COMPLETED' } -You may terminate the REPL and the server (with ``CTRL + C`` in the respective terminals). We have +You may stop the REPL and the server with ``CTRL + C`` in the respective terminals. We have tested each operation we implemented in the server using the generated client, and verified both the client and server communicate with each other. -Running the Application +Running the application ======================= -Since we know how to generate and use the client and server, let's put it all together to use with a browser-based web application. The web application exists under the ``app/`` directory, and is built using the ``build-app`` make target. The application will run when using the ``run-app`` target. Since this application uses the generated client to make requests, the server must be ran alongside the app. For convenience, you may run both -the web-application and the server in the same terminal: +Since we know how to generate and use the client and server, let's put it all together to use with a browser-based +web application. The web application exists under the ``app/`` directory. To build the application, use the +``build-app`` make target. The application will run when using the ``run-app`` target. Since this application uses the +generated client to make requests, the server must be ran alongside the app. For convenience, you may run both the web +application and the server in the same terminal: .. code-block:: sh :caption: ``/bin/sh`` make run -While running the application in this way is convenient, it will intertwine the output of the web-app and server. If -you would like to keep them separate, you should run the other targets (`run-server` and `run-app`). Using the method -of your choice, launch the server and the application. - -.. TODO: Add snippets for how we are calling the service with the client +While running the application in this way is convenient, it will intertwine the output of the application and server. +If you would like to keep them separate, you should run the other targets (`run-server` and `run-app`). +Using the method of your choice, launch the server and the application. While this application is incredibly simple, it shows how to integrate a smithy-generated client with an application running in the browser. .. TODO: maybe another sentence on takeaways -Making a Change (Optional) +Making a change (optional) ========================== Now, say we would like to add a new coffee to our menu. The new menu item should have the following details: @@ -714,7 +744,8 @@ Now, say we would like to add a new coffee to our menu. The new menu item should
Solution -To add a new coffee, we will first make a change to our model. We need to add a new value for the ``CoffeeType`` enum: +To add a new coffee, we will first make a change to our model. We need to add a new value for the ``CoffeeType`` +enumeration: .. code-block:: smithy :caption: ``coffee.smithy`` @@ -729,7 +760,9 @@ To add a new coffee, we will first make a change to our model. We need to add a } Next, we need to update the server code to add a new item to the menu. First, we should build the model and run the -code-generation for the server SDK, so the new types are generated. Run ``make build-ssdk``. After re-generating the server SDK, we will make the change to our implementation of ``GetMenu``. We will use the new enum value and format the description from above to add a new item: +code-generation for the server SDK to generate the new value. Run ``make build-ssdk``. After re-generating the +server SDK, we will make the change to our implementation of ``GetMenu``. We will use the new value and the +description above to add a new item to the menu: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` @@ -756,9 +789,12 @@ the new menu item in the web application, and should be able to order it.
-Wrapping Up +Wrapping up =========== -In this tutorial, you used Smithy to build a full-stack application for a simple coffee shop. You wrote a Smithy model for a service based on a list of requirements. Afterwards, you configured builds using the ``smithy-build.json`` configuration, which you set up to code-generate a TypeScript server SDK and a TypeScript client. You implemented the service using the generated server SDK, and then made requests to it using the generated client. Finally, you used +In this tutorial, you used Smithy to build a full-stack application for a simple coffee shop. You wrote a Smithy model +for a service based on a list of requirements. Afterwards, you configured builds using the ``smithy-build.json`` +configuration, which you set up to code-generate a TypeScript server SDK and a TypeScript client. You implemented the +service using the generated server SDK, and then made requests to it using the generated client. Finally, you used the client in a web application to make requests from within the browser to our service. .. TOOO: what else? @@ -766,8 +802,8 @@ the client in a web application to make requests from within the browser to our --------- What now? --------- -We covered several topics in this tutorial, there is still so much to learn! Please check out the -following resources: +We covered several topics in this tutorial, but there is still so much to learn. For other examples of smithy projects, +please see the following repositories: * `awesome-smithy `_: A list of projects based in the smithy ecosystem -* `smithy-examples `_: A repository of example smithy projects \ No newline at end of file +* `smithy-examples `_: A repository of example smithy projects From a1038f7b82fcd0ed87a6cdfa70a2ae65a0219ec3 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Mon, 26 Aug 2024 10:20:35 -0700 Subject: [PATCH 05/16] Add init and app client section --- .../tutorials/full-stack-tutorial.rst | 134 ++++++++++++++---- 1 file changed, 104 insertions(+), 30 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index 7288e921afd..d771734ff50 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -9,7 +9,7 @@ order online, and be able to grab their coffee on the go. This application shoul allow the customer to order a coffee. To build this application, we will walk you through using Smithy to define a model for the coffee service, generate -code for a client and a server, and implement a front-end and back-end for the service. +code for the client and server, and implement the front-end and back-end for the service. .. tip:: This tutorial does not assume you are an expert in Smithy, but you may find it helpful to work through the @@ -62,10 +62,21 @@ The directory tree should look like: └── smithy ├── ... -The ``README.md`` file contains important information about the project, like its directory structure and how to build or -run it. However, for this tutorial, we will show you which commands to run, when to run them, and the expected outputs. +The ``README.md`` file contains important information about the project, like its directory structure and how to +build or run it. However, for this tutorial, we will show you which commands to run, when to run them, and +the expected outputs. -.. TODO: Provide the skeleton template or a git patch? +Please run the following command to set the project up, so you can follow this tutorial: + +.. code-block:: sh + :caption: ``/bin/sh`` + + make init + +.. hint:: If you get stuck and want to completely start over, run ``make reset && make init`` . + + Also, the complete project code for this tutorial is available + `here `_. Modeling the service ==================== @@ -80,8 +91,11 @@ The service should provide a few capabilities: Adding the service ------------------ The service shape is the entry-point of our API, and is where we define the operations our service exposes to a -consumer. First and foremost, let's define the initial service shape without any operations: +consumer. First and foremost, let's define the initial service shape without any operations. Open the main.smithy file +and add the following: +.. important:: For code blocks, the name of the current file is given in the top-left corner. + .. code-block:: smithy :caption: ``main.smithy`` @@ -145,7 +159,8 @@ of coffee-related structures: ------------------- Modeling operations ------------------- -With the shapes defined above, let's create an operation on our service for returning a menu to the consumer: +With the shapes defined above, let's create an operation for returning a menu to the consumer, and add it +to the service: .. code-block:: smithy :caption: ``main.smithy`` @@ -394,7 +409,7 @@ Run the build: smithy build model/ -The will should fail for the following reason: +The build will should fail for the following reason: .. code-block:: text :caption: ``failure message`` @@ -425,7 +440,7 @@ to our service, meaning all operations in the service may return it. Let's do th } -After fixing this and running the build, the TypeScript code-generator plugin will have created a new +After fixing this and running the build, the TypeScript code-generator plugin will create a new artifact under ``build/smithy/source/typescript-ssdk-codegen``. This artifact contains the generated server SDK (SSDK), which we will use in our back-end. @@ -458,16 +473,19 @@ the server imports the generated code from. Let's take a look at the core of the CreateOrder = async (input: CreateOrderServerInput): Promise => { console.log("received an order request...") + // TODO: Implement me! return; } GetMenu = async (input: GetMenuServerInput): Promise => { console.log("getting menu...") + // TODO: Implement me! return; } GetOrder = async (input: GetOrderServerInput): Promise => { console.log(`getting an order (${input.id})...`) + // TODO: Implement me! return; } @@ -516,7 +534,9 @@ each type of coffee: } For our menu, we have added a distinct item and description for each of our coffee enumerations (``CoffeeType``). -With our menu complete, let's implement order submission, ``CreateOrder``: +For ordering, we will maintain an order map to simulate a database that stores historical order information, +and an order queue to keep track of in-flight orders. The ``handleOrders`` method processes in-flight orders +and updates this queue. Let's implement order submission, or ``CreateOrder``: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` @@ -540,10 +560,8 @@ With our menu complete, let's implement order submission, ``CreateOrder``: } } -For ordering, we will maintain an order map to simulate a database that stores historical order information, -and an order queue to keep track of in-flight orders. The ``handleOrders`` method processes in-flight orders -and updates this queue. After submitting an order, ``GetOrder`` can retrieve its information. -Let's implement ``GetOrder``: +After submitting an order, we can retrieve its information from the order map. This information should be retrievable +through the ``GetOrder`` operation. Let's implement it now: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` @@ -581,21 +599,22 @@ This command will build and run the server. You should see the following output: Started server on port 3001... handling orders... -With the server running, let's test it by sending it requests. Open a new terminal and send a request to the ``/menu`` -route using ``cURL``: +With the server running, let's test it by sending it a request. Open a new terminal and send a request to the ``/menu`` +route using ``cURL``. This will send a request to the server, and the server should handle it with +the ``GetMenu`` operation: .. code-block:: sh :caption: ``/bin/sh`` curl localhost:3001/menu -You should see the output of the ``GetMenu`` operation we implemented. You may stop the server with ``CTRL + C`` in the -terminal where it is running. With the server implemented, we will move on to the client. +You should see the output of the ``GetMenu`` operation that we implemented above. You may stop the server with +``CTRL + C`` in the terminal where it is running. With the server implemented, we will move on to the client. Generating the client ===================== -To run the code-generation for a client, we will add another plugin to the ``smithy-build.json`` configuration file: +To run the code-generation for the client, we will add another plugin to the ``smithy-build.json`` configuration file: .. code-block:: json :caption: ``smithy-build.json`` @@ -703,17 +722,71 @@ Once you execute the command, you should see your order information: status: 'COMPLETED' // <--- the order status, which should be 'COMPLETED' } -You may stop the REPL and the server with ``CTRL + C`` in the respective terminals. We have +You may stop the REPL with ``CTRL + C`` in the terminal where it is running. We have tested each operation we implemented in the server using the generated client, and verified both the client and server communicate with each other. +------------------ +In the application +------------------ +Using the client in the application is not much different from what we just did. + +In the ``app/`` directory, there is a file, ``app/index.ts``, which contains code that instantiates and uses the +client. First, we create the client, and then we create helper methods to use the client: + +.. code-block:: TypeScript + :caption: ``app/index.ts`` + + import { CoffeeItem, CoffeeShop, CoffeeType, OrderStatus } from "@com.example/coffee-service-client"; + + ... + // create a coffee service client singleton and getter + let client: CoffeeShop + export function getClient(): CoffeeShop { + return client || (client = new CoffeeShop({ + endpoint: { + protocol: "http", + hostname: "localhost", + port: 3001, + path: "/" + } + })); + } + + // coffee service client helpers ------ + export async function getMenuItems(): Promise { + let items: CoffeeItem[] = [] + try { + const res = await getClient().getMenu(); + items = res.items || [] + } catch (err) { + console.log(err) + } + return items + } + ... + +We use these helper methods in our application to make requests to the server: + +.. code-block:: TypeScript + :caption: ``components/Menu.tsx`` + + ... + import MenuItem from "@/components/MenuItem"; + import { CoffeeItem } from "@com.example/coffee-service-client"; + + const Menu = async () => { + let menuItems: CoffeeItem[] = await getMenuItems(); + ... + + Running the application ======================= -Since we know how to generate and use the client and server, let's put it all together to use with a browser-based -web application. The web application exists under the ``app/`` directory. To build the application, use the -``build-app`` make target. The application will run when using the ``run-app`` target. Since this application uses the -generated client to make requests, the server must be ran alongside the app. For convenience, you may run both the web -application and the server in the same terminal: +Since we know how to generate and use the client and server, let's put it all together to use with the web application. +The application exists under the ``app/`` directory. To build the application, use the ``build-app`` make target. +The application will run when using the ``run-app`` target. Since this application uses the generated client to make +requests, the server must be ran alongside the app. For convenience, you may run both the web application and +the server in the same terminal: .. code-block:: sh :caption: ``/bin/sh`` @@ -721,16 +794,17 @@ application and the server in the same terminal: make run While running the application in this way is convenient, it will intertwine the output of the application and server. -If you would like to keep them separate, you should run the other targets (`run-server` and `run-app`). +If you would like to keep them separate, you should run the other targets (``run-server`` and ``run-app``). Using the method of your choice, launch the server and the application. While this application is incredibly simple, it shows how to integrate a smithy-generated client with an application running in the browser. + .. TODO: maybe another sentence on takeaways Making a change (optional) ========================== -Now, say we would like to add a new coffee to our menu. The new menu item should have the following details: +We would like to add a new coffee to our menu. The new menu item should have the following details: * type: COLD_BREW * description: A high-extraction and chilled form of coffee that has been cold-pressed. @@ -761,7 +835,7 @@ enumeration: Next, we need to update the server code to add a new item to the menu. First, we should build the model and run the code-generation for the server SDK to generate the new value. Run ``make build-ssdk``. After re-generating the -server SDK, we will make the change to our implementation of ``GetMenu``. We will use the new value and the +server SDK, we will make the change to the implementation of ``GetMenu``. We will use the new value and the description above to add a new item to the menu: .. code-block:: TypeScript @@ -782,7 +856,7 @@ description above to add a new item to the menu: } } -Finally, we will run the whole application to see our change (``make run``). After you run it, you should see +Finally, we will run the whole application to see the changes (``make run``). After you run it, you should see the new menu item in the web application, and should be able to order it. .. raw:: html @@ -794,8 +868,8 @@ Wrapping up In this tutorial, you used Smithy to build a full-stack application for a simple coffee shop. You wrote a Smithy model for a service based on a list of requirements. Afterwards, you configured builds using the ``smithy-build.json`` configuration, which you set up to code-generate a TypeScript server SDK and a TypeScript client. You implemented the -service using the generated server SDK, and then made requests to it using the generated client. Finally, you used -the client in a web application to make requests from within the browser to our service. +service using the generated server SDK, and made requests to it using the generated client. Finally, you used +the client in the web application to make requests from within the browser to our service. .. TOOO: what else? From 1bef153f54462ef02b2aa148c5603301adbd6272 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Tue, 27 Aug 2024 11:59:18 -0700 Subject: [PATCH 06/16] nits --- .../tutorials/full-stack-tutorial.rst | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index d771734ff50..1890674367d 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -4,7 +4,7 @@ Full Stack Application Overview ======== -In this tutorial, imagine we own a coffee shop. We would like to create a website for our customers to place an +For this tutorial, imagine we own a coffee shop. We would like to create a website for our customers to place an order online, and be able to grab their coffee on the go. This application should show the available coffees, and allow the customer to order a coffee. @@ -17,7 +17,7 @@ code for the client and server, and implement the front-end and back-end for the Setting up the project ====================== -This application will consist of a 4 major components: +This application will consist of 4 major components: 1. A model, which defines the service 2. A server, which handles requests for coffee @@ -91,8 +91,8 @@ The service should provide a few capabilities: Adding the service ------------------ The service shape is the entry-point of our API, and is where we define the operations our service exposes to a -consumer. First and foremost, let's define the initial service shape without any operations. Open the main.smithy file -and add the following: +consumer. First and foremost, let's define the initial service shape without any operations. Open the ``main.smithy`` +file and add the following: .. important:: For code blocks, the name of the current file is given in the top-left corner. @@ -357,8 +357,8 @@ for adding, updating, and removing coffees. In this case, coffee would be a prim Building the model ================== -The model for our coffee service is complete. Before we build the model, let's take a moment and learn how we configure -a build. The :ref:`smithy-build.json configuration file ` is how we instruct Smithy to build the +The model for our coffee service is complete. Before we build the model, let's take a moment and learn how to configure +the build. The :ref:`smithy-build.json configuration file ` is how we instruct Smithy to build the model. A :ref:`projection ` is a version of a model based on a set of :ref:`transformations ` and :ref:`plugins `. For our model, we will not configure any explicit projections, since Smithy will always build the ``source`` projection. The ``source`` projection does not have any transformations applied, and its output @@ -494,7 +494,7 @@ the server imports the generated code from. Let's take a look at the core of the These three methods are how we implement the core business logic of the service. They are exposed by the ``CoffeeShopService`` interface exported by the server SDK. This file already contains some of the underlying logic -for how our implementation will run: there is an orders queue, an orders map, and a order-handling procedure +for how our implementation will run: there is an orders queue, an orders map, and an order-handling procedure (``handleOrders``). We will use these to implement the operations for our service. Let's start with the simplest operation, ``GetMenu``. We will modify the operation to return a menu containing one coffee item for each type of coffee: @@ -527,7 +527,7 @@ each type of coffee: type: CoffeeType.ESPRESSO, description: "A highly concentrated form of coffee, brewed under high pressure.\n" + "Syrupy, thick liquid in a small serving size.\n" + - "Full bodied and intensly aromatic." + "Full bodied and intensely aromatic." } ] } @@ -582,7 +582,6 @@ through the ``GetOrder`` operation. Let's implement it now: }) } } -.. TODO: above snippet may need to be updated, verify errors With these operations implemented, our server is fully implemented. Let's build and run it: @@ -779,13 +778,12 @@ We use these helper methods in our application to make requests to the server: let menuItems: CoffeeItem[] = await getMenuItems(); ... - Running the application ======================= Since we know how to generate and use the client and server, let's put it all together to use with the web application. The application exists under the ``app/`` directory. To build the application, use the ``build-app`` make target. The application will run when using the ``run-app`` target. Since this application uses the generated client to make -requests, the server must be ran alongside the app. For convenience, you may run both the web application and +requests, the server must be run alongside the app. For convenience, you may run both the web application and the server in the same terminal: .. code-block:: sh @@ -797,11 +795,9 @@ While running the application in this way is convenient, it will intertwine the If you would like to keep them separate, you should run the other targets (``run-server`` and ``run-app``). Using the method of your choice, launch the server and the application. -While this application is incredibly simple, it shows how to integrate a smithy-generated client with an +While this application is simple, it shows how to integrate a smithy-generated client with an application running in the browser. -.. TODO: maybe another sentence on takeaways - Making a change (optional) ========================== We would like to add a new coffee to our menu. The new menu item should have the following details: @@ -866,12 +862,10 @@ the new menu item in the web application, and should be able to order it. Wrapping up =========== In this tutorial, you used Smithy to build a full-stack application for a simple coffee shop. You wrote a Smithy model -for a service based on a list of requirements. Afterwards, you configured builds using the ``smithy-build.json`` -configuration, which you set up to code-generate a TypeScript server SDK and a TypeScript client. You implemented the -service using the generated server SDK, and made requests to it using the generated client. Finally, you used -the client in the web application to make requests from within the browser to our service. - -.. TOOO: what else? +for a service based on a list of requirements. Afterward, you configured Smithy using the ``smithy-build.json`` +configuration, which you set up to code-generate a TypeScript server SDK and client. You implemented the +service using the server SDK, and made requests to it using the client. Finally, you used the client in the web +application to make requests from within the browser to our service. --------- What now? From 17c0e038eb5353c50064c747b09253e5f1775b32 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Tue, 27 Aug 2024 14:31:03 -0700 Subject: [PATCH 07/16] nits --- .../tutorials/full-stack-tutorial.rst | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index 1890674367d..fc02b207014 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -35,7 +35,7 @@ To follow this tutorial, you will need to install a few tools: ------ Set up ------ -Once you have these installed, the CLI has a useful command to easily bootstrap projects from +Once you have the prerequisites installed, the Smithy CLI has a useful command to easily bootstrap projects from `the repository of example smithy projects `_. Execute the following command to set up the initial project: @@ -115,8 +115,7 @@ file and add the following: We apply the ``@restJson1`` protocol trait to the service to indicate the service supports the :doc:`../aws/protocols/aws-restjson1-protocol`. Protocols define the rules and conventions for serializing and -de-serializing data when communicating between client and server. Protocols are a highly complex topic, so we will not -discuss them further in this tutorial. +de-serializing data when communicating between client and server. ------------- Modeling data @@ -201,8 +200,8 @@ With the ``restJson1`` protocol, the serialized response might look like the bel ------------------- Representing orders ------------------- -At this point, we still need to model the ordering functionality of our service. Let's create a new file, -``order.smithy``, which will hold definitions related to ordering. First, let's consider the following when +At this point, we still need to model the ordering functionality of our service. Let's modify the +``order.smithy`` file to hold definitions related to ordering. First, let's consider the following when modeling an order: 1. an order needs a unique identifier @@ -241,8 +240,8 @@ Let's compose these shapes together to create our representation of an order: /// An Order, which has an id, a status, and the type of coffee ordered structure Order { - id: Uuid, - coffeeType: CoffeeType, + id: Uuid + coffeeType: CoffeeType status: OrderStatus } @@ -359,15 +358,16 @@ Building the model ================== The model for our coffee service is complete. Before we build the model, let's take a moment and learn how to configure the build. The :ref:`smithy-build.json configuration file ` is how we instruct Smithy to build the -model. A :ref:`projection ` is a version of a model based on a set of :ref:`transformations ` -and :ref:`plugins `. For our model, we will not configure any explicit projections, since Smithy will always -build the ``source`` projection. The ``source`` projection does not have any transformations applied, and its output -includes the artifacts of plugins applied at the root. To build the model, run: +model. A :ref:`projection ` is a version of a model based on a set of :ref:`transformations `. +Plugins can be applied to a projection to produce artifacts based on its "version" of the model. +For our model, we will not configure any explicit projections, since Smithy always builds the ``source`` projection. +The ``source`` projection does not have any transformations applied, and its output includes the artifacts of +plugins applied at the root. To build the model, run: .. code-block:: sh :caption: ``/bin/sh`` - smithy build model/ + smithy build Building the model will render artifacts under the ``build/smithy`` directory. Under it, The ``source`` directory corresponds to the output (or "build artifacts") of the ``source`` projection. With the current configuration, Smithy @@ -456,7 +456,8 @@ lives). Let's try it now: make build-server This command will run the code-generation for the server SDK, and then build the server implementation (which uses -the server SDK). The server package is simple, and contains only two files under ``src/``: +the server SDK). The server package is located under the ``server/`` directory, and contains +only two files under ``src/``: * ``index.ts``: entry-point of the backend application, and where we initialize our service * ``CoffeeShop.ts``: implementation of a `CoffeeShopService` from the generated server SDK From b0f32424536334d3c2e33b755fac75da9afd400a Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Tue, 27 Aug 2024 15:21:43 -0700 Subject: [PATCH 08/16] updates --- .../tutorials/full-stack-tutorial.rst | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index fc02b207014..7701f3c0470 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -380,13 +380,14 @@ Generating the server SDK The server SDK is a code-generated component which provides built-in serialization, request-handling, and scaffolding (or "stubs") for our service. It facilitates the implementation of the service by providing these things, and allowing the implementer to focus on the business logic. Let's generate the server SDK -for our service by adding the following build configuration: +for our service by using the following build configuration: .. code-block:: json :caption: ``smithy-build.json`` { "version": "1.0", + "sources": ["model/"], "maven": { "dependencies": [ "software.amazon.smithy:smithy-aws-traits:1.50.0", @@ -472,19 +473,19 @@ the server imports the generated code from. Let's take a look at the core of the export class CoffeeShop implements CoffeeShopService<{}> { ... - CreateOrder = async (input: CreateOrderServerInput): Promise => { + CreateOrder(input: CreateOrderServerInput, context: CoffeeShopContext): CreateOrderServerOutput { console.log("received an order request...") // TODO: Implement me! return; } - GetMenu = async (input: GetMenuServerInput): Promise => { + GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): GetMenuServerOutput { console.log("getting menu...") // TODO: Implement me! return; } - GetOrder = async (input: GetOrderServerInput): Promise => { + GetOrder(input: GetOrderServerInput, context: CoffeeShopContext): GetOrderServerOutput { console.log(`getting an order (${input.id})...`) // TODO: Implement me! return; @@ -503,7 +504,7 @@ each type of coffee: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` - GetMenu = async (input: GetMenuServerInput): Promise => { + GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): GetMenuServerOutput { console.log("getting menu...") return { items: [ @@ -542,7 +543,7 @@ and updates this queue. Let's implement order submission, or ``CreateOrder``: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` - CreateOrder = async (input: CreateOrderServerInput): Promise => { + CreateOrder(input: CreateOrderServerInput, context: CoffeeShopContext): CreateOrderServerOutput { console.log("received an order request...") const order = { orderId: randomUUID(), @@ -550,8 +551,8 @@ and updates this queue. Let's implement order submission, or ``CreateOrder``: status: OrderStatus.IN_PROGRESS } - this.orders.set(order.orderId, order) - this.queue.push(order) + context.orders.set(order.orderId, order) + context.queue.push(order) console.log(`created order: ${JSON.stringify(order)}`) return { @@ -567,10 +568,10 @@ through the ``GetOrder`` operation. Let's implement it now: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` - GetOrder = async (input: GetOrderServerInput): Promise => { + GetOrder(input: GetOrderServerInput, context: CoffeeShopContext): GetOrderServerOutput { console.log(`getting an order (${input.id})...`) - if (this.orders.has(input.id)) { - const order = this.orders.get(input.id) + if (context.orders.has(input.id)) { + const order = context.orders.get(input.id) return { id: order.orderId, coffeeType: order.coffeeType, @@ -579,7 +580,8 @@ through the ``GetOrder`` operation. Let's implement it now: } else { console.log(`order (${input.id}) does not exist.`) throw new OrderNotFound({ - message: `order ${input.id} not found.` + message: `order ${input.id} not found.`, + orderId: input.id }) } } @@ -838,7 +840,7 @@ description above to add a new item to the menu: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` - GetMenu = async (input: GetMenuServerInput): Promise => { + GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): GetMenuServerOutput { console.log("getting menu...") return { items: [ From 2f00ea69a515d9a5c7363bd93a84cc370dc64d58 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Wed, 28 Aug 2024 10:46:02 -0700 Subject: [PATCH 09/16] fixes --- .../tutorials/full-stack-tutorial.rst | 59 ++++++++++++------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index 7701f3c0470..b11616771f6 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -110,7 +110,7 @@ file and add the following: @title("Coffee Shop Service") @restJson1 service CoffeeShop { - version: "2024-04-04" + version: "2024-08-23" } We apply the ``@restJson1`` protocol trait to the service to indicate the service supports the @@ -166,7 +166,7 @@ to the service: ... service CoffeeShop { - version: "2024-04-04" + version: "2024-08-23" operations: [ GetMenu ] @@ -408,7 +408,7 @@ Run the build: .. code-block:: sh :caption: ``/bin/sh`` - smithy build model/ + smithy build The build will should fail for the following reason: @@ -441,9 +441,9 @@ to our service, meaning all operations in the service may return it. Let's do th } -After fixing this and running the build, the TypeScript code-generator plugin will create a new -artifact under ``build/smithy/source/typescript-ssdk-codegen``. This artifact contains the generated -server SDK (SSDK), which we will use in our back-end. +After fixing this, run the build command again. The build should now succeed. The TypeScript code-generator +plugin will create a new artifact under ``build/smithy/source/typescript-ssdk-codegen``. This artifact contains +the generated server SDK (SSDK), which we will use in our back-end. Implementing the server ======================= @@ -473,19 +473,19 @@ the server imports the generated code from. Let's take a look at the core of the export class CoffeeShop implements CoffeeShopService<{}> { ... - CreateOrder(input: CreateOrderServerInput, context: CoffeeShopContext): CreateOrderServerOutput { + async CreateOrder(input: CreateOrderServerInput, context: CoffeeShopContext): Promise { console.log("received an order request...") // TODO: Implement me! return; } - GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): GetMenuServerOutput { + async GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): Promise { console.log("getting menu...") // TODO: Implement me! return; } - GetOrder(input: GetOrderServerInput, context: CoffeeShopContext): GetOrderServerOutput { + async GetOrder(input: GetOrderServerInput, context: CoffeeShopContext): Promise { console.log(`getting an order (${input.id})...`) // TODO: Implement me! return; @@ -504,7 +504,7 @@ each type of coffee: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` - GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): GetMenuServerOutput { + async GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): Promise { console.log("getting menu...") return { items: [ @@ -543,7 +543,7 @@ and updates this queue. Let's implement order submission, or ``CreateOrder``: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` - CreateOrder(input: CreateOrderServerInput, context: CoffeeShopContext): CreateOrderServerOutput { + async CreateOrder(input: CreateOrderServerInput, context: CoffeeShopContext): Promise { console.log("received an order request...") const order = { orderId: randomUUID(), @@ -568,7 +568,7 @@ through the ``GetOrder`` operation. Let's implement it now: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` - GetOrder(input: GetOrderServerInput, context: CoffeeShopContext): GetOrderServerOutput { + async GetOrder(input: GetOrderServerInput, context: CoffeeShopContext): Promise { console.log(`getting an order (${input.id})...`) if (context.orders.has(input.id)) { const order = context.orders.get(input.id) @@ -624,7 +624,11 @@ To run the code-generation for the client, we will add another plugin to the ``s { // ... "plugins": { - // ... + "typescript-ssdk-codegen": { + "package" : "@com.example/coffee-service-server", + "packageVersion": "0.0.1" + }, + // add the client codegen plugin "typescript-client-codegen": { "package": "@com.example/coffee-service-client", "packageVersion": "0.0.1" @@ -637,7 +641,7 @@ Run the build: .. code-block:: sh :caption: ``/bin/sh`` - smithy build model/ + smithy build Similar to the server SDK, Smithy will generate the TypeScript client artifacts under the ``build/smithy/source/typescript-client-codegen`` directory. We will use this client to make calls to our backend @@ -724,7 +728,7 @@ Once you execute the command, you should see your order information: status: 'COMPLETED' // <--- the order status, which should be 'COMPLETED' } -You may stop the REPL with ``CTRL + C`` in the terminal where it is running. We have +You may stop the REPL and server with ``CTRL + C`` in the respective terminals. We have tested each operation we implemented in the server using the generated client, and verified both the client and server communicate with each other. @@ -789,6 +793,8 @@ The application will run when using the ``run-app`` target. Since this applicati requests, the server must be run alongside the app. For convenience, you may run both the web application and the server in the same terminal: +.. important:: If you are already running the server, stop it before continuing past this point. + .. code-block:: sh :caption: ``/bin/sh`` @@ -798,8 +804,10 @@ While running the application in this way is convenient, it will intertwine the If you would like to keep them separate, you should run the other targets (``run-server`` and ``run-app``). Using the method of your choice, launch the server and the application. +Launch your browser and open http://localhost:3000. You should see the coffee shop web application. +Try ordering a coffee. When interacting with this application, you should see logs for both the client and server. While this application is simple, it shows how to integrate a smithy-generated client with an -application running in the browser. +application running in the browser. You may stop the application in the terminal and close the browser. Making a change (optional) ========================== @@ -823,7 +831,7 @@ enumeration: .. code-block:: smithy :caption: ``coffee.smithy`` - /// A enum describing the types of coffees available + /// An enum describing the types of coffees available enum CoffeeType { DRIP POUR_OVER @@ -840,7 +848,7 @@ description above to add a new item to the menu: .. code-block:: TypeScript :caption: ``CoffeeShop.ts`` - GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): GetMenuServerOutput { + async GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): Promise { console.log("getting menu...") return { items: [ @@ -855,8 +863,19 @@ description above to add a new item to the menu: } } -Finally, we will run the whole application to see the changes (``make run``). After you run it, you should see -the new menu item in the web application, and should be able to order it. +Now, make a similar change in the web application code to render a new image for the new type of coffee: + +.. code-block:: TypeScript + :caption: ``app/index.ts`` + + ... + case CoffeeType.COLD_BREW: + return "/cold-brew.png" + default: + ... + +Finally, we will run the whole application to see the changes (``make run``). After you run it and open +http://localhost:3000 in your browser, you should see the new menu item in the web application. .. raw:: html From e856c8fceace0c304d72ce6585ace2c6c3441d83 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Wed, 28 Aug 2024 11:07:51 -0700 Subject: [PATCH 10/16] feedbacks --- .../tutorials/full-stack-tutorial.rst | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index b11616771f6..40adb300787 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -48,7 +48,7 @@ After running this command, you should have the project in the ``full-stack-appl The directory tree should look like: .. code-block:: sh - :caption: ``/bin/sh`` + :caption: ``directory structure`` full-stack-application ├── Makefile @@ -73,7 +73,7 @@ Please run the following command to set the project up, so you can follow this t make init -.. hint:: If you get stuck and want to completely start over, run ``make reset && make init`` . +.. hint:: If you get stuck and want to completely start over, run ``make reset && make init``. Also, the complete project code for this tutorial is available `here `_. @@ -97,7 +97,7 @@ file and add the following: .. important:: For code blocks, the name of the current file is given in the top-left corner. .. code-block:: smithy - :caption: ``main.smithy`` + :caption: ``model/main.smithy`` $version: "2.0" @@ -127,7 +127,7 @@ of coffee-related structures: .. _full-stack-tutorial-operations: .. code-block:: smithy - :caption: ``coffee.smithy`` + :caption: ``model/coffee.smithy`` $version: "2.0" @@ -162,7 +162,7 @@ With the shapes defined above, let's create an operation for returning a menu to to the service: .. code-block:: smithy - :caption: ``main.smithy`` + :caption: ``model/main.smithy`` ... service CoffeeShop { @@ -211,7 +211,7 @@ modeling an order: With these requirements in mind, let's create the underlying data model: .. code-block:: smithy - :caption: ``order.smithy`` + :caption: ``model/order.smithy`` $version: "2.0" @@ -236,7 +236,7 @@ we defined earlier. Let's compose these shapes together to create our representation of an order: .. code-block:: smithy - :caption: ``order.smithy`` + :caption: ``model/order.smithy`` /// An Order, which has an id, a status, and the type of coffee ordered structure Order { @@ -252,7 +252,7 @@ lifecycle, while deleting an order would "end" it. In Smithy, we encapsulate the and its operations with :ref:`resources `. Instead of the above structure, let's define an order "resource": .. code-block:: smithy - :caption: ``order.smithy`` + :caption: ``model/order.smithy`` /// An Order resource, which has a unique id and describes an order by the type of coffee /// and the order's status @@ -268,7 +268,7 @@ represent the state of an instance. In this case, we will only define a subset o :ref:`lifecycle operations ` to keep it simple (``create`` and ``read``). Let's define those now: .. code-block:: smithy - :caption: ``order.smithy`` + :caption: ``model/order.smithy`` /// Create an order @idempotent @@ -326,7 +326,7 @@ When we define an operation which may return an explicit error, we should model :ref:`httpError trait ` to set a specific HTTP response status code when the service returns the error: .. code-block:: smithy - :caption: ``order.smithy`` + :caption: ``model/order.smithy`` /// An error indicating an order could not be found @httpError(404) @@ -339,7 +339,7 @@ When we define an operation which may return an explicit error, we should model Now that we have defined an order resource and its operations, we need to attach the resource to the service: .. code-block:: smithy - :caption: ``main.smithy`` + :caption: ``model/main.smithy`` ... service CoffeeShop { @@ -365,10 +365,14 @@ The ``source`` projection does not have any transformations applied, and its out plugins applied at the root. To build the model, run: .. code-block:: sh - :caption: ``/bin/sh`` + :caption: ``/bin/sh - smithy/`` smithy build +.. hint:: For ``smithy`` commands, you should be under the ``full-stack-application/smithy/`` directory. + + For ``make`` commands, you should be under the top-level directory (``full-stack-application/``) + Building the model will render artifacts under the ``build/smithy`` directory. Under it, The ``source`` directory corresponds to the output (or "build artifacts") of the ``source`` projection. With the current configuration, Smithy will produce the model in its :ref:`JSON AST representation `, and a ``sources`` directory which contains the @@ -406,7 +410,7 @@ for our service by using the following build configuration: Run the build: .. code-block:: sh - :caption: ``/bin/sh`` + :caption: ``/bin/sh - smithy/`` smithy build @@ -639,7 +643,7 @@ To run the code-generation for the client, we will add another plugin to the ``s Run the build: .. code-block:: sh - :caption: ``/bin/sh`` + :caption: ``/bin/sh - smithy/`` smithy build @@ -691,6 +695,8 @@ Let's try submitting an order: .. code-block:: TypeScript :caption: ``repl`` + import { CoffeeType } from '@com.example/coffee-service-client' + await client.createOrder({ coffeeType: "DRIP" }) After creating the order, you should get response like: @@ -885,7 +891,7 @@ Wrapping up =========== In this tutorial, you used Smithy to build a full-stack application for a simple coffee shop. You wrote a Smithy model for a service based on a list of requirements. Afterward, you configured Smithy using the ``smithy-build.json`` -configuration, which you set up to code-generate a TypeScript server SDK and client. You implemented the +configuration. You added plugins to code-generate a TypeScript server SDK and client. You implemented the service using the server SDK, and made requests to it using the client. Finally, you used the client in the web application to make requests from within the browser to our service. From 8a602de632108f2c9820b30bc6c0dacdbe2c3d95 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Wed, 28 Aug 2024 11:09:20 -0700 Subject: [PATCH 11/16] small fix --- docs/source-2.0/tutorials/full-stack-tutorial.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index 40adb300787..ab05316be3b 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -401,7 +401,7 @@ for our service by using the following build configuration: }, "plugins": { "typescript-ssdk-codegen": { - "package" : "@com.example/coffee-service-server", + "package" : "@com.example/coffee-shop-server", "packageVersion": "0.0.1" } } @@ -629,12 +629,12 @@ To run the code-generation for the client, we will add another plugin to the ``s // ... "plugins": { "typescript-ssdk-codegen": { - "package" : "@com.example/coffee-service-server", + "package" : "@com.example/coffee-shop-server", "packageVersion": "0.0.1" }, // add the client codegen plugin "typescript-client-codegen": { - "package": "@com.example/coffee-service-client", + "package": "@com.example/coffee-shop-client", "packageVersion": "0.0.1" } } @@ -683,7 +683,7 @@ following: .. code-block:: TypeScript :caption: ``repl`` - import { CoffeeShop } from '@com.example/coffee-service-client' + import { CoffeeShop } from '@com.example/coffee-shop-client' const client = new CoffeeShop({ endpoint: { protocol: 'http', hostname: 'localhost', port: 3001, path: '/' } }) @@ -695,7 +695,7 @@ Let's try submitting an order: .. code-block:: TypeScript :caption: ``repl`` - import { CoffeeType } from '@com.example/coffee-service-client' + import { CoffeeType } from '@com.example/coffee-shop-client' await client.createOrder({ coffeeType: "DRIP" }) @@ -749,7 +749,7 @@ client. First, we create the client, and then we create helper methods to use th .. code-block:: TypeScript :caption: ``app/index.ts`` - import { CoffeeItem, CoffeeShop, CoffeeType, OrderStatus } from "@com.example/coffee-service-client"; + import { CoffeeItem, CoffeeShop, CoffeeType, OrderStatus } from "@com.example/coffee-shop-client"; ... // create a coffee service client singleton and getter @@ -785,7 +785,7 @@ We use these helper methods in our application to make requests to the server: ... import MenuItem from "@/components/MenuItem"; - import { CoffeeItem } from "@com.example/coffee-service-client"; + import { CoffeeItem } from "@com.example/coffee-shop-client"; const Menu = async () => { let menuItems: CoffeeItem[] = await getMenuItems(); From 60426f7467f3fa2a1b978694e31cc51f52ef85da Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Wed, 28 Aug 2024 11:18:30 -0700 Subject: [PATCH 12/16] warning about windows --- docs/source-2.0/tutorials/full-stack-tutorial.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index ab05316be3b..0d5d4a423da 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -32,6 +32,8 @@ To follow this tutorial, you will need to install a few tools: * :doc:`Smithy CLI <../guides/smithy-cli/cli_installation>` * `Node.js (>= 16) `_ and `yarn `_ +.. warning:: This project was made for Mac/Linux, it may not build correctly on Windows. + ------ Set up ------ From a7a031b3d7085e4efbab5ac08022de5ed3f91c11 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Wed, 28 Aug 2024 13:16:01 -0700 Subject: [PATCH 13/16] fix typos --- docs/source-2.0/tutorials/full-stack-tutorial.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index 0d5d4a423da..eb847bbc1a6 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -433,7 +433,7 @@ the ``smithy.framework#ValidationException`` attached to it. We will fix this is to our service, meaning all operations in the service may return it. Let's do this now: .. code-block:: smithy - :caption: ``main.smithy`` + :caption: ``model/main.smithy`` use aws.protocols#restJson1 use smithy.framework#ValidationException @@ -476,7 +476,7 @@ the server imports the generated code from. Let's take a look at the core of the :caption: ``CoffeeShop.ts`` // An implementation of the service from the SSDK - export class CoffeeShop implements CoffeeShopService<{}> { + export class CoffeeShop implements CoffeeShopService { ... async CreateOrder(input: CreateOrderServerInput, context: CoffeeShopContext): Promise { From 7b86b4a9ec3a2f2589ea64a9a3ddb468c2eec233 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Wed, 28 Aug 2024 13:21:34 -0700 Subject: [PATCH 14/16] add paths --- .../tutorials/full-stack-tutorial.rst | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index eb847bbc1a6..cae9f65f06e 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -99,7 +99,7 @@ file and add the following: .. important:: For code blocks, the name of the current file is given in the top-left corner. .. code-block:: smithy - :caption: ``model/main.smithy`` + :caption: ``smithy/model/main.smithy`` $version: "2.0" @@ -129,7 +129,7 @@ of coffee-related structures: .. _full-stack-tutorial-operations: .. code-block:: smithy - :caption: ``model/coffee.smithy`` + :caption: ``smithy/model/coffee.smithy`` $version: "2.0" @@ -164,7 +164,7 @@ With the shapes defined above, let's create an operation for returning a menu to to the service: .. code-block:: smithy - :caption: ``model/main.smithy`` + :caption: ``smithy/model/main.smithy`` ... service CoffeeShop { @@ -213,7 +213,7 @@ modeling an order: With these requirements in mind, let's create the underlying data model: .. code-block:: smithy - :caption: ``model/order.smithy`` + :caption: ``smithy/model/order.smithy`` $version: "2.0" @@ -238,7 +238,7 @@ we defined earlier. Let's compose these shapes together to create our representation of an order: .. code-block:: smithy - :caption: ``model/order.smithy`` + :caption: ``smithy/model/order.smithy`` /// An Order, which has an id, a status, and the type of coffee ordered structure Order { @@ -254,7 +254,7 @@ lifecycle, while deleting an order would "end" it. In Smithy, we encapsulate the and its operations with :ref:`resources `. Instead of the above structure, let's define an order "resource": .. code-block:: smithy - :caption: ``model/order.smithy`` + :caption: ``smithy/model/order.smithy`` /// An Order resource, which has a unique id and describes an order by the type of coffee /// and the order's status @@ -270,7 +270,7 @@ represent the state of an instance. In this case, we will only define a subset o :ref:`lifecycle operations ` to keep it simple (``create`` and ``read``). Let's define those now: .. code-block:: smithy - :caption: ``model/order.smithy`` + :caption: ``smithy/model/order.smithy`` /// Create an order @idempotent @@ -328,7 +328,7 @@ When we define an operation which may return an explicit error, we should model :ref:`httpError trait ` to set a specific HTTP response status code when the service returns the error: .. code-block:: smithy - :caption: ``model/order.smithy`` + :caption: ``smithy/model/order.smithy`` /// An error indicating an order could not be found @httpError(404) @@ -341,7 +341,7 @@ When we define an operation which may return an explicit error, we should model Now that we have defined an order resource and its operations, we need to attach the resource to the service: .. code-block:: smithy - :caption: ``model/main.smithy`` + :caption: ``smithy/model/main.smithy`` ... service CoffeeShop { @@ -389,7 +389,7 @@ providing these things, and allowing the implementer to focus on the business lo for our service by using the following build configuration: .. code-block:: json - :caption: ``smithy-build.json`` + :caption: ``smithy/smithy-build.json`` { "version": "1.0", @@ -433,7 +433,7 @@ the ``smithy.framework#ValidationException`` attached to it. We will fix this is to our service, meaning all operations in the service may return it. Let's do this now: .. code-block:: smithy - :caption: ``model/main.smithy`` + :caption: ``smithy/model/main.smithy`` use aws.protocols#restJson1 use smithy.framework#ValidationException @@ -473,7 +473,7 @@ The ``ssdk/`` directory is a link to our generated server SDK, which is an outpu the server imports the generated code from. Let's take a look at the core of the coffee shop implementation: .. code-block:: TypeScript - :caption: ``CoffeeShop.ts`` + :caption: ``server/src/CoffeeShop.ts`` // An implementation of the service from the SSDK export class CoffeeShop implements CoffeeShopService { @@ -508,7 +508,7 @@ operation, ``GetMenu``. We will modify the operation to return a menu containing each type of coffee: .. code-block:: TypeScript - :caption: ``CoffeeShop.ts`` + :caption: ``server/src/CoffeeShop.ts`` async GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): Promise { console.log("getting menu...") @@ -547,7 +547,7 @@ and an order queue to keep track of in-flight orders. The ``handleOrders`` metho and updates this queue. Let's implement order submission, or ``CreateOrder``: .. code-block:: TypeScript - :caption: ``CoffeeShop.ts`` + :caption: ``server/src/CoffeeShop.ts`` async CreateOrder(input: CreateOrderServerInput, context: CoffeeShopContext): Promise { console.log("received an order request...") @@ -572,7 +572,7 @@ After submitting an order, we can retrieve its information from the order map. T through the ``GetOrder`` operation. Let's implement it now: .. code-block:: TypeScript - :caption: ``CoffeeShop.ts`` + :caption: ``server/src/CoffeeShop.ts`` async GetOrder(input: GetOrderServerInput, context: CoffeeShopContext): Promise { console.log(`getting an order (${input.id})...`) @@ -625,7 +625,7 @@ Generating the client To run the code-generation for the client, we will add another plugin to the ``smithy-build.json`` configuration file: .. code-block:: json - :caption: ``smithy-build.json`` + :caption: ``smithy/smithy-build.json`` { // ... @@ -749,7 +749,7 @@ In the ``app/`` directory, there is a file, ``app/index.ts``, which contains cod client. First, we create the client, and then we create helper methods to use the client: .. code-block:: TypeScript - :caption: ``app/index.ts`` + :caption: ``app/app/index.ts`` import { CoffeeItem, CoffeeShop, CoffeeType, OrderStatus } from "@com.example/coffee-shop-client"; @@ -783,7 +783,7 @@ client. First, we create the client, and then we create helper methods to use th We use these helper methods in our application to make requests to the server: .. code-block:: TypeScript - :caption: ``components/Menu.tsx`` + :caption: ``app/components/Menu.tsx`` ... import MenuItem from "@/components/MenuItem"; @@ -837,7 +837,7 @@ To add a new coffee, we will first make a change to our model. We need to add a enumeration: .. code-block:: smithy - :caption: ``coffee.smithy`` + :caption: ``smithy/model/coffee.smithy`` /// An enum describing the types of coffees available enum CoffeeType { @@ -854,7 +854,7 @@ server SDK, we will make the change to the implementation of ``GetMenu``. We wil description above to add a new item to the menu: .. code-block:: TypeScript - :caption: ``CoffeeShop.ts`` + :caption: ``server/src/CoffeeShop.ts`` async GetMenu(input: GetMenuServerInput, context: CoffeeShopContext): Promise { console.log("getting menu...") @@ -874,7 +874,7 @@ description above to add a new item to the menu: Now, make a similar change in the web application code to render a new image for the new type of coffee: .. code-block:: TypeScript - :caption: ``app/index.ts`` + :caption: ``app/app/index.ts`` ... case CoffeeType.COLD_BREW: From 44bf11b980725d715006c5b5a469615730aa903e Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Wed, 28 Aug 2024 13:29:19 -0700 Subject: [PATCH 15/16] nits --- docs/source-2.0/tutorials/full-stack-tutorial.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index cae9f65f06e..a241bfb2033 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -371,9 +371,9 @@ plugins applied at the root. To build the model, run: smithy build -.. hint:: For ``smithy`` commands, you should be under the ``full-stack-application/smithy/`` directory. +.. hint:: For ``smithy`` commands, you should be under the ``full-stack-application/smithy`` directory. - For ``make`` commands, you should be under the top-level directory (``full-stack-application/``) + For ``make`` commands, you should be under the top-level directory (``full-stack-application``) Building the model will render artifacts under the ``build/smithy`` directory. Under it, The ``source`` directory corresponds to the output (or "build artifacts") of the ``source`` projection. With the current configuration, Smithy @@ -463,13 +463,13 @@ lives). Let's try it now: make build-server This command will run the code-generation for the server SDK, and then build the server implementation (which uses -the server SDK). The server package is located under the ``server/`` directory, and contains -only two files under ``src/``: +the server SDK). The server package is located under the ``server`` directory, and contains +only two files under ``src``: * ``index.ts``: entry-point of the backend application, and where we initialize our service * ``CoffeeShop.ts``: implementation of a `CoffeeShopService` from the generated server SDK -The ``ssdk/`` directory is a link to our generated server SDK, which is an output of the smithy build. This is where +The ``ssdk`` directory is a link to our generated server SDK, which is an output of the smithy build. This is where the server imports the generated code from. Let's take a look at the core of the coffee shop implementation: .. code-block:: TypeScript @@ -745,7 +745,7 @@ In the application ------------------ Using the client in the application is not much different from what we just did. -In the ``app/`` directory, there is a file, ``app/index.ts``, which contains code that instantiates and uses the +In the ``app`` directory, there is a file, ``app/index.ts``, which contains code that instantiates and uses the client. First, we create the client, and then we create helper methods to use the client: .. code-block:: TypeScript @@ -796,7 +796,7 @@ We use these helper methods in our application to make requests to the server: Running the application ======================= Since we know how to generate and use the client and server, let's put it all together to use with the web application. -The application exists under the ``app/`` directory. To build the application, use the ``build-app`` make target. +The application exists under the ``app`` directory. To build the application, use the ``build-app`` make target. The application will run when using the ``run-app`` target. Since this application uses the generated client to make requests, the server must be run alongside the app. For convenience, you may run both the web application and the server in the same terminal: From 28b5ca349c6264768151a7540e66561f100630a6 Mon Sep 17 00:00:00 2001 From: Hayden Baker Date: Thu, 29 Aug 2024 09:56:19 -0700 Subject: [PATCH 16/16] address feedback --- .../tutorials/full-stack-tutorial.rst | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/source-2.0/tutorials/full-stack-tutorial.rst b/docs/source-2.0/tutorials/full-stack-tutorial.rst index a241bfb2033..e204651438d 100644 --- a/docs/source-2.0/tutorials/full-stack-tutorial.rst +++ b/docs/source-2.0/tutorials/full-stack-tutorial.rst @@ -6,7 +6,7 @@ Overview ======== For this tutorial, imagine we own a coffee shop. We would like to create a website for our customers to place an order online, and be able to grab their coffee on the go. This application should show the available coffees, and -allow the customer to order a coffee. +allow a customer to order a coffee. To build this application, we will walk you through using Smithy to define a model for the coffee service, generate code for the client and server, and implement the front-end and back-end for the service. @@ -19,10 +19,10 @@ Setting up the project ====================== This application will consist of 4 major components: -1. A model, which defines the service -2. A server, which handles requests for coffee -3. A client, which is generated from the model -4. A web application, which uses the client to make requests to the server +1. A model, which defines the service. +2. A server, which handles requests for coffee. +3. A client, which is generated from the model. +4. A web application, which uses the client to make requests to the server. -------------- Pre-requisites @@ -63,6 +63,7 @@ The directory tree should look like: │ ├── ... └── smithy ├── ... + ... The ``README.md`` file contains important information about the project, like its directory structure and how to build or run it. However, for this tutorial, we will show you which commands to run, when to run them, and @@ -85,9 +86,9 @@ Modeling the service With the basic framework for the project established, let's walk through how to model our coffee service. The service should provide a few capabilities: -* provide a menu of coffees -* provide the ability to order a coffee -* provide the ability to check the status of an order +* Provide a menu of coffees. +* Provide the ability to order a coffee. +* Provide the ability to check the status of an order. ------------------ Adding the service @@ -160,7 +161,7 @@ of coffee-related structures: ------------------- Modeling operations ------------------- -With the shapes defined above, let's create an operation for returning a menu to the consumer, and add it +With the shapes defined above, let's create an operation for returning the menu to the consumer, and add it to the service: .. code-block:: smithy @@ -206,9 +207,9 @@ At this point, we still need to model the ordering functionality of our service. ``order.smithy`` file to hold definitions related to ordering. First, let's consider the following when modeling an order: -1. an order needs a unique identifier -2. an order needs to have a status, such as "in-progress" or "completed" -3. an order needs to hold the coffee information (``CoffeeType``) +1. An order needs a unique identifier. +2. An order needs to have a status, such as "in-progress" or "completed". +3. An order needs to hold the coffee information (``CoffeeType``). With these requirements in mind, let's create the underlying data model: @@ -231,7 +232,7 @@ With these requirements in mind, let's create the underlying data model: } A universally unique identifier (or `"UUID" `_) should be -more than sufficient for our service. The order status is ``IN_PROGRESS`` (after submitting the order) or +sufficient for our service. The order status is ``IN_PROGRESS`` (after submitting the order) or ``COMPLETED`` (when the order is ready). We will represent the coffee order information with the ``CoffeeType`` shape we defined earlier. @@ -466,8 +467,8 @@ This command will run the code-generation for the server SDK, and then build the the server SDK). The server package is located under the ``server`` directory, and contains only two files under ``src``: -* ``index.ts``: entry-point of the backend application, and where we initialize our service -* ``CoffeeShop.ts``: implementation of a `CoffeeShopService` from the generated server SDK +* ``index.ts``: entry-point of the backend application, and where we initialize our service. +* ``CoffeeShop.ts``: implementation of a `CoffeeShopService` from the generated server SDK. The ``ssdk`` directory is a link to our generated server SDK, which is an output of the smithy build. This is where the server imports the generated code from. Let's take a look at the core of the coffee shop implementation: