Using atomic counters in the Enhanced DynamoDB Amazon SDK for Java 2.x client

海外精选
海外精选的内容汇集了全球优质的亚马逊云科技相关技术内容。同时,内容中提到的“AWS” 是 “Amazon Web Services” 的缩写,在此网站不作为商标展示。
0
0
{"value":"We are pleased to announce that users of the [enhanced client](https://aws.amazon.com/blogs/developer/introducing-enhanced-dynamodb-client-in-the-aws-sdk-for-java-v2/) for [Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html) in [AWS SDK for Java 2.x](https://github.com/aws/aws-sdk-java-v2) can now enable atomic counters, as well as add custom DynamoDB update expressions through the enhanced client extension framework.\n\nCustomers have told us that they want improved performance and consistency when updating table records. The record update workflow in the DynamoDB enhanced client often means reading a record from the database to access current values before writing it back. This overhead can be painful if the records are large, and it can incur additional costs. Furthermore, there’s no guarantee that the record isn’t updated between a read and a write. This means that it isn’t an atomic set of actions.\n\nThis change means that you can tag a numeric record attribute as an atomic counter and use DynamoDB to update the attribute with a specific value each time that you call the ```updateItem```operation on your table. Moreover, we exposed an API that models update expressions. This lets you write your own extensions that directly create schema-level update expressions for DynamoDB.\n\n#### **Concepts**\n[**DynamoDB UpdateExpression**](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html) – This is the syntax used by DynamoDB when calling its ```UpdateItem```operation. Use the enhanced DynamoDB client to automatically generate this expression when you supply an item to update.\n\n**Enhanced client UpdateExpression API** – This is the abstraction representing DynamoDB UpdateExpression in the enhanced client. Read more under Introduction to the enhanced client UpdateExpression API later in this post.\n\n**Enhanced client extension **– This is a class implementing the ```DynamoDbEnhancedClientExtension```interface that hooks into the logic of operations, such as```updateItem```and provides the ability to modify requests or response parameters.\n\n**Extension chain** – All of the extensions that are activated for an enhanced client. The extensions in a chain are applied in order.\n\n#### **Using Atomic Counters**\nWhen you want to create an atomic counter and update DynamoDB every time that you call the ```\nupdateItem```\n operation, create a table attribute of the type ```\nLong ```\nthat represents the counter, and tag it as an atomic counter.\n\nYou enable atomic counter functionality when you instantiate an enhanced client. This is because it automatically loads the corresponding extension, ```\nAtomicCounterExtension```By default, a counter starts at 0 and increments by 1 each time the record in the table is updated. You can customize it by changing the start value and/or the increment value, including negative values.\n\nThe start value is set either when\n- you call the ```updateItem```operation and the attribute doesn’t exist, or\n- you call the ```putItem``` operation.\n\nThe following example shows how to create and use atomic counters for both bean-based table schemas and static table schemas.\n\n##### **Step 1: Define a schema with a tagged attribute**\n##### **Option 1: Bean-based table schema**\nCreate an attribute of the type Long, and annotate it with ```@DynamoDbAtomicCounter```\n\nJava\n```\n@DynamoDbBean\npublic class Customer {\n \n @DynamoDbPartitionKey\n public String getId() { ... }\n public void setId(String id) { ... }\n\n @DynamoDbAtomicCounter\n public Long getUpdateCounter() { ... }\n public void setUpdateCounter(Long counter) { ... }\n\n @DynamoDbAtomicCounter(delta = 5, startValue = 10)\n public Long getCustomCounter() { ... }\n public void setCustomCounter(Long counter) { ... }\n}\n```\n##### **Option 2: Static immutable table schema**\nCreate a StaticAttribute attribute with the attribute type ```Long```and use one of the ```StaticAttributeTags.atomicCounter()```methods to tag the attribute.\n\nThe following code example assumes that the ```Customer.class```exists and defines a schema to reference the class.\n\nJava\n```\nstatic final StaticTableSchema<Customer> TABLE_SCHEMA=\n StaticTableSchema.builder(Customer.class)\n .newItemSupplier(Customer::new)\n .addAttribute(String.class, a -> a.name(\"id\") ... )\n .addAttribute(Long.class, a -> a.name(\"defaultCounter\")\n .getter(Customer::getDefaultCounter)\n .setter(Customer::setDefaultCounter)\n .addTag(atomicCounter()))\n .addAttribute(Long.class, a -> a.name(\"customCounter\")\n .getter(Customer::getCustomCounter)\n .setter(Customer::setCustomCounter)\n .addTag(atomicCounter(5, 10)))\n .build();\t\n```\n##### **Step 2: Create a client and table resource**\nInstantiate the enhanced DynamoDB client, and create the table resource by giving it the previously defined schema:\n\nJava\n```\nDynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.create();\nTableSchema tableSchema = TableSchema.fromBean(Customer.class); // or TABLE_SCHEMA for static schema \nDynamoDbTable<Customer> customerTable = enhancedClient.table(\"customers_table\", tableSchema);\n```\n##### **Step 3: Call DynamoDB**\nInstantiate a Customer object and set the key attribute. Then, call ```updateItem``` and ```getItem```to verify the automatic updates of the counter attributes:\n\nJava\n```\nCustomer customer = Customer.builder().id(\"SOME_ID\").build():\n\ncustomerTable.updateItem(customer); //both putItem and updateItem can be used to add records\nCustomer retrievedCustomer = customerTable.getItem(customer)\n\nretrievedCustomer.defaultCounter(); //the value is 0\nretrievedCustomer.customCounter(); //the value is 10\n\ncustomerTable.updateItem(customer); \n\nretrievedCustomer = customerTable.getItem(customer)\nretrievedCustomer.defaultCounter(); //the value is 1\nretrievedCustomer.customCounter(); //the value is 15\n```\nAs you can see, we don’t reference the attributes in the record that’s sent to DynamoDB. However, the record is continually updated with values each time that you call the database. Manually setting a value on the record will cause DynamoDB to throw a DynamoDBException, saying “Invalid UpdateExpression: Two document paths overlap […]”, because the update expression with counters auto-generated by the ```AtomicCounterExtension```will collide with the expression that was created for the record itself.\n#### **Creating a custom UpdateExpression extension**\nWrite your own extension that takes advantage of the new option to provide a custom ```UpdateExpression```in the extension framework.\n\nUpdate expressions in the extensions are applicable for use cases where you want to do the same thing with an attribute every time that you call the database, such as atomic counters. However, if you need a one-time effect for a single request, then leveraging the extension framework isn’t useful. Before considering support for single-request update expressions, we’ll evaluate the usage of the new UpdateExpression API included in this release, as well as the feedback that we get.\n\n##### **Introduction to the enhanced client UpdateExpression API**\nAn enhanced client UpdateExpression consists of one or more UpdateAction that correspond to the DynamoDB UpdateExpression syntax. Before sending an update request to DynamoDB, the enhanced client parses an UpdateExpression into a format that DynamoDB understands.\n\nFor example, you can create a RemoveAction that will remove the attribute with the name “attr1” from a record:\t\n\nJava\n```\nRemoveAction removeAction = \n RemoveAction.builder()\n .path(\"#attr1_ref\")\n .putExpressionName(\"#attr1_ref\", \"attr1\")\n .build();\n```\nNote that, while usage of ExpressionNames is optional, we recommend it to avoid name collisions.\n\n##### **Step 1: Create an extension class**\nCreate an extension class that implements the ```beforeWrite``` extension hook:\n\nJava\n```\npublic final class CustomExtension implements DynamoDbEnhancedClientExtension {\n\n @Override\n public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {\n return WriteModification.builder()\n .updateExpression(createUpdateExpression())\n .build();\n }\n}\n```\nYou can use the context object to retrieve information about the following:\n\n- The transformed item\n- Table metadata, such as custom tags\n- The name of the operation that is being invoked\n\n##### **Step 2: Create the UpdateExpression**\nIn our extension, a SetAction changes the value of “attr2”, which we can infer is a String attribute:\n\nJava\n```\nprivate static UpdateExpression createUpdateExpression() {\n AttributeValue newValue = AttributeValue.builder().s(\"A new value\").build();\n SetAction setAction = \n SetAction.builder()\n .path(\"#attr1_ref\")\n .value(\":new_value\")\n .putExpressionName(\"#attr1_ref\", \"attr1\")\n .putExpressionValue(\":new_value\", newValue)\n .build();\n\n UpdateExpression.builder()\n .addAction(setAction)\n .build();\n}\n```\n##### **Step 3: Add the extension to a client**\n\nAdd your custom extension to the client:\n\nJava\n```\nDynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()\n .extensions(new CustomExtension()))\n .build();\n```\n##### **Step 4: Call DynamoDB**\nRetrieve a reference to the table and call updateItem:\n\nJava\n```\nDynamoDbTable<MyRecord> table = enhancedClient.table(\"tableName\", tableSchema);\ntable.updateItem(new MyRecord());\n```\n\n\n#### **Under the hood**\n- It can be useful to understand how the extension framework and the UpdateItem operation work together to combine the output from the extensions with request-level item information to form a cohesive DynamoDB request:\n- If there are several extensions that add UpdateExpressions in the extension chain, then these are merged without any checks to form a single expression.\n- The item to be updated that is provided by the enhanced UpdateItemRequest is transformed by the operation to an internal UpdateExpression.\n- The internal UpdateExpression in the UpdateItem operation is merged with the one from the extension framework – if it exists.\n- DynamoDB allows for only one action to manipulate a single attribute. Therefore, any duplicate actions referencing the same attribute will fail locally in the client when the UpdateExpression is parsed into the low-level DynamoDB request.\n- The enhanced client generates remove statements for any attribute that isn’t explicitly set on an item supplied to the UpdateItem operation. Because this default behavior (controlled by the ```ignoreNulls```flag) interferes with extension functionality, the client automatically filters those attributes by checking if they’re present in an extension UpdateExpression. If they are, then the client doesn’t create remove statements for them.\n\n#### **Conclusion**\nIn this post, you’ve learned to use atomic counters in the [enhanced DynamoDB client](https://github.com/aws/aws-sdk-java-v2/tree/master/services-custom/dynamodb-enhanced), and you’ve seen how extensions can work with the enhanced UpdateExpression API for custom applications. The enhanced client is open-source and resides in the same repository as the [AWS SDK for Java 2.0](https://github.com/aws/aws-sdk-java-v2).\n\nWe hope you found this post useful, and we look forward to your feedback. You can always share your input on our [GitHub issues page](https://github.com/aws/aws-sdk-java-v2/issues), or up-vote other ideas for features that you want to see in the DynamoDB enhanced client or the AWS SDK for Java in general.\n\n![image.png](https://dev-media.amazoncloud.cn/d2a349b1e860441da173ada5b0995fa7_image.png)\n\nAnna-Karin is a maintainer of AWS SDK for Java. She has a passion for writing maintainable software and infrastructure, as well as enjoying gardening, hiking and painting. You can find her on GitHub [@cenedhryn](https://github.com/cenedhryn).","render":"<p>We are pleased to announce that users of the <a href=\"https://aws.amazon.com/blogs/developer/introducing-enhanced-dynamodb-client-in-the-aws-sdk-for-java-v2/\" target=\"_blank\">enhanced client</a> for <a href=\"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html\" target=\"_blank\">Amazon DynamoDB</a> in <a href=\"https://github.com/aws/aws-sdk-java-v2\" target=\"_blank\">AWS SDK for Java 2.x</a> can now enable atomic counters, as well as add custom DynamoDB update expressions through the enhanced client extension framework.</p>\n<p>Customers have told us that they want improved performance and consistency when updating table records. The record update workflow in the DynamoDB enhanced client often means reading a record from the database to access current values before writing it back. This overhead can be painful if the records are large, and it can incur additional costs. Furthermore, there’s no guarantee that the record isn’t updated between a read and a write. This means that it isn’t an atomic set of actions.</p>\n<p>This change means that you can tag a numeric record attribute as an atomic counter and use DynamoDB to update the attribute with a specific value each time that you call the <code>updateItem</code>operation on your table. Moreover, we exposed an API that models update expressions. This lets you write your own extensions that directly create schema-level update expressions for DynamoDB.</p>\n<h4><a id=\"Concepts_6\"></a><strong>Concepts</strong></h4>\n<p><a href=\"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html\" target=\"_blank\"><strong>DynamoDB UpdateExpression</strong></a> – This is the syntax used by DynamoDB when calling its <code>UpdateItem</code>operation. Use the enhanced DynamoDB client to automatically generate this expression when you supply an item to update.</p>\n<p><strong>Enhanced client UpdateExpression API</strong> – This is the abstraction representing DynamoDB UpdateExpression in the enhanced client. Read more under Introduction to the enhanced client UpdateExpression API later in this post.</p>\n<p>**Enhanced client extension **– This is a class implementing the <code>DynamoDbEnhancedClientExtension</code>interface that hooks into the logic of operations, such as<code>updateItem</code>and provides the ability to modify requests or response parameters.</p>\n<p><strong>Extension chain</strong> – All of the extensions that are activated for an enhanced client. The extensions in a chain are applied in order.</p>\n<h4><a id=\"Using_Atomic_Counters_15\"></a><strong>Using Atomic Counters</strong></h4>\n<p>When you want to create an atomic counter and update DynamoDB every time that you call the <code> updateItem</code><br />\noperation, create a table attribute of the type <code>Long</code><br />\nthat represents the counter, and tag it as an atomic counter.</p>\n<p>You enable atomic counter functionality when you instantiate an enhanced client. This is because it automatically loads the corresponding extension, <code> AtomicCounterExtension</code>By default, a counter starts at 0 and increments by 1 each time the record in the table is updated. You can customize it by changing the start value and/or the increment value, including negative values.</p>\n<p>The start value is set either when</p>\n<ul>\n<li>you call the <code>updateItem</code>operation and the attribute doesn’t exist, or</li>\n<li>you call the <code>putItem</code> operation.</li>\n</ul>\n<p>The following example shows how to create and use atomic counters for both bean-based table schemas and static table schemas.</p>\n<h5><a id=\"Step_1_Define_a_schema_with_a_tagged_attribute_31\"></a><strong>Step 1: Define a schema with a tagged attribute</strong></h5>\n<h5><a id=\"Option_1_Beanbased_table_schema_32\"></a><strong>Option 1: Bean-based table schema</strong></h5>\n<p>Create an attribute of the type Long, and annotate it with <code>@DynamoDbAtomicCounter</code></p>\n<p>Java</p>\n<pre><code class=\"lang-\">@DynamoDbBean\npublic class Customer {\n \n @DynamoDbPartitionKey\n public String getId() { ... }\n public void setId(String id) { ... }\n\n @DynamoDbAtomicCounter\n public Long getUpdateCounter() { ... }\n public void setUpdateCounter(Long counter) { ... }\n\n @DynamoDbAtomicCounter(delta = 5, startValue = 10)\n public Long getCustomCounter() { ... }\n public void setCustomCounter(Long counter) { ... }\n}\n</code></pre>\n<h5><a id=\"Option_2_Static_immutable_table_schema_53\"></a><strong>Option 2: Static immutable table schema</strong></h5>\n<p>Create a StaticAttribute attribute with the attribute type <code>Long</code>and use one of the <code>StaticAttributeTags.atomicCounter()</code>methods to tag the attribute.</p>\n<p>The following code example assumes that the <code>Customer.class</code>exists and defines a schema to reference the class.</p>\n<p>Java</p>\n<pre><code class=\"lang-\">static final StaticTableSchema&lt;Customer&gt; TABLE_SCHEMA=\n StaticTableSchema.builder(Customer.class)\n .newItemSupplier(Customer::new)\n .addAttribute(String.class, a -&gt; a.name(&quot;id&quot;) ... )\n .addAttribute(Long.class, a -&gt; a.name(&quot;defaultCounter&quot;)\n .getter(Customer::getDefaultCounter)\n .setter(Customer::setDefaultCounter)\n .addTag(atomicCounter()))\n .addAttribute(Long.class, a -&gt; a.name(&quot;customCounter&quot;)\n .getter(Customer::getCustomCounter)\n .setter(Customer::setCustomCounter)\n .addTag(atomicCounter(5, 10)))\n .build();\t\n</code></pre>\n<h5><a id=\"Step_2_Create_a_client_and_table_resource_74\"></a><strong>Step 2: Create a client and table resource</strong></h5>\n<p>Instantiate the enhanced DynamoDB client, and create the table resource by giving it the previously defined schema:</p>\n<p>Java</p>\n<pre><code class=\"lang-\">DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.create();\nTableSchema tableSchema = TableSchema.fromBean(Customer.class); // or TABLE_SCHEMA for static schema \nDynamoDbTable&lt;Customer&gt; customerTable = enhancedClient.table(&quot;customers_table&quot;, tableSchema);\n</code></pre>\n<h5><a id=\"Step_3_Call_DynamoDB_83\"></a><strong>Step 3: Call DynamoDB</strong></h5>\n<p>Instantiate a Customer object and set the key attribute. Then, call <code>updateItem</code> and <code>getItem</code>to verify the automatic updates of the counter attributes:</p>\n<p>Java</p>\n<pre><code class=\"lang-\">Customer customer = Customer.builder().id(&quot;SOME_ID&quot;).build():\n\ncustomerTable.updateItem(customer); //both putItem and updateItem can be used to add records\nCustomer retrievedCustomer = customerTable.getItem(customer)\n\nretrievedCustomer.defaultCounter(); //the value is 0\nretrievedCustomer.customCounter(); //the value is 10\n\ncustomerTable.updateItem(customer); \n\nretrievedCustomer = customerTable.getItem(customer)\nretrievedCustomer.defaultCounter(); //the value is 1\nretrievedCustomer.customCounter(); //the value is 15\n</code></pre>\n<p>As you can see, we don’t reference the attributes in the record that’s sent to DynamoDB. However, the record is continually updated with values each time that you call the database. Manually setting a value on the record will cause DynamoDB to throw a DynamoDBException, saying “Invalid UpdateExpression: Two document paths overlap […]”, because the update expression with counters auto-generated by the <code>AtomicCounterExtension</code>will collide with the expression that was created for the record itself.</p>\n<h4><a id=\"Creating_a_custom_UpdateExpression_extension_103\"></a><strong>Creating a custom UpdateExpression extension</strong></h4>\n<p>Write your own extension that takes advantage of the new option to provide a custom <code>UpdateExpression</code>in the extension framework.</p>\n<p>Update expressions in the extensions are applicable for use cases where you want to do the same thing with an attribute every time that you call the database, such as atomic counters. However, if you need a one-time effect for a single request, then leveraging the extension framework isn’t useful. Before considering support for single-request update expressions, we’ll evaluate the usage of the new UpdateExpression API included in this release, as well as the feedback that we get.</p>\n<h5><a id=\"Introduction_to_the_enhanced_client_UpdateExpression_API_108\"></a><strong>Introduction to the enhanced client UpdateExpression API</strong></h5>\n<p>An enhanced client UpdateExpression consists of one or more UpdateAction that correspond to the DynamoDB UpdateExpression syntax. Before sending an update request to DynamoDB, the enhanced client parses an UpdateExpression into a format that DynamoDB understands.</p>\n<p>For example, you can create a RemoveAction that will remove the attribute with the name “attr1” from a record:</p>\n<p>Java</p>\n<pre><code class=\"lang-\">RemoveAction removeAction = \n RemoveAction.builder()\n .path(&quot;#attr1_ref&quot;)\n .putExpressionName(&quot;#attr1_ref&quot;, &quot;attr1&quot;)\n .build();\n</code></pre>\n<p>Note that, while usage of ExpressionNames is optional, we recommend it to avoid name collisions.</p>\n<h5><a id=\"Step_1_Create_an_extension_class_123\"></a><strong>Step 1: Create an extension class</strong></h5>\n<p>Create an extension class that implements the <code>beforeWrite</code> extension hook:</p>\n<p>Java</p>\n<pre><code class=\"lang-\">public final class CustomExtension implements DynamoDbEnhancedClientExtension {\n\n @Override\n public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {\n return WriteModification.builder()\n .updateExpression(createUpdateExpression())\n .build();\n }\n}\n</code></pre>\n<p>You can use the context object to retrieve information about the following:</p>\n<ul>\n<li>The transformed item</li>\n<li>Table metadata, such as custom tags</li>\n<li>The name of the operation that is being invoked</li>\n</ul>\n<h5><a id=\"Step_2_Create_the_UpdateExpression_144\"></a><strong>Step 2: Create the UpdateExpression</strong></h5>\n<p>In our extension, a SetAction changes the value of “attr2”, which we can infer is a String attribute:</p>\n<p>Java</p>\n<pre><code class=\"lang-\">private static UpdateExpression createUpdateExpression() {\n AttributeValue newValue = AttributeValue.builder().s(&quot;A new value&quot;).build();\n SetAction setAction = \n SetAction.builder()\n .path(&quot;#attr1_ref&quot;)\n .value(&quot;:new_value&quot;)\n .putExpressionName(&quot;#attr1_ref&quot;, &quot;attr1&quot;)\n .putExpressionValue(&quot;:new_value&quot;, newValue)\n .build();\n\n UpdateExpression.builder()\n .addAction(setAction)\n .build();\n}\n</code></pre>\n<h5><a id=\"Step_3_Add_the_extension_to_a_client_164\"></a><strong>Step 3: Add the extension to a client</strong></h5>\n<p>Add your custom extension to the client:</p>\n<p>Java</p>\n<pre><code class=\"lang-\">DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()\n .extensions(new CustomExtension()))\n .build();\n</code></pre>\n<h5><a id=\"Step_4_Call_DynamoDB_174\"></a><strong>Step 4: Call DynamoDB</strong></h5>\n<p>Retrieve a reference to the table and call updateItem:</p>\n<p>Java</p>\n<pre><code class=\"lang-\">DynamoDbTable&lt;MyRecord&gt; table = enhancedClient.table(&quot;tableName&quot;, tableSchema);\ntable.updateItem(new MyRecord());\n</code></pre>\n<h4><a id=\"Under_the_hood_184\"></a><strong>Under the hood</strong></h4>\n<ul>\n<li>It can be useful to understand how the extension framework and the UpdateItem operation work together to combine the output from the extensions with request-level item information to form a cohesive DynamoDB request:</li>\n<li>If there are several extensions that add UpdateExpressions in the extension chain, then these are merged without any checks to form a single expression.</li>\n<li>The item to be updated that is provided by the enhanced UpdateItemRequest is transformed by the operation to an internal UpdateExpression.</li>\n<li>The internal UpdateExpression in the UpdateItem operation is merged with the one from the extension framework – if it exists.</li>\n<li>DynamoDB allows for only one action to manipulate a single attribute. Therefore, any duplicate actions referencing the same attribute will fail locally in the client when the UpdateExpression is parsed into the low-level DynamoDB request.</li>\n<li>The enhanced client generates remove statements for any attribute that isn’t explicitly set on an item supplied to the UpdateItem operation. Because this default behavior (controlled by the <code>ignoreNulls</code>flag) interferes with extension functionality, the client automatically filters those attributes by checking if they’re present in an extension UpdateExpression. If they are, then the client doesn’t create remove statements for them.</li>\n</ul>\n<h4><a id=\"Conclusion_192\"></a><strong>Conclusion</strong></h4>\n<p>In this post, you’ve learned to use atomic counters in the <a href=\"https://github.com/aws/aws-sdk-java-v2/tree/master/services-custom/dynamodb-enhanced\" target=\"_blank\">enhanced DynamoDB client</a>, and you’ve seen how extensions can work with the enhanced UpdateExpression API for custom applications. The enhanced client is open-source and resides in the same repository as the <a href=\"https://github.com/aws/aws-sdk-java-v2\" target=\"_blank\">AWS SDK for Java 2.0</a>.</p>\n<p>We hope you found this post useful, and we look forward to your feedback. You can always share your input on our <a href=\"https://github.com/aws/aws-sdk-java-v2/issues\" target=\"_blank\">GitHub issues page</a>, or up-vote other ideas for features that you want to see in the DynamoDB enhanced client or the AWS SDK for Java in general.</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/d2a349b1e860441da173ada5b0995fa7_image.png\" alt=\"image.png\" /></p>\n<p>Anna-Karin is a maintainer of AWS SDK for Java. She has a passion for writing maintainable software and infrastructure, as well as enjoying gardening, hiking and painting. You can find her on GitHub <a href=\"https://github.com/cenedhryn\" target=\"_blank\">@cenedhryn</a>.</p>\n"}
目录
亚马逊云科技解决方案 基于行业客户应用场景及技术领域的解决方案
联系亚马逊云科技专家
亚马逊云科技解决方案
基于行业客户应用场景及技术领域的解决方案
联系专家
0
目录
关闭