Skip to content

Create separate operation-specific configs for the object-mapper Batch and Transact operations #3376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

ashovlin
Copy link
Member

@ashovlin ashovlin commented Jul 8, 2024

Description

Creates new operation-specific config objects for the DynamoDB object-mapper operations.

I'm splitting this into 3 PRs:

  1. Batch and Transact - This PR
  2. Delete/Load/Save - Create separate operation-specific configs for the Delete, Load, and Save Operations #3387
  3. Query/Scan - Create separate operation-specific configs for the object-mapper Scan and Query operations #3383

I have a longer analysis in an internal doc, but I'm planning to apply these properties to all operations because they deal with the table itself or the DynamoDB item <-> .NET object transformation:

  1. OverrideTableName
  2. TableNamePrefix
  3. MetadataCachingMode
  4. IsEmptyStringValueEnabled
  5. Conversion
  6. DisableFetchingTableMetadata

Then these are on a case-by-case basis:

  1. BackwardQuery - only when querying
  2. IndexName - only when querying or scanning
  3. ConditionalOperator - only when querying or scanning
  4. QueryFilter - only when querying or scanning
  5. ConsistentRead - when getting items (including querying or scanning)
  6. SkipVersionCheck - when writing or deleting items
  7. IgnoreNullValues - when writing items
  8. RetrieveDateTimeInUtc - when getting items (including querying or scannign)

Motivation and Context

For V4 of the SDK, we're planning to split the shared DynamoDBOperationConfig object into operation-specific configs, since the shared object has grown overtime to include properties that are not relevant for all operations.

DOTNET-7513

Testing

Ran DynamoDBv2 sln tests locally

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • My code follows the code style of this project
  • My change requires a change to the documentation
  • I have updated the documentation accordingly
  • I have read the README document
  • I have added tests to cover my changes
  • All new and existing tests passed

License

  • I confirm that this pull request can be released under the Apache 2 license

@ashovlin ashovlin requested review from 96malhar and philasmar July 8, 2024 17:44
@ashovlin ashovlin added the v4 label Jul 8, 2024
@normj
Copy link
Member

normj commented Jul 9, 2024

If we are putting the same 6 properties on all operation configs should those be in a base class? Or does that cause confusion when you get to other operations.

@ashovlin
Copy link
Member Author

ashovlin commented Jul 9, 2024

If we are putting the same 6 properties on all operation configs should those be in a base class? Or does that cause confusion when you get to other operations.

I was on the fence about this. Given that we are unwinding an object that became over-shared, I opted to treat it like generated code and keep all the definitions separate. But I am planning to use those 6 for all of our current operations as they're used either at the table level or anytime we convert POCO <-> items.

I suppose that if we did use a base class and added a new operation that didn't use one of the 6, then we can not use the base class.

Any thoughts @96malhar or @philasmar ?

/// <summary>
/// The object persistence model API relies on an internal cache of the DynamoDB table's metadata to construct and validate
/// requests. This controls how the cache key is derived, which influences when the SDK will call
/// IAmazonDynamoDB.DescribeTable(string) internally to populate the cache.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add doc reference to IAmazonDynamoDB.DescribeTable(string)

Copy link
Member Author

@ashovlin ashovlin Jul 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DescribeTable isn't available in all targets, it's DescribeTableAync in .NET/Core/Standard.

I didn't think it was worth #if in the doc string, and the API reference generator is going to pick one anyway (i.e. we don't generate target-specific pages).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved over in #3380

/// </summary>
/// <remarks>
/// Setting this to true can avoid latency and thread starvation due to blocking asynchronous
/// IAmazonDynamoDB.DescribeTable(string) calls that are used to populate the SDK's cache of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add doc reference to IAmazonDynamoDB.DescribeTable(string)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved over in #3380

/// <summary>
/// The object persistence model API relies on an internal cache of the DynamoDB table's metadata to construct and validate
/// requests. This controls how the cache key is derived, which influences when the SDK will call
/// IAmazonDynamoDB.DescribeTable(string) internally to populate the cache.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add doc reference to IAmazonDynamoDB.DescribeTable(string)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved over in #3380

/// </summary>
/// <remarks>
/// Setting this to true can avoid latency and thread starvation due to blocking asynchronous
/// IAmazonDynamoDB.DescribeTable(string) calls that are used to populate the SDK's cache of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add doc reference to IAmazonDynamoDB.DescribeTable(string)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved over in #3380

/// <summary>
/// Creates a strongly-typed BatchGet object, allowing
/// a batch-get operation against DynamoDB.
/// </summary>
/// <typeparam name="T">Type of objects to get</typeparam>
/// <param name="operationConfig">Config object which can be used to override that table used.</param>
/// <returns>Empty strongly-typed BatchGet object</returns>
BatchGet<T> CreateBatchGet<T>(DynamoDBOperationConfig operationConfig = null);
[Obsolete("Use the CreateBatchGet overload that takes BatchGetConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchGet.")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you change the signature of the method?

Copy link
Member Author

@ashovlin ashovlin Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we added something similar to what was there, with the new config as optional:

BatchGet<T> CreateBatchGet<T>(DynamoDBOperationConfig operationConfig = null);
BatchGet<T> CreateBatchGet<T>(BatchGetConfig batchGetConfig = null);

Then these two calls are both ambiguous:

context.CreateBatchGet<Foo>();
context.CreateBatchGet<Foo>(null);

If we didn't make the new config optional:

BatchGet<T> CreateBatchGet<T>(DynamoDBOperationConfig operationConfig = null);
BatchGet<T> CreateBatchGet<T>(BatchGetConfig batchGetConfig);

Then

context.CreateBatchGet<Foo>();     // Calling the now obsolete overload, which we don't want
context.CreateBatchGet<Foo>(null); // Still ambiguous

So for that context.CreateBatchGet<Foo>(); case, I added a non-obsolete parameterless CreateBatchGet<T>()

BatchGet<T> CreateBatchGet<T>();
BatchGet<T> CreateBatchGet<T>(DynamoDBOperationConfig operationConfig = null);
BatchGet<T> CreateBatchGet<T>(BatchGetConfig batchGetConfig);

With that I was worried context.CreateBatchGet<Foo>() would still be ambiguous between the first two, but reviewing https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#12643-better-function-member again the parameterless one will win. So I'll add back the middle call.

context.CreateBatchGet<Foo>(null); is still ambiguous, but I don't think we can do anything about that , nor do I think we we should, since that call doesn't add anything over context.CreateBatchGet<Foo>() that I can see.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added back existing optional DynamoDBOperationConfigs in latest commits.

BatchWrite<T> CreateBatchWrite<T>(DynamoDBOperationConfig operationConfig = null);
[Obsolete("Use the CreateBatchWrite overload that takes BatchWriteConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchWrite.")]

BatchWrite<T> CreateBatchWrite<T>(DynamoDBOperationConfig operationConfig);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you change the signature of the method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -156,7 +199,32 @@ public partial interface IDynamoDBContext : IDisposable
/// <param name="valuesType">The type of data which will be persisted in this batch.</param>
/// <param name="operationConfig">Config object which can be used to override that table used.</param>
/// <returns>Empty strongly-typed BatchWrite object</returns>
BatchWrite<object> CreateBatchWrite(Type valuesType, DynamoDBOperationConfig operationConfig = null);
[Obsolete("Use the CreateBatchWrite overload that takes BatchWriteConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchWrite.")]
BatchWrite<object> CreateBatchWrite(Type valuesType, DynamoDBOperationConfig operationConfig);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you change the signature of the method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// <summary>
/// Creates a strongly-typed TransactGet object, allowing
/// a transactional get operation against DynamoDB.
/// </summary>
/// <typeparam name="T">Type of objects to get.</typeparam>
/// <param name="operationConfig">Config object which can be used to override that table used.</param>
/// <returns>Empty strongly-typed TransactGet object.</returns>
TransactGet<T> CreateTransactGet<T>(DynamoDBOperationConfig operationConfig = null);
[Obsolete("Use the CreateTransactGet overload that takes TransactGetConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to BatchGet.")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you change the signature of the method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// <summary>
/// Creates a strongly-typed TransactWrite object, allowing
/// a transactional write operation against DynamoDB.
/// </summary>
/// <typeparam name="T">Type of objects to write.</typeparam>
/// <param name="operationConfig">Config object which can be used to override that table used.</param>
/// <returns>Empty strongly-typed TransactWrite object.</returns>
TransactWrite<T> CreateTransactWrite<T>(DynamoDBOperationConfig operationConfig = null);
[Obsolete("Use the CreateTransactWrite overload that takes TransactWriteConfig instead, since DynamoDBOperationConfig contains properties that are not applicable to CreateTransactWrite.")]
TransactWrite<T> CreateTransactWrite<T>(DynamoDBOperationConfig operationConfig);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you change the signature of the method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@philasmar
Copy link
Contributor

If we are putting the same 6 properties on all operation configs should those be in a base class? Or does that cause confusion when you get to other operations.

I was on the fence about this. Given that we are unwinding an object that became over-shared, I opted to treat it like generated code and keep all the definitions separate. But I am planning to use those 6 for all of our current operations as they're used either at the table level or anytime we convert POCO <-> items.

I suppose that if we did use a base class and added a new operation that didn't use one of the 6, then we can not use the base class.

Any thoughts @96malhar or @philasmar ?

I prefer to use a BaseClass. This is going to become a maintainability issue. We should not add any property to this base class that does not apply to all operations. Any property that is operation-specific should go on the operation-specific config object.

@ashovlin
Copy link
Member Author

ashovlin commented Jul 10, 2024

If we are putting the same 6 properties on all operation configs should those be in a base class? Or does that cause confusion when you get to other operations.

I was on the fence about this. Given that we are unwinding an object that became over-shared, I opted to treat it like generated code and keep all the definitions separate. But I am planning to use those 6 for all of our current operations as they're used either at the table level or anytime we convert POCO <-> items.
I suppose that if we did use a base class and added a new operation that didn't use one of the 6, then we can not use the base class.
Any thoughts @96malhar or @philasmar ?

I prefer to use a BaseClass. This is going to become a maintainability issue. We should not add any property to this base class that does not apply to all operations. Any property that is operation-specific should go on the operation-specific config object.

Created #3380 to introduce the base class. Once that is merged I'll rebase this on top of it.

@ashovlin ashovlin force-pushed the shovlia/v4-ddb-separate-batch branch from 68ad0d1 to 2f5fb43 Compare July 11, 2024 15:54
Copy link
Contributor

@peterrsongg peterrsongg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with a comment

@@ -18,5 +18,37 @@ public void BaseOperationConfig()
// `ToDynamoDBOperationConfig` before updating this unit test
Assert.AreEqual(6, typeof(BaseOperationConfig).GetProperties().Length);
}

[TestMethod]
public void BatchGetConfig()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-blocking. but maybe worth adding unit test coverage for ToDynamoDBOperationConfig. Don't have to see this added, but just commenting since this is a new method, and these tests mainly seem to be a reminder to add new properties to ToDynamoDbOperationConfig

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 6895929.

It's testing that one of the properties on the new config object is correctly overriding the context-level config by inspecting the low-level request.

/// Property that directs <see cref="DynamoDBContext"/> to use consistent reads.
/// If property is not set, behavior defaults to non-consistent reads.
/// </summary>
public bool? ConsistentRead { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can you add a link to https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html in the property comment. This will helps users exercise better judgment while setting this value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 6895929

@ashovlin ashovlin merged commit ecb0c92 into feature/v4-ddb-separate-config Jul 30, 2024
1 check passed
@ashovlin ashovlin deleted the shovlia/v4-ddb-separate-batch branch July 30, 2024 18:53
ashovlin added a commit that referenced this pull request Aug 7, 2024
ashovlin added a commit that referenced this pull request Aug 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants