Skip to content

Rory-Reid/Dynamimic

Repository files navigation

Dynamimic

Dynamimic is a mimic of DynamoDB for use with local environments. Dynamimic is built with automated testing in mind, but should perform plenty well enough for manual local testing if that's your bag.

Dynamimic facilitates state and behaviour based testing, rather than interaction testing.

Usage

The DynamoDbMimic class implements the AWS SDK IAmazonDynamoDB interface and is used through that alone. You should be able to drop it in directly wherever you depend on the real thing.

Important Note: due to the implementation of the AWS SDK this cannot support the Document Model right now.

Expressions

Dynamimic does not implement any expression lexing, parsing and evaluation itself (yet) but provides an API to "register" the expressions used by your application with an implementation you define. A standard set of already-implemented "Expression Evaluators" for condition expressions and "Update Handlers" for update expresisons are provided in the hopes that you shouldn't need to define any custom code yourself here.

You can register using the IDynamoDbMimic interface API.

Expression registration requires you to register something for each and every unique DynamoDB expression used throughout your application - and the strings must match exactly for each and every usage in order to execute. Because this is a manually handled concept today and it can be error prone, Dynamimic will throw exceptions by default on unregistered expressions (this can be overridden)

The long-term goal for Dynamimic ultimately is to handle this automatically. The short-term goal is to provide a set of handlers and evaluators in this tool already and a simple API you can use to set things up without having to write your own logic.

Registering a condition evaluator

The following API exists to register condition expression evaluators:

public delegate bool ConditionExpressionEvaluator(
    Dictionary<string, AttributeValue> item,
    Dictionary<string, AttributeValue> expressionAttributeValues,
    Dictionary<string, string> expressionAttributeNames);

public interface IDynamoDbMimic
{
    void RegisterConditionEvaluator(
        string conditionExpression,
        ConditionExpressionEvaluator evaluator);
}

Pre-existing evaluators are supplied via the static class DynamoExpressionEvaluator.

An example registration is as follows:

var mimic = new DynamoDbMimic();
micmic.RegisterConditionEvaluator(
    "some_attribute < :value",
    DynamoExpressionEvaluator.LessThan("some_attribute", ":value"));

This means that whenever DynamoDbMimic encounters the expression some_attribute < :value it will execute the registered evaluator to provide the result.

The DynamoExpressionEvaluator functions can be chained, which can be used to combine expressions with .And() and .Or().

Registering an update handler

The following API exists to register update expression handlers:

public delegate bool UpdateExpressionHandler(
    Dictionary<string, AttributeValue> item,
    Dictionary<string, AttributeValue> expressionAttributeValues,
    Dictionary<string, string> expressionAttributeNames);

public interface IDynamoDbMimic
{
    void RegisterUpdateHandler(
        string updateExpression,
        UpdateExpressionHandler handler);
}

Pre-existing handlers are supplied via the static class DynamoUpdateHandler.

An example registration is as follows:

var mimic = new DynamoDbMimic();
mimic.RegisterUpdateHandler("SET Price = Price - :p REMOVE InStock",
    DynamoUpdateHandler
        .Set("Price", "Price", UpdateOperator.Subtract, ":p")
        .Remove("InStock"));

This means that whenever DynamoDbMimic encounters the expression SET Price = Price - :p REMOVE InStock it will execute the registered handlers to update the item.

The DynamoUpdateHandler functions can be chained, which can be used to combine expressions for several update statements in a single expression.

Custom evaluators and handlers

All handlers/evaluators are simple delegates which receive the context of the item and request being executed against so that you can logically implement something based on real input. This allows your implementation to do something logical but of course you could easily ignore the input and return a fixed behaviour, closer to an ignorant mock.

An example of always returning true for an expression would be:

var mimic = new DynamoDbMimic();
micmic.RegisterConditionEvaluator(
    "some_attribute < :value",
    (_, _, _) => true);

You can pass your own delegates manually, or via a call to .Custom() using the same chainable API as the handlers/evaluators provided by Dynamimic.

Current state

This is not fit for use unless your only use case is to create tables, put, update, and get single items. Development still has a long way to go before it's worth using.

"Basic support" means that the core function works but some details around the response might be slightly wrong, or not fully supported. "DescribeTable" for example will return core things like key schema, but has no support for GSI/LSI description today.

API Method Basic support
CreateTable
DescribeTable
PutItem
GetItem
UpdateItem

All other methods should not crash but won't do anything useful.

Contributing

Contributions welcome.