Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,7 @@ include(":smithy-java-examples:integ")
// ---- Smithy-Rust examples ----
// templates
includeBuild("smithy-rs-examples/quickstart-rust")

// ---- Smithy-Kotlin examples ----
// templates
includeBuild("smithy-kotlin-examples/quickstart-kotlin")
2 changes: 2 additions & 0 deletions smithy-kotlin-examples/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Smithy Kotlin
The examples in this directory demonstrate the use of the [Smithy Kotlin](https://github.com/smithy-lang/smithy-kotlin) code generator for clients.
30 changes: 30 additions & 0 deletions smithy-kotlin-examples/quickstart-kotlin/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## Smithy-Kotlin Quickstart

This project provides a template to get started using [Smithy Kotlin](https://github.com/smithy-lang/smithy-kotlin)
to create Kotlin clients.

### Layout

- `client/`: Code generated Kotlin client that can call the server.
- `smithy/`: Common package for the service API model. Shared by both client and server.
- `server/`: Code generated Java Server that implements stubbed operations code-generated from the service model.

### Usage

To create a new project from this template, use the [Smithy CLI](https://smithy.io/2.0/guides/smithy-cli/index.html)
`init` command as follows:

```console
smithy init -t smithy-kotlin-quickstart
```

### Running and testing client

To run and test the client, first start a server by running `./gradlew :server:run` from the root of a project created from this
template. That will start the server running on port `8888`.

Once the server is running you can run the client application in the `client` subproject

```console
./gradlew :client:run
```
8 changes: 8 additions & 0 deletions smithy-kotlin-examples/quickstart-kotlin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

// Add repositories for all subprojects to resolve dependencies.
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
description = "CoffeeShop service client"

plugins {
application
kotlin("jvm") version "2.3.0"
// Executes smithy-build process to generate client code
id("software.amazon.smithy.gradle.smithy-base")
}

// Add generated client source code to the main sourceSet
afterEvaluate {
val clientPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "kotlin-codegen")
sourceSets.main.get().kotlin.srcDir(clientPath)
}

tasks.named("compileKotlin") {
dependsOn("smithyBuild")
}

dependencies {
// Code generators
compileOnly(libs.smithy.kotlin.aws.codegen)

// Service model
implementation(project(":smithy"))

// Client Dependencies
implementation(libs.smithy.kotlin.runtime.core)
implementation(libs.smithy.kotlin.smithy.client)
implementation(libs.smithy.kotlin.http.client)
implementation(libs.smithy.kotlin.telemetry.api)
implementation(libs.smithy.kotlin.telemetry.defaults)
implementation(libs.smithy.kotlin.rpcv2.protocol)
implementation(libs.smithy.kotlin.aws.protocol.core)
implementation(libs.smithy.kotlin.aws.signing.common)
implementation(libs.smithy.kotlin.serde)
implementation(libs.smithy.kotlin.serde.cbor)
implementation(libs.smithy.kotlin.http.client.engine.default)
implementation(libs.kotlinx.coroutines.core)
}

val optinAnnotations = listOf("kotlin.RequiresOptIn", "aws.smithy.kotlin.runtime.InternalApi")
kotlin.sourceSets.all {
optinAnnotations.forEach { languageSettings.optIn(it) }
}

application {
mainClass.set("io.smithy.kotlin.client.example.MainKt")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": "1.0",
"plugins": {
"kotlin-codegen": {
"service": "com.example#CoffeeShop",
"sdkId": "CoffeeShop",
"package": {
"name": "io.smithy.kotlin.client.example",
"version": "0.0.1"
},
"build": {
"rootProject": false,
"generateDefaultBuildFiles": false
},
"api": {
"nullabilityCheckMode": "client"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.smithy.kotlin.client.example

import aws.smithy.kotlin.runtime.client.endpoints.Endpoint
import io.smithy.kotlin.client.example.endpoints.CoffeeShopEndpointProvider
import io.smithy.kotlin.client.example.model.CoffeeType
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import java.util.logging.Logger
import kotlin.time.Duration.Companion.seconds

fun main(): Unit = runBlocking {
val logger: Logger = Logger.getLogger("SmithyKotlinClient")

CoffeeShopClient {
endpointProvider = CoffeeShopEndpointProvider {
Endpoint("http://localhost:8888")
}
}.use { client ->
logger.info("Creating coffee order")

val createOrderResponse = client.createOrder {
coffeeType = CoffeeType.Latte
}

logger.info("Created order with ID: ${createOrderResponse.id}")

logger.info("Waiting for order to complete.")
delay(5.seconds)

logger.info("Checking order status")
val orderStatus = client.getOrder {
id = createOrderResponse.id
}.status

logger.info("Order status: $orderStatus")
Comment on lines +27 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can make this a little more comprehensive example by explicitly waiting until the order status is OrderStatus.COMPLETED

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[versions]
smithyVersion="1.63.0"
smithyJavaVersion="0.0.1"
smithyKotlinCodegenVersion="0.35.25"
smithyKotlinRuntimeVersion="1.5.25"
coroutinesCoreVersion="1.10.2"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: version catalogs typically use kebab-case instead of camelCase


[libraries]
smithy-aws-traits = { module="software.amazon.smithy:smithy-aws-traits", version.ref = "smithyVersion" }

smithy-java-plugins = { module="software.amazon.smithy.java.codegen:plugins", version.ref = "smithyJavaVersion" }
smithy-java-server-netty = { module="software.amazon.smithy.java:server-netty", version.ref = "smithyJavaVersion" }
smithy-java-aws-server-restjson = { module="software.amazon.smithy.java:aws-server-restjson", version.ref = "smithyJavaVersion" }
smithy-java-aws-server-rpcv2-cbor = { module="software.amazon.smithy.java:server-rpcv2-cbor", version.ref = "smithyJavaVersion" }

smithy-kotlin-aws-codegen = { module = "software.amazon.smithy.kotlin:smithy-aws-kotlin-codegen", version.ref = "smithyKotlinCodegenVersion" }
smithy-kotlin-runtime-core = { module = "aws.smithy.kotlin:runtime-core", version.ref = "smithyKotlinRuntimeVersion" }
smithy-kotlin-smithy-client = { module = "aws.smithy.kotlin:smithy-client", version.ref = "smithyKotlinRuntimeVersion" }
smithy-kotlin-http-client = { module = "aws.smithy.kotlin:http-client", version.ref = "smithyKotlinRuntimeVersion" }
smithy-kotlin-telemetry-api = { module = "aws.smithy.kotlin:telemetry-api", version.ref = "smithyKotlinRuntimeVersion" }
smithy-kotlin-telemetry-defaults = { module = "aws.smithy.kotlin:telemetry-defaults", version.ref = "smithyKotlinRuntimeVersion" }
smithy-kotlin-rpcv2-protocol = { module = "aws.smithy.kotlin:smithy-rpcv2-protocols", version.ref = "smithyKotlinRuntimeVersion" }
smithy-kotlin-aws-protocol-core = { module = "aws.smithy.kotlin:aws-protocol-core", version.ref = "smithyKotlinRuntimeVersion" }
smithy-kotlin-aws-signing-common = { module = "aws.smithy.kotlin:aws-signing-common", version.ref = "smithyKotlinRuntimeVersion" }
smithy-kotlin-serde = { module = "aws.smithy.kotlin:serde", version.ref = "smithyKotlinRuntimeVersion" }
smithy-kotlin-serde-cbor = { module = "aws.smithy.kotlin:serde-cbor", version.ref = "smithyKotlinRuntimeVersion" }
smithy-kotlin-http-client-engine-default = { module = "aws.smithy.kotlin:http-client-engine-default", version.ref = "smithyKotlinRuntimeVersion" }

kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutinesCoreVersion" }
4 changes: 4 additions & 0 deletions smithy-kotlin-examples/quickstart-kotlin/license.txt
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need it for the Java Kotlin server to work. When I remove it I get:

Projection source failed: software.amazon.smithy.codegen.core.CodegenException: Header file /Users/aoperez/Documents/kotlinSdkWork/smithyExamples/smithy-examples/smithy-kotlin-examples/quickstart-kotlin/server/../license.txt does not exist.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*
* Example file license header.
* File header line two
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
description = "Coffee shop service server implementation"

plugins {
`java-library`
application
// Executes smithy-build process to generate server stubs
id("software.amazon.smithy.gradle.smithy-base")
}

dependencies {
// Code generators
smithyBuild(libs.smithy.java.plugins)

// Service model
implementation(project(":smithy"))

// Server dependencies
// Adds an HTTP server implementation based on netty
implementation(libs.smithy.java.server.netty)
// Adds a server implementation of the `RestJson1` protocol
implementation(libs.smithy.java.aws.server.restjson)
// Adds a server implementation of the `Rpcv2Cbor` protocol
implementation(libs.smithy.java.aws.server.rpcv2.cbor)
}

// Add generated source code to the compilation sourceSet
afterEvaluate {
val serverPath = smithy.getPluginProjectionPath(smithy.sourceProjection.get(), "java-server-codegen")
sourceSets.main.get().java.srcDir(serverPath)
}

tasks.named("compileJava") {
dependsOn("smithyBuild")
}

// Use the application plugin to start the service via the `run` task.
application {
mainClass = "io.smithy.kotlin.server.example.CoffeeShopService"
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"version": "1.0",
"plugins": {
"java-server-codegen": {
"service": "com.example#CoffeeShop",
"namespace": "io.smithy.kotlin.server.example",
"headerFile": "../license.txt"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*/

package io.smithy.kotlin.server.example;

import io.smithy.kotlin.server.example.service.CoffeeShop;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import software.amazon.smithy.java.server.Server;

public class CoffeeShopService implements Runnable {
private static final Logger LOGGER = Logger.getLogger(CoffeeShopService.class.getName());

public static void main(String... args) throws InterruptedException, ExecutionException {
new CoffeeShopService().run();
}

@Override
public void run() {
Server server = Server.builder()
.endpoints(URI.create("http://localhost:8888"))
.addService(
CoffeeShop.builder()
.addCreateOrderOperation(new CreateOrder())
.addGetMenuOperation(new GetMenu())
.addGetOrderOperation(new GetOrder())
.build()
)
.build();
LOGGER.info("Starting server...");
server.start();
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
LOGGER.info("Stopping server...");
try {
server.shutdown().get();
} catch (InterruptedException | ExecutionException ex) {
throw new RuntimeException(ex);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*/

package io.smithy.kotlin.server.example;

import io.smithy.kotlin.server.example.model.CreateOrderInput;
import io.smithy.kotlin.server.example.model.CreateOrderOutput;
import io.smithy.kotlin.server.example.model.OrderStatus;
import io.smithy.kotlin.server.example.service.CreateOrderOperation;
import java.util.UUID;

import java.util.logging.Logger;
import software.amazon.smithy.java.server.RequestContext;

/**
* Create an order for a coffee.
*/
final class CreateOrder implements CreateOrderOperation {
private static final Logger LOGGER = Logger.getLogger(CreateOrder.class.getName());

@Override
public CreateOrderOutput createOrder(CreateOrderInput input, RequestContext context) {
var id = UUID.randomUUID();
OrderTracker.putOrder(new Order(id, input.coffeeType(), OrderStatus.IN_PROGRESS));

LOGGER.info("Created order " + id + " for a " + input.coffeeType());

return CreateOrderOutput.builder()
.id(id.toString())
.coffeeType(input.coffeeType())
.status(OrderStatus.IN_PROGRESS)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: MIT-0
*/

package io.smithy.kotlin.server.example;

import io.smithy.kotlin.server.example.model.CoffeeItem;
import io.smithy.kotlin.server.example.model.CoffeeType;
import io.smithy.kotlin.server.example.model.GetMenuInput;
import io.smithy.kotlin.server.example.model.GetMenuOutput;
import io.smithy.kotlin.server.example.service.GetMenuOperation;
import java.util.List;

import software.amazon.smithy.java.server.RequestContext;

/**
* Returns the menu for the coffee shop
*/
final class GetMenu implements GetMenuOperation {
private static final List<CoffeeItem> MENU = List.of(
CoffeeItem.builder()
.typeMember(CoffeeType.DRIP)
.description("""
A clean-bodied, rounder, and more simplistic flavour profile.
Often praised for mellow and less intense notes.
Far less concentrated than espresso.
""")
.build(),
CoffeeItem.builder()
.typeMember(CoffeeType.POUR_OVER)
.description("""
Similar to drip coffee, but with a process that brings out more subtle nuances in flavor.
More concentrated than drip, but less than espresso.
""")
.build(),
CoffeeItem.builder()
.typeMember(CoffeeType.LATTE)
.description("""
A creamier, milk-based drink made with espresso.
A subtle coffee taste, with smooth texture.
High milk-to-coffee ratio.
""")
.build(),
CoffeeItem.builder()
.typeMember(CoffeeType.ESPRESSO)
.description("""
A highly concentrated form of coffee, brewed under high pressure.
Syrupy, thick liquid in a small serving size.
Full bodied and intensely aromatic.
""")
.build()
);

@Override
public GetMenuOutput getMenu(GetMenuInput input, RequestContext context) {
return GetMenuOutput.builder().items(MENU).build();
}
}
Loading
Loading