NAME

MooseX::Storage::IO::AmazonDynamoDB - Store and retrieve Moose objects to AWS's DynamoDB, via MooseX::Storage.

SYNOPSIS

First, create a table in DynamoDB. Currently only single-keyed tables are supported.

aws dynamodb create-table \
  --table-name my_docs \
  --key-schema "AttributeName=doc_id,KeyType=HASH" \
  --attribute-definitions "AttributeName=doc_id,AttributeType=S" \
  --provisioned-throughput "ReadCapacityUnits=2,WriteCapacityUnits=2"

Then, configure your Moose class via a call to Storage:

package MyDoc;
use Moose;
use MooseX::Storage;

with Storage(io => [ 'AmazonDynamoDB' => {
    table_name => 'my_docs',
    key_attr   => 'doc_id',
    force_type => { doc_id => 'S' },
}]);

has 'doc_id'  => (is => 'ro', isa => 'Str', required => 1);
has 'title'   => (is => 'rw', isa => 'Str');
has 'body'    => (is => 'rw', isa => 'Str');
has 'tags'    => (is => 'rw', isa => 'ArrayRef');
has 'authors' => (is => 'rw', isa => 'HashRef');

1;

Now you can store/load your class to DyanmoDB:

use MyDoc;

# Create a new instance of MyDoc
my $doc = MyDoc->new(
    doc_id   => 'foo12',
    title    => 'Foo',
    body     => 'blah blah',
    tags     => [qw(horse yellow angry)],
    authors  => {
        jdoe => {
            name  => 'John Doe',
            email => 'jdoe@gmail.com',
            roles => [qw(author reader)],
        },
        bsmith => {
            name  => 'Bob Smith',
            email => 'bsmith@yahoo.com',
            roles => [qw(editor reader)],
        },
    },
);

# Save it to DynamoDB
$doc->store();

# Load the saved data into a new instance
my $doc2 = MyDoc->load('foo12');

# This should say 'Bob Smith'
print $doc2->authors->{bsmith}{name};

DESCRIPTION

MooseX::Storage::IO::AmazonDynamoDB is a Moose role that provides an io layer for MooseX::Storage to store/load your Moose objects to Amazon's DynamoDB NoSQL database service.

You should understand the basics of Moose, MooseX::Storage, and DynamoDB before using this module.

This module uses Paws as its client library to the DynamoDB service, via PawsX::DynamoDB::DocumentClient. By default it uses the Paws configuration defaults (region, credentials, etc.). You can customize this behavior - see "CLIENT CONFIGURATION".

At a bare minimum the consuming class needs to tell this role what table to use and what field to use as a primary key - see "table_name" and "key_attr".

BREAKING CHANGES IN v0.07

v0.07 transitioned the underlying DynamoDB client from Amazon::DynamoDB to Paws::Dynamodb, in order to stay more up-to-date with AWS features. Any existing code which customized the client configuration will break when upgrading to v0.07. Support for creating tables was also removed.

The following role parameters were removed: client_attr, client_builder_method, client_class, client_args_method, host, port, ssl, dynamodb_local, create_table_method.

The following attibutes were removed: dynamo_db_client

The following methods were removed: build_dynamo_db_client, dynamo_db_client_args, dynamo_db_create_table

The dynamo_db_client parameter to load() was removed, in favor of dynamodb_document_client.

The dynamo_db_client and async parameters to store() were removed.

Please see See "CLIENT CONFIGURATION" for details on how to configure your client in v0.07 and above.

PARAMETERS

There are many parameters you can set when consuming this role that configure it in different ways.

REQUIRED

key_attr

"key_attr" is a required parameter when consuming this role. It specifies an attribute in your class that will provide the primary key value for storing your object to DynamoDB. Currently only single primary keys are supported, or what DynamoDB calls "Hash Type Primary Key" (see their documentation). See the "SYNOPSIS" for an example.

OPTIONAL

table_name

Specifies the name of the DynamoDB table to use for your objects - see the example in the "SYNOPSIS". Alternatively, you can return the table name via a class method - see "dynamo_db_table_name".

table_name_method

By default, this role will add a method named 'dynamo_db_table_name' to your class (see below for method description). If you want to use a different name for this method (e.g., because it conflicts with an existing method), you can change it via this parameter.

force_type

Gets passed to Net::Amazon::DynamoDB::Marshaler when converting our packed data to DynamoDB format.

It is highly recommended that you set the types for any attributes that are part of a key (either key_attr, or an attribute that's part of an index). Read up on force_type in Net::Amazon::DynamoDB::Marshaler for more details.

document_client_attribute_name

By default, this role adds an attribute to your class named 'dynamodb_document_client' (see below for attribute description). If you want to use a different name for this attribute, you can change it via this parameter.

parameter document_client_builder

Allows customization of the PawsX::DynamoDB::DocumentClient object used to interact with DynamoDB. See "CLIENT CONFIGURATION" for more details.

ATTRIBUTES

dynamodb_document_client

This role adds an attribute named "dynamodb_document_client" to your consuming class. This attribute holds an instance of PawsX::DynamoDB::DocumentClient that will be used to communicate with the DynamoDB service.

You can change this attribute's name via the document_client_attribute_name parameter.

The attribute is lazily built via document_client_builder. See "CLIENT CONFIGURATION" for more details.

METHODS

Following are methods that will be added to your consuming class.

$obj->store()

Object method. Stores the packed Moose object to DynamoDb.

$obj = $class->load($key, [, dynamodb_document_client => $client ][, inject => { key => val, ... } ])

Class method. Queries DynamoDB with a primary key, and returns a new Moose object built from the resulting data. Returns undefined if they key could not be found in DyanmoDB.

The first argument is the primary key to use, and is required.

Optional parameters can be specified following the key:

dynamodb_document_client - Directly provide a PawsX::DynamoDB::DocumentClient object, instead of trying to build one using the class' configuration.
inject - supply additional arguments to the class' new function, or override ones from the resulting data.

dynamo_db_table_name

A class method that will return the table name to use. This method will be called if the "table_name" parameter is not set. So you could rewrite the Moose class in the "SYNOPSIS" like this:

package MyDoc;
use Moose;
use MooseX::Storage;

with Storage(io => [ 'AmazonDynamoDB' => {
    key_attr   => 'doc_id',
}]);

...

sub dynamo_db_table_name {
    my $class = shift;
    return $ENV{DEVELOPMENT} ? 'my_docs_dev' : 'my_docs';
}

You can change this method's name via the table_name_method parameter.

CLIENT CONFIGURATION

This role uses the 'dynamodb_document_client' attribute (assuming you didn't rename it via 'document_client_attribute_name') to interact with DynamoDB. This attribute is lazily built, and should hold an instance of PawsX::DynamoDB::DocumentClient.

The client is built by a coderef that is stored in the role's document_client_builder parameter. By default, that coderef is simply:

sub { return PawsX::DynamoDB::DocumentClient->new(); }

If you need to customize the client, you do so by providing your own builder coderef. For instance, you could set the region directly:

package MyDoc;
use Moose;
use MooseX::Storage;
use PawsX::DynamoDB::DocumentClient;

with Storage(io => [ 'AmazonDynamoDB' => {
    table_name              => 'my_docs',
    key_attr                => 'doc_id',
    document_client_builder => \&_build_document_client,
}]);

sub _build_document_client {
    my $region = get_my_region_somehow();
    return PawsX::DynamoDB::DocumentClient->new(region => $region);
}

See "DYNAMODB LOCAL" for an example of configuring our Paws client to run against a locally running dynamodb clone.

Note: the dynamodb_document_client attribute is not typed to a strict isa('PawsX::DynamoDB::DocumentClient'), but instead requires an object that has a 'get' and 'put' method. So you can provide some kind of mocked object, but that is left as an exercise to the reader - although examples are welcome!

DYNAMODB LOCAL

Here's an example of configuring your client to run against DynamoDB Local based on an environment variable. Make sure you've read "CLIENT CONFIGURATION". More information about DynamoDB Local can be found at AWS.

package MyDoc;
use Moose;
use MooseX::Storage;
use Paws;
use Paws::Credential::Explicit;
use PawsX::DynamoDB::DocumentClient;

with Storage(io => [ 'AmazonDynamoDB' => {
    table_name              => $table_name,
    key_attr                => 'doc_id',
    document_client_builder => \&_build_document_client,
}]);

sub _build_document_client {
    if ($ENV{DYNAMODB_LOCAL}) {
        my $dynamodb = Paws->service(
            'DynamoDB',
            region       => 'us-east-1',
            region_rules => [ { uri => 'http://localhost:8000'} ],
            credentials  => Paws::Credential::Explicit->new(
                access_key => 'XXXXXXXXX',
                secret_key => 'YYYYYYYYY',
            ),
            max_attempts => 2,
        );
        return PawsX::DynamoDB::DocumentClient->new(dynamodb => $dynamodb);
    }
    return PawsX::DynamoDB::DocumentClient->new();
}

NOTES

Strongly consistent reads

When executing load(), this module will always use strongly consistent reads when calling DynamoDB's GetItem operation. Read about DyanmoDB's consistency model in their FAQ to learn more.

Format level (freeze/thaw)

Note that this role does not need you to implement a 'format' level for your object, i.e freeze/thaw. You can add one if you want it for other purposes.

Pre-v0.07 objects

Before v0.07, this module stored objects to DyanmoDB using Amazon::DynamoDB. It worked around some issues with that module by serializing certain data types to JSON. Objects stored using this old system will be deserialized correctly.

SEE ALSO

Moose
MooseX::Storage
Amazon's DynamoDB Homepage
PawsX::DynamoDB::DocumentClient - DynamoDB client.
Paws - AWS library.

AUTHOR

Steve Caldwell <scaldwell@gmail.com>

COPYRIGHT

Copyright 2015- Steve Caldwell <scaldwell@gmail.com>

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

ACKNOWLEDGEMENTS

Thanks to Campus Explorer, who allowed me to release this code as open source.