In this workshop, we will write Java code to save objects to S3 and metadata about those objects to DynamoDB. The workshop assumes that you have an AWS account with the required credentials for interacting with S3 and DynamoDB, and that those credentials are configured on your workstation.
Our solution must meet the following requirements/specifications:
- We intend to store documents in S3 that contain a message with the current time
in milliseconds of the format
The current time in milliseconds is {current time}
- When we store a document, we will create a new user key that will be a UUID and associate that user UUID with the object in S3
- We need to be able to look up an S3 object key by a user UUID
First we will create an S3 bucket to store our documents
- In the AWS console, navigate to the S3 service
- Click "Create Bucket"
- Create a bucket named
nuvalence-workshop-{your name}
- Leave the rest of the default settings (No encryption, no public access, no versioning)
Next we will create a table for our object metadata
- In the AWS console, navigate to the DynamoDB service
- Click "Create Table"
- Specify
nuvalence-workshop-{your name}
as the table name - Specify
associatedUser
as the partition key with typeString
- Click the checkbox to add a sort key
- Specify
objectKey
as the sort key with typeString
- Uncheck the box for "Use default settings"
- Under "Auto Scaling", uncheck the box for both "Read capacity" and "Write capacity"
- Under "Provisioned capacity", set the RCU and WCU both to 1
CHECKPOINT - If you do not have an S3 bucket and DynamoDB, go back and start from the top of this workshop
We are now ready to build the class that will create our object in S3 and store the metadata in DynamoDB. You can copy the example solution from the workshop directory or begin with the empty base provided and try to implement it yourself!
The base already provides a constructor for our class that does three things:
- Instantiate an S3 client
- Instantiate a DynamoDB client
- Read environment variables corresponding to the names of both the bucket and table
Let's now begin building the logic.
Create the key information we want to store in S3
We need three pieces information, the current time, a random UUID and the key to be used for our object. Let's define them. Copy the following under the appropriate section in your class.
final long time = System.currentTimeMillis();
final UUID userGUID = UUID.randomUUID();
final String objectKey = "document/" + time + ".txt";
Create object metadata to be used when submitting our object to S3
The following section is necessary to create metadata about the object that we will be uploading to S3. It is different than the metadata being stored in DynamoDB (which is a business requirement).
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType("plain/text"); // set the content type of the file to be uploaded
metadata.addUserMetadata("x-amz-meta-title", "Hello"); // set a title for the file
Create InputStream object representing the content of our file
InputStream stream = new StringInputStream("The time is " + time);
Create a put object request and use the S3 client to put our object into S3 at the specified key
We now use the S3 client instantiated as part of the constructor of this class to upload our file to S3.
PutObjectRequest request = new PutObjectRequest(bucketName, objectKey, stream, metadata);
s3Client.putObject(request);
System.out.println("Successfully put object in S3 at key " + objectKey);
Create the object to be stored in DynamoDB
For the purpose of this exercise, we want to create a HashMap object of type <String,AttributeValue>
and add three values, associatedUser, objectKey and data. We then will put this object to our DynamoDB table.
item.put("associatedUser", new AttributeValue(userGUID.toString()));
item.put("objectKey", new AttributeValue(objectKey));
item.put("date", new AttributeValue().withN(Long.toString(time)));
Upload item to DynamoDB
Once we created our metadata object, it's time to upload it to DynamoDB.
System.out.println("Adding new item...");
this.dynamo.putItem(this.tableName, item);
System.out.println("Success! Added item to Dynamo for user " + userGUID + " and object key " + objectKey);
Print out the current state of both the bucket and the table
As a last step, we want to output the contents of the bucket and the dynamo table. We can retrieve all current items and loop through them using the following snippet.
System.out.println("Current bucket contents:");
ListObjectsV2Result result = s3Client.listObjectsV2(bucketName);
result.getObjectSummaries()
.stream()
.map(S3ObjectSummary::getKey)
.forEach(System.out::println);
System.out.println("Current table contents:");
ScanResult dbresult = dynamo.scan(tableName, Arrays.asList("associatedUser", "objectKey", "date"));
dbresult.getItems().stream()
.map(m -> String.format("%s - %s - %s", m.get("associatedUser").getS(), m.get("objectKey").getS(), m.get("date").getN()))
.forEach(System.out::println);
Running the Application
There are a few things you need to do in order to run the solution. If you started with the empty base class, start on step 1, otherwise go to step 2.
- Build your project using
./gradlew build
at the root of your source - Configure the environment variables with the name of your S3 bucket and DynamoDB table (BUCKET_NAME and TABLE_NAME)
- Run your application and remember to pass
docs
as an argument