Last active
July 7, 2024 19:42
-
-
Save jamiefdhurst/6fc5990c588f89520f136ffc1c3ccbe5 to your computer and use it in GitHub Desktop.
Updated dynamodb client to use query and sort key effectively.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"github.com/aws/aws-sdk-go-v2/aws" | |
"github.com/aws/aws-sdk-go-v2/config" | |
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue" | |
"github.com/aws/aws-sdk-go-v2/service/dynamodb" | |
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types" | |
) | |
const gameName string = "standard" | |
const tableName string = "scoreboard" | |
func createClient(endpoint string) *dynamodb.Client { | |
cfg, err := config.LoadDefaultConfig(context.TODO(), func(o *config.LoadOptions) error { | |
o.Region = "eu-west-1" | |
return nil | |
}) | |
if err != nil { | |
panic(err) | |
} | |
if endpoint == "" { | |
endpoint = "https://dynamodb.eu-west-1.amazonaws.com" | |
} | |
return dynamodb.NewFromConfig(cfg, func(o *dynamodb.Options) { | |
o.BaseEndpoint = &endpoint | |
}) | |
} | |
func createTable(c *dynamodb.Client) error { | |
_, err := c.CreateTable(context.TODO(), &dynamodb.CreateTableInput{ | |
TableName: aws.String(tableName), | |
BillingMode: types.BillingModePayPerRequest, | |
AttributeDefinitions: []types.AttributeDefinition{ | |
{ | |
AttributeName: aws.String("game"), | |
AttributeType: types.ScalarAttributeTypeS, | |
}, | |
{ | |
AttributeName: aws.String("score-name"), | |
AttributeType: types.ScalarAttributeTypeS, | |
}, | |
}, | |
KeySchema: []types.KeySchemaElement{ | |
{ | |
AttributeName: aws.String("game"), | |
KeyType: types.KeyTypeHash, | |
}, | |
{ | |
AttributeName: aws.String("score-name"), | |
KeyType: types.KeyTypeRange, | |
}, | |
}, | |
}) | |
return err | |
} | |
func save(c *dynamodb.Client, name string, value uint32) error { | |
_, err := c.PutItem(context.TODO(), &dynamodb.PutItemInput{ | |
TableName: aws.String(tableName), | |
Item: map[string]types.AttributeValue{ | |
"game": &types.AttributeValueMemberS{Value: gameName}, | |
"score-name": &types.AttributeValueMemberS{Value: fmt.Sprint(value) + "-" + name}, | |
"name": &types.AttributeValueMemberS{Value: name}, | |
"value": &types.AttributeValueMemberN{Value: fmt.Sprint(value)}, | |
}, | |
}) | |
return err | |
} | |
func get(c *dynamodb.Client) []Score { | |
out, err := c.Query(context.TODO(), &dynamodb.QueryInput{ | |
TableName: aws.String(tableName), | |
KeyConditionExpression: aws.String("game = :hashKey"), | |
ExpressionAttributeValues: map[string]types.AttributeValue{ | |
":hashKey": &types.AttributeValueMemberS{Value: gameName}, | |
}, | |
ScanIndexForward: aws.Bool(false), | |
}) | |
if err != nil { | |
panic(err) | |
} | |
var result []Score | |
attributevalue.UnmarshalListOfMaps(out.Items, &result) | |
return result | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"testing" | |
"github.com/aws/aws-sdk-go-v2/service/dynamodb" | |
containers "github.com/testcontainers/testcontainers-go" | |
"github.com/testcontainers/testcontainers-go/wait" | |
) | |
// Create the test container and wait for it to be ready | |
func setupContainer(t *testing.T) (string, func(t *testing.T)) { | |
ctx := context.Background() | |
req := containers.ContainerRequest{ | |
Image: "amazon/dynamodb-local:latest", | |
ExposedPorts: []string{"8000/tcp"}, | |
WaitingFor: wait.ForExposedPort(), | |
} | |
container, err := containers.GenericContainer(ctx, containers.GenericContainerRequest{ | |
ContainerRequest: req, | |
Started: true, | |
}) | |
if err != nil { | |
t.Fatalf("Could not start DynamoDB: %s", err) | |
} | |
endpoint, err := container.Endpoint(ctx, "") | |
if err != nil { | |
t.Fatalf("Could not get DynamoDB endpoint: %s", err) | |
} | |
return endpoint, func(t *testing.T) { | |
if err := container.Terminate(ctx); err != nil { | |
t.Fatalf("Could not stop DynamoDB: %s", err) | |
} | |
} | |
} | |
func connect(e string, t *testing.T) *dynamodb.Client { | |
client := createClient("http://" + e) | |
if err := createTable(client); err != nil { | |
t.Errorf("Expected to be able to create DynamoDB table, but received: %s", err) | |
} | |
return client | |
} | |
func fill(c *dynamodb.Client, t *testing.T) { | |
if err := save(c, "foo", 90); err != nil { | |
t.Errorf("Expected to be able to put item into DynamoDB, but received: %s", err) | |
} | |
if err := save(c, "bar", 75); err != nil { | |
t.Errorf("Expected to be able to put item into DynamoDB, but received: %s", err) | |
} | |
if err := save(c, "baz", 80); err != nil { | |
t.Errorf("Expected to be able to put item into DynamoDB, but received: %s", err) | |
} | |
} | |
func TestConnect(t *testing.T) { | |
ep, tearDown := setupContainer(t) | |
defer tearDown(t) | |
connect(ep, t) | |
} | |
func TestSave(t *testing.T) { | |
ep, tearDown := setupContainer(t) | |
defer tearDown(t) | |
c := connect(ep, t) | |
if err := save(c, "testing", 50); err != nil { | |
t.Errorf("Expected to be able to save item, but received error: %s", err) | |
} | |
} | |
func TestGet(t *testing.T) { | |
ep, tearDown := setupContainer(t) | |
defer tearDown(t) | |
c := connect(ep, t) | |
fill(c, t) | |
result := get(c) | |
if result[0].Name != "foo" { | |
t.Errorf("Expected entry 0 to be 'foo' but received: %s", result[0].Name) | |
} | |
if result[0].Value != 90 { | |
t.Errorf("Expected entry 0 to be '90' but received: %d", result[0].Value) | |
} | |
if result[1].Name != "baz" { | |
t.Errorf("Expected entry 1 to be 'baz' but received: %s", result[1].Name) | |
} | |
if result[1].Value != 80 { | |
t.Errorf("Expected entry 1 to be '80' but received: %d", result[1].Value) | |
} | |
if result[2].Name != "bar" { | |
t.Errorf("Expected entry 2 to be 'bar' but received: %s", result[2].Name) | |
} | |
if result[2].Value != 75 { | |
t.Errorf("Expected entry 2 to be '75' but received: %d", result[2].Value) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"log" | |
"net/http" | |
"os" | |
) | |
type Score struct { | |
Name string `json:"name"` | |
Value uint32 `json:"score"` | |
} | |
func main() { | |
dynamodb := createClient(os.Getenv("DYNAMODB_ENDPOINT")) | |
http.HandleFunc("/score", func(w http.ResponseWriter, r *http.Request) { | |
if r.Method == "POST" { | |
var s Score | |
// Try to decode the request body into the struct. If there is an error, | |
// respond to the client with the error message and a 400 status code. | |
err := json.NewDecoder(r.Body).Decode(&s) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusBadRequest) | |
return | |
} | |
err = save(dynamodb, s.Name, s.Value) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusInternalServerError) | |
return | |
} | |
w.WriteHeader(http.StatusCreated) | |
return | |
} | |
if r.Method == "GET" { | |
w.Header().Add("Content-Type", "application/json") | |
scores := get(dynamodb) | |
json.NewEncoder(w).Encode(scores) | |
return | |
} | |
http.Error(w, "Not found", http.StatusNotFound) | |
}) | |
log.Println("Listening on port 8080...") | |
log.Fatal(http.ListenAndServe(":8080", nil)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment