Skip to content

ishutin/terseq

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

57 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Terseq: AWS DynamoDB Query Builder

codecov

This document provides a comprehensive guide on how to utilize the Terseq library to build and execute queries on AWS DynamoDB using the AWS SDK for PHP.

Features

Terseq supports building queries for the following DynamoDB operations:

Single-item operations

Query operations

Transactions

Batch

Why I should use this library?

AWS SDK for PHP is a powerful tool for working with AWS services, but it can be challenging to use due to its complexity. It requires a lot of boilerplate code to build and execute queries. Terseq simplifies this process by providing a fluent interface to build queries for DynamoDB operations. It also supports single-table design, which is a recommended practice for DynamoDB.

Installation

To install the Terseq package, run the following command in your project directory using Composer:

composer require aiotu/terseq

Usage

Initialize

Create client by AWS SDK and DatabaseManager

$client = new \Aws\DynamoDb\DynamoDbClient([
    'region' => 'us-west-2',
    'version' => 'latest',
]);

$manager = new \Terseq\DatabaseManager($client, new Marshaler());

Operations

GetItem

$manager->getItem()
    ->table(['Books', 'BookId'])
    ->pk('super-cool-id')
    ->dispatch();

PutItem

$manager->putItem()
    ->table(['Books', 'BookId'])
    ->item([
        'BookId' => 'super-cool-id',
        'Title' => 'Super Cool Book',
        'Author' => 'Super Cool Author',
    ])
    ->dispatch();

UpdateItem

$manager->updateItem()
    ->table(['Books', 'BookId'])
    ->pk('super-cool-id')
    ->set('Title', 'Super Cool Book Updated')
    ->set('Author', 'Super Cool Author Updated')
    ->dispatch();

DeleteItem

$manager->deleteItem()
    ->table(['Books', 'BookId'])
    ->pk('super-cool-id')
    ->dispatch();

Query

$result = $manager->query()
    ->table(['Books', 'BookId'])
    ->pk('super-cool-id')
    ->consistentRead()
    ->disaptch();

TransactGetItems

use Terseq\Builders\Operations\TransactGetItems\Operations\Get;

$manager->transactGetItems()
    ->get(
            [
                static fn (Get $get) => $get->pk('super-cool-id1'),
                static fn (Get $get) => $get->pk('super-cool-id2'),
            ], 
            table: ['Books', 'BookId'],
        )
    ->dispatch();

TransactWriteItems

use Terseq\Builders\Operations\TransactWriteItems\Operations\Delete;
use Terseq\Builders\Operations\TransactWriteItems\Operations\Put;
use Terseq\Builders\Operations\TransactWriteItems\Operations\Update;

$manager->transactWriteItems()
    ->put(
        [
            fn (Put $put) => $put->item([
                'BookId' => 'super-book1',
                'Author' => 'Unknown',
            ]),
            fn (Put $put) => $put->item([
                'BookId' => 'super-book-2',
                'Author' => 'Incognito',
            ]),
        ],
        table: ['Books', 'BookId'],
    )
    ->update(
        fn (Update $update) => $update
            ->pk('super-book-3')
            ->set('Author', 'Incognito'),
        table: ['Books', 'BookId'],
    )
    ->delete(
        fn (Delete $delete) => $delete->pk('super-book-4'),
        table: ['Books', 'BookId'],
    )
    ->dispatch();

BatchGetItem

$manager->batchGetItem()
    ->get(
        [
            'BookId' => 'super-book-1',
            'Author' => 'Unknown',
        ],
        table: ['Books', 'BookId'],
    )
    ->get(
        [
            'BookId' => 'super-book-2',
            'Author' => 'Incognito',
        ],
        table: ['Books', 'BookId'],
    )
    ->dispatch();

BatchWriteItem

$manager->batchWriteItem()
    ->put(
        [
            'BookId' => 'super-book-1',
            'Author' => 'Unknown',
        ],
        table: ['Books', 'BookId'],
    )
    ->put(
        [
            'BookId' => 'super-book-2',
            'Author' => 'Incognito',
        ],
        table: ['Books', 'BookId'],
    )
    ->delete(
        [
            'BookId' => 'super-book-3',
        ],
        table: ['Books', 'BookId'],
    )
    ->dispatch();

Table

Table as abject (recommended)

Example of using table object

use Terseq\Builders\Table;

class Books extends Table 
{
    public function getTableName(): string
    {
        return 'Books';
    }

    public function getKeys(): Keys
    {
        return new Keys(partitionKey: 'BookId', sortKey: null);
    }
}

Example with secondary indexes

use Terseq\Builders\Table;

class BooksTable extends Table 
{
    /**
     * Table name
     */
    public function getTableName(): string
    {
        return 'Books';
    }

    /**
     * Partition key and sort key (optional)
     */
    public function getKeys(): Keys
    {
        return new Keys(partitionKey: 'BookId', sortKey: 'ReleaseDate');
    }

    /**
     * Secondary index map (optional) also known as GSI and LSI
     */
    public function getSecondaryIndexMap(): ?array
    {
        return [
            'AuthorIndex' => new Keys(partitionKey: 'AuthorId', sortKey: 'AuthorBornYear'),
            'GenreIndex' => new Keys(partitionKey: 'GenreId', sortKey: 'GenreName'),
            'LsiExample' => new Keys(partitionKey: 'BookId', sortKey: 'AuthorBornYear'),
        ];
    }
}

Table as array

table(table: ['TableName', 'PartitionKey', 'SortKey']);

OR

table(table: ['TableName', 'PartitionKey']); // Sort key by default is null

OR

table(table: ['TableName']); // throws exception, because PartitionKey is required

OR

table(table: [
    'table' => 'TableName',
    'pk' => 'PartitionKey',
    'sk' => 'SortKey',
]);

Single-table design (recommended)

Library supports single-table design.

Example of using single-table design

$manager = new \Terseq\DatabaseManager(
    client: $client, 
    marshaler: new Marshaler(),
    singleTable: new class extends \Terseq\Builders\Table {
        public function getTableName(): string
        {
            return 'Books';
        }
        
        public function getKeys(): Keys
        {
            return new Keys(partitionKey: 'BookId');
        }
    },
);

That's all! Now you can build queries without passing table name and keys.

Usage

// Query
$manager->getItem()->pk('super-cool-id')->disaptch();

$manager->batchWriteItem()
    ->put(
        [
            'BookId' => 'super-book-1',
            'Author' => 'Unknown',
        ],
    )
    ->put(
        [
            'BookId' => 'super-book-2',
            'Author' => 'Incognito',
        ],
    )
    ->delete(
        [
            'BookId' => 'super-book-3',
        ],
    )
    ->dispatch();

Comparison with AWS SDK

Example of using AWS SDK

$client->updateItem(
    [
        'TableName' => 'Books',
        'UpdateExpression' => 'SET #Title = :title_0, #Author = :author_0',
        'ExpressionAttributeNames' =>
            [
                '#Title' => 'Title',
                '#Author' => 'Author',
            ],
        'ExpressionAttributeValues' =>
            [
                ':title_0' =>
                    [
                        'S' => 'Super Cool Book Updated',
                    ],
                ':author_0' =>
                    [
                        'S' => 'Super Cool Author Updated',
                    ],
            ],
        'Key' =>
            [
                'BookId' =>
                    [
                        'S' => 'super-cool-id',
                    ],
            ],
    ],
);

Example of using Terseq

for the same operation

$manager->updateItem()
    ->table(['Books', 'BookId'])
    ->pk('super-cool-id')
    ->set('Title', 'Super Cool Book Updated')
    ->set('Author', 'Super Cool Author Updated')
    ->dispatch();