UNPKG

@benie/lambda-lib

Version:

Builders and tools for creating AWS Lambda function handlers that provides automation for things such as logging, instrumentation and parameters propagation

214 lines (188 loc) 7.97 kB
const { DynamoDB } = require('aws-sdk'); const uuid = require('uuid/v4'); const Exceptions = require('./repository-exceptions'); const log = require('../log'); /** * A generic dynamo db repository with basic CRUD operations */ class DynamoRepository { /** * Constructor * @param {*} tableName The name of the underlying dynamo table for this instance */ constructor(tableName, dynamoDbDocumentClient = new DynamoDB.DocumentClient()) { if (!tableName) throw new Error('Missing table name to create repository.'); this._tableName = tableName; this._dynamoDbDocClient = dynamoDbDocumentClient; } /** * Retrieve all records */ // FIXME esta implementação está quebrada. O Dynamo pagina retornos muito grandes (veja exemplo abaixo): /* module.exports.scanTable = async (table) => { log.info('Scanning table', {table}); let result = []; //TODO estamos colocando todo resultado no array; isso eventualmente pode estourar memória. ideal seria retornar um iterador (yield), stream, ou paginado. let offset; do { let params = { TableName: table }; if (offset) { log.debug('Last scan returned a partial result; resuming from offset', {uuid: offset}); params.ExclusiveStartKey = offset; } let res = await dynamoDb.scan(params).promise(); result = result.concat(res.Items); offset = res.LastEvaluatedKey; } while (offset) log.info('Scanning complete', {table, count: result.length}); return result; }; */ async getAll() { var params = { TableName: this._tableName }; log.debug('Database scan request', params); let response = await this._dynamoDbDocClient.scan(params).promise(); log.debug('Database scan response', response); if (response.Items) { return response.Items; } return []; } /** * Retrieve a record by its primary unique identifier (uuid) * @param {*Order's unique universal identifier} uuid If the partition key is the default name (uuid), just provide value. Else, provide an object {keyName: keyValue} */ async get(uuid) { if (!uuid) { return Promise.reject(new Exceptions.ParameterMissingException()); } const key = (typeof uuid === 'object') ? uuid : { uuid }; const params = { TableName: this._tableName, Key: key }; log.debug('Database get request', params); const response = await this._dynamoDbDocClient.get(params).promise(); log.debug('Database get response', response); if (response.Item) { return response.Item; } throw new Exceptions.NoEntityFoundException(); } /** * Queries a table and returns a set of matching records * @param {*} indexName The name of the table index used in the query * @param {*} fieldName The name of the key field * @param {*} key The value we are looking for in the key field */ // FIXME esta implementação está quebrada. O Dynamo pagina retornos muito grandes (veja o comentário em this.getAll()) async query(key, fieldName, indexName) { var params = { TableName: this._tableName, IndexName: indexName, KeyConditionExpression: '#field = :key', ExpressionAttributeNames: { '#field': fieldName }, ExpressionAttributeValues: { ':key': key }, ScanIndexForward: false }; if(!indexName) delete params.IndexName; log.debug('Database query request', params); let response = await this._dynamoDbDocClient.query(params).promise(); log.debug('Database query response', response); return response.Items; } /** * Updates (overwrites) an existing entity by it's primary key (uuid). * An entity must have an uuid and a version fields, and the table must have an 'uuid-index'. * @param {*} entity new version to be updated. * @throws {OptimisticLockException} if the current version is different from the provided entity. * @returns {*} The saved entity with the updated version */ async update(entity, key) { if (!(entity.uuid && entity.version)) { throw new Exceptions.UnidentifiedEntityException(); } const finalKey = key ? (typeof key === 'object') ? key : { uuid: key } : { uuid: entity.uuid }; const currentVersion = Number(entity.version); const newVersion = Date.now(); entity.version = newVersion; const params = { TableName: this._tableName, IndexName: 'uuid-index', Key: finalKey, Item: entity, // the parameters below garantee optimistic lock violations will throw "ConditionalCheckFailedException: The conditional request failed" error ConditionExpression: '#version = :currentVersion', ExpressionAttributeNames: { '#version': 'version' }, ExpressionAttributeValues: { ':currentVersion': currentVersion } }; try { log.debug('Database put request (update)', params); let result = await this._dynamoDbDocClient.put(params).promise(); log.debug('Database put response (update)', result); } catch (error) { if (('' + error) === 'ConditionalCheckFailedException: The conditional request failed') { log.debug('Database update aborted because concurrency (optimistick lock) conditional check failed', {message: error.message, stack: error.stack}); throw new Exceptions.OptimisticLockException(); } else throw error; } return entity; } /** * Creates a new entity record in the database and assigns a new uuid (if not provided) and version to it. * @param {*} entity The saved entity, with assigned uuid and version. */ async create(entity) { if (entity === null || typeof entity !== 'object') { throw new Exceptions.InvalidEntityException(); } entity.uuid = entity.uuid || uuid(); entity.version = Date.now(); entity.creation = new Date().toISOString(); var params = { TableName: this._tableName, Item: entity, ReturnValues: 'NONE' }; log.debug('Database put request (create)', params); let result = await this._dynamoDbDocClient.put(params).promise(); log.debug('Database put response (create)', result); return entity; } /** * Overwrites or adds some properties to an existing entity * * @param {*} uuid The id of the existing entity to be updated * @param {*} props The properties to be added/overwriten */ async patch(uuid, props) { if (props.uuid && props.uuid !== uuid) { throw new Exceptions.BusinessError('Mismatching entity identifiers'); } let entity = await this.get(uuid); if(!entity) { throw new Exceptions.NoEntityFoundException(); } let newVersion = Object.assign(entity, props); return this.update(newVersion, uuid); } async delete(hashKeyName, hashKey, rangeKeyName, rangeKey) { let params = { TableName: this._tableName, Key: { [hashKeyName]: hashKey, } }; if (rangeKeyName && rangeKey) { params.Key[rangeKeyName] = rangeKey; } log.debug('Database delete request', params); let result = await this._dynamoDbDocClient.delete(params).promise(); log.debug('Database delete response', result); } } module.exports = DynamoRepository;