-
Notifications
You must be signed in to change notification settings - Fork 55
initial implementation of codegen for low-level operations/types #1357
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
Conversation
| member.type in attrMapTypes -> if (member.name == "key") MapKeys else MapAll | ||
| member.isTableName -> Hoist | ||
| else -> PassThrough | ||
| }.also { println(" ${member.name} is $it") } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: stray println
| } | ||
| } | ||
|
|
||
| val hAttributes = llStructure.attributes + (ModelAttributes.LowLevelStructure to llStructure) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit/naming: hlAttributes
| // Start by invoking the JVM-only KSP configuration | ||
| dependencies.kspJvm(project(":hll:ddb-mapper:ddb-mapper-ops-codegen")) | ||
|
|
||
| // Then we need to move the generated source from jvm to common. Gradle lacks a move task so that means a copy... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gradle lacks a move task so that means a copy...
Have you tried the File.renameTo() API?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤦♂️ No, that's precisely what I was looking for and failed to find. I'll try it out and see.
| */ | ||
| public sealed interface KeySpec<in K> { | ||
| /** | ||
| * A [KeySpec] which for a [kotlin.ByteArray]-typed field |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: unnecessary word "which", applies to other KDocs in this class
| data object MapAll : MemberCodegenBehavior | ||
| data object MapKeys : MemberCodegenBehavior | ||
| data object Drop : MemberCodegenBehavior | ||
| data object Hoist : MemberCodegenBehavior // FIXME Note sure this is useful...get rid of Hoist? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: why is this only used for tableName members?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hoist indicates that the field shouldn't appear in the request/response structure itself but should be "hoisted" to a different location in the overall API. In the case of tableName, that's required before you can even invoke an operation:
val table = mapper.getTable("the-table-name", ...)
table.getItem(...)As the FIXME notes, I'm not entirely sure this is a meaningful codegen behavior. Right now the effect is the same as Drop in that it's not included in the generated high-level structures. The presence of tableName in other APIs is a result of hand-written code, not codegen. I've left it here because it may be useful for non-table-centric operations such as BatchWriteItem, in which multiple table names may be specified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose the YAGNI principle dictates that this code shouldn't exist until there's a clear planned/actual use for it so perhaps I should just clean it up for now. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait no, I did actually make use of it! 😅 It's used when rendering the convert operation that turns low-level requests into high-level requests:
// generated code
private fun <T> GetItemRequest<T>.convert(
tableName: String?,
schema: ItemSchema<T>,
) = LowLevelGetItemRequest {
consistentRead = this@convert.consistentRead
returnConsumedCapacity = this@convert.returnConsumedCapacity
this@convert.key?.let { key = schema.converter.toItem(it, schema.keyAttributeNames) }
this.tableName = tableName
}In that generated code the name/type tableName: String are derived from the low-level structure, not hand-written. So I believe I'll keep it as-is for now and remove the FIXME.
| Table.CompositeKey<T, PK, SK>, | ||
| TableSpec.CompositeKey<T, PK, SK> by specImpl, | ||
| TableOperations<T> by opsImpl { | ||
| override suspend fun getItem(partitionKey: PK, sortKey: SK) = TODO("Not yet implemented") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: should these override suspend fun getItem be removed now that operations are codegenerated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, they should stay here and get implemented because they're convenience overloads of the codegenned method. This will allow users to do something simple like table.getItem(123) instead of table.getItem { key = SomeItem(id = 123) }. We may add other hand-written convenience overloads for codegenned methods but I knew for sure at least this one would be useful.
| port = DDB_LOCAL_PORT | ||
| } | ||
|
|
||
| region = "us-west-2" // FIXME |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: What is the FIXME here? It seems like DDB Local requires a region:
The AWS SDKs for DynamoDB require that your application configuration specify an access key value and an AWS Region value. Unless you're using the -sharedDb or the -inMemory option, DynamoDB uses these values to name the local database file. These values don't have to be valid AWS values to run locally. However, you might find it convenient to use valid values so that you can run your code in the cloud later by changing the endpoint you're using.
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.UsageNotes.html
Maybe we can update it to DUMMY like the other unnecessary parameters
|
|
||
| private const val TABLE_NAME = "foo-table" | ||
|
|
||
| // FIXME Should be in commonTest but mockk is JVM-only and finding a good KMP mocking library is hard |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible/useful to rewrite these tests and remove the mockk dependency? Do we need to use a mock to test this functionality?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically we never need a mocking framework to test functionality, they're merely convenient for stubbing out mock objects quickly and with minimal boilerplate. In this case, I'm using mockk to mock DynamoDbMapper (which will eventually have dozens of codegenned operations on it) and to spy on Interceptor (which also has many methods) and verify invocation order. All that is achievable with hand-written mocks but would increase the size of the test code.
This test shouldn't be platform-specific but also the behavior isn't platform-specific either—it's all common code. Thus, the risk of it working differently on non-JVM platforms is pretty low. All things being equal, I'd definitely prefer it in commonTest but since the tradeoff is more boilerplate for marginal test utility, I think I prefer leaving it here until we can find a good KMP mocking library.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine to keep it JVM-only since the code is all common. We have other tests set up like this IIRC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason for the naming inconsistencies between modules like ddb-mapper-* and dynamodb-mapper-*?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a reason, yes, but I'm not sure if it's a good one or not: modules named dynamodb-* are intended to be released publicly as Maven artifacts. Modules named ddb-* are intended to remain as build details and should not be released as Maven artifacts. It's a quick way to see/remember which modules our users will interact with vs not.
How does that sound to you? Should we make the naming more consistent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh ok. I'm open to either way but I think keeping them consistent would be better. I'm guessing we'll have a
clear mechanism for deciding which modules should be published (ignoreProjects = listOf(...)) or something like that?
|
|
||
| override fun persist() { | ||
| val content = buildString { | ||
| appendLine("// Code generated by ddb-mapper-ops-codegen. DO NOT EDIT!") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For a second I thought you fixed #314 until I realized this was hand-written 😃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, totally separate codegen I'm afraid. #314 still stands.
0marperez
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: k docs on some of the classes would be helpful
|
|
||
| withBlock("internal fun <T> #L(table: #T) = #T(", ")", factoryName, Types.tableSpec("T"), Types.Operation) { | ||
| write( | ||
| "initialize = { hReq: #T -> #T(hReq, table.schema, #T(table, #S)) },", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: highLevelReq over hReq/hlReq for readability
lauzadis
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, so many docs!
hll/build.gradle.kts
Outdated
| val libraries = libs | ||
|
|
||
| subprojects { | ||
| println("Subproject $this needsKmpConfigured? $needsKmpConfigured") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: stray println?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, should've removed that.
|
…lin (#1451) * initial poc commit of DynamoDB Mapper (#1232) * add support for Mapper initialization (#1237) * implement mapper pipeline (#1266) * initial implementation of codegen for low-level operations/types (#1357) * initial implementation of secondary index support (#1375) * Create new codegen module and refactor annotation processor to use it (#1382) * feat: add Schema generator Gradle plugin (#1385) * Fix plugin test package * add attribute converters for "standard" values (#1381) * fix: schema generator plugin test module (#1394) * feat: annotation processor codegen configuration (#1392) * feat: add `@DynamoDbIgnore` annotation (#1402) * DDB Mapper filter expressions (runtime components) (#1401) * feat: basic annotation processing (#1399) * add DSL overloads, paginators, and better builder integration for DDB Mapper ops codegen (#1409) * chore: split dynamodb-mapper-codegen into two modules (#1414) * emit DDB_MAPPER business metric (#1426) * feat: setup DynamoDbMapper publication (#1419) * DDB Mapper filter expressions (codegen components) (#1424) * correct docs * mark every HLL/DDBM API experimental (#1428) * fix accidental inclusion of expression attribute members in high-level DynamoDB Mapper requests (#1432) * Upgrade to latest build plugin version * fix: various issues found during testing (#1450) * chore: update Athena changelog notes for 1.3.57 (2024-10-18) release (#1449) * feat: update AWS API models * feat: update AWS service endpoints metadata * chore: release 1.3.60 * chore: bump snapshot version to 1.3.61-SNAPSHOT * feat: initial release of Developer Preview of DynamoDB Mapper for Kotlin * Fix Kotlin gradle-plugin version * fix: ddb mapper tests (#1453) * Bump build plugin version --------- Co-authored-by: Matas <lauzmata@amazon.com> Co-authored-by: aws-sdk-kotlin-ci <aws-kotlin-sdk-automation@amazon.com>



Issue #
Part of #76
Description of changes
At long last, the wait for codegenned mapper operations and high-level types is
donestarted! This change removes the handwritten toy implementation of agetItemoperation and now generates the operation glue code for the following DDB ops:deleteItemgetItemputItemquery*scan** These operations should be paginated but the codegen doesn't handle that yet. It's left as a
FIXMEin the code for follow-up.PR flight plan
Recommended order of review:
HighLevelOpsProcessorwhich is the main entry point for KSP. Everything else in this module is called somehow from this point.modelpackage is for data types for describing the low- & high-level operations, structures, and typescorepackage contains rudimentary code writing utilities such as indentation, blocks, imports, and template processing. These are good candidates for commonizing somewhere in the future, since we'll likely need similar functionality for DDB mapper annotation processing and possibly other HLLs in the future.renderingpackage contains structured codegen for rendering the operations, convenience functions, and typesoperationspackage which use DynamoDB Local in lieu of mocking or making actual calls to DDBSample codegen output
Understanding the codegen may be easier if you see the generated code. All code is generated into the hll/ddb-mapper/dynamodb-mapper/build/generated/ksp/common/commonMain/kotlin directory.
GetItem operation
Table operations interface/implementation
Known work remaining
Several
TODOs andFIXMEs are to be found right now. At the very least I know we'll need to:Query,Scan, etc.) correctlyBy submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.