-
Notifications
You must be signed in to change notification settings - Fork 597
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
Feature request (dynamodb-util): Expose premarshall configuration #1533
Comments
I just realized the example where we pass in attributeKey gets hairy for nested attributes e.g.
|
We like this idea of premarshalling, but want to limit it as follows:
We're brainstorming this internally, and will post updates in this issue. |
Exploratory (not production-ready) code to define preMarshallConfig as an array of tuples: type PreMarshallConfig<T extends object> = [T, (value: T) => NativeAttributeValue][];
interface MockMarshallOptions {
readonly preMarshallConfig: PreMarshallConfig<any>;
}
const mockMarshall = (data: any, options: MockMarshallOptions): NativeAttributeValue => {
const { preMarshallConfig } = options;
const getPreMarsshallFunc = (data: any, preMarshallConfig: PreMarshallConfig<any>) => {
for (const [objType, objFunc] of preMarshallConfig) {
if (data instanceof objType) {
return objFunc;
}
}
return undefined;
}
const preMarshallFunction = getPreMarsshallFunc(data, preMarshallConfig);
if (preMarshallFunction) {
return preMarshallFunction(data);
}
return data;
} Verified on TypeScript Playground |
Totally cool not to allow changing keys, but I have a couple questions about the above:
|
We are in the process of porting aws-sdk v2 over to v3. If we modified async put(model: T, overwriteExisting = false) {
const command = new PutItemCommand({
Item: marshall(model, { convertEmptyValues: true }), //**********PROBLEMATIC LINE********
TableName: this._tableName,
ReturnValues: 'NONE'
});
if (!overwriteExisting) {
command.input.ConditionExpression = `attribute_not_exists(#${this.hashKey})`;
command.input.ExpressionAttributeNames = { [`#${this.hashKey}`]: this.hashKey };
}
await this.dynamoDb.send(command);
return model;
}
If #1865 and #1866 will not be addressed, but addressed via #1533, prior to a premarshalling solution, is there any guidance on how we should port our code (other than writing a helper function to recursively walk through our models and change named classes to anonymous objects)? We're just looking for a reasonable migration path from v2 DocumentClient to v3. Allowing users to specify custom marshalling is a great idea, whether it's for people to write ORM libraries, or just for one off custom serialization (e.g. Dates to a number, or ISO string, etc). However, I also think that you need to provide an easy migration path for existing v2 users who already are using DocumentClient without major code changes to their existing base (e.g., the base narrowness of the aws dynamo library to conform with v2, but allow callers to override marshalling and register their own marshaller based on value types, whether it's via Thanks! |
Just some more thoughts about premarshalling. I think premarshalling may not be as useful as initially thought because the above proposal is only about Because of the asymmetry, the user of the SDK will still need to have custom logic after reading from Dynamo, if they had custom premarshalling. Because of this, I think the v2 way of how the DocumentClient handled marshalling, albeit not perfect, may be the best compromise. The premarshall is quite useless, since it needs to be symmetrical with the unmarshall to be "correct", and the unmarshall can only be as good as the intersection between JS/TS/JSON and Dynamo types. Thoughts? |
Changing named classes to anonymous objects is the recommendation we have as of now. The marshall and premarshall utility methods won't be taking a call on user's behalf. We'll evaluate exposing new utility methods - say |
Update: Instead of creating new utility methods, we'll be evaluating adding a configuration (say |
Sounds great! The nice thing about the v3 Anyway, thank you! |
We're not planning to add premarshall configuration in util-dynamodb, as the equivalent post-marshall configuration can't work DynamoDB records. Closing this issue as wontfix. The option to convert class instances to map was added in #1969, and was released with |
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread. |
Is your feature request related to a problem? Please describe.
The problem, as detailed here, is that the current (v2) DocumentClient, and the upcoming (v3) DocumentClient, will serialize native JS Dates as {}.
We cannot make assumptions about how consumers want to serialize Dates (some want ISO strings, some want numbers, some may want just a date with no time component), but we can expose configuration to make it easy for consumers to instruct the client on how to serialize certain attributes based on type or name, without overstepping the client's responsibility and becoming a full blown ORM.
Describe the solution you'd like
I'd love to hammer out details of the contract here, and as an avid dynamo user, I'm also happy to implement this feature. The rough concept is something along these lines:
I included TableName in case you want to serialize items differently based on table (especially important if you are writing to multiple tables during a transaction, you might have different rules in place for each one).
Describe alternatives you've considered
I'm not sure if we should cross the line into allowing consumers to change the key name... it would make sense for Put, but is kind of confusing for UpdateItem, where your UpdateExpression should already have your keys clearly defined.
But this is something every dynamo client struggles with - creating a clean, usable client that handles all the unique (and kind of weird) ways Dynamo is used. For example, both DynamoDB Data Mapper and DynamoDB Toolbox provide ways to instruct the client on how to map attributes, but they also come at the cost of having to strictly define your types either as classes with decorators, or via configs, so it's easy to create disconnects between the client configuration and the types you are passing to the DB when using typescript.
Thus, pure DocumentClient tends to be more usable than either of these alternatives, and I want to be careful to not take on too responsibility. However, it might be nice to allow consumers to do something like the following contrived example:
In this example, the consumer is choosing to cram createdAt, updatedAt, favoriteColor, and hometown in to a GSI's sort key, and remove those individual attributes from the object itself. But this is where it's easy for the client to start to box users in take on too much responsibility, leading to complexity and, in some cases, becoming unusable (for example, in the example above, if I wanted to store all 4 attributes separately, AS WELL AS cram into the GSI1, it would be easy for 3/4, but what about hometown, where I rewrite the attribute name?)
Additional context
IMO it's more important to have this on the way in to dynamo on the way out, because there's always some casting from DB values to runtime values anyway (e.g. converting iso string back to date, stripping database concerns, etc). But some way to inject some casting logic that could be applied to Item, Attributes, Items, etc based on endpoint is a secondary item on my wishlist.
Obviously this is something that consumers can do themselves before calling UpdateItem / PutItem, but it creates additional complexity if, for example, every consumer has to wrap the documentClient with their own functionality to marshall. Or, even worse, have one-off code like such:
Code like the above becomes highly error prone, because even with typescript, adding a new Date value to typeof T will not break any assumptions and consumers have to jump through additional hoops to keep their DB logic in line with their types, when in reality it should "just work" (without having to cross the line into a full blown ORM).
Also: DynamoDB is rad 👍
The text was updated successfully, but these errors were encountered: