Leverage L2 constructs to reduce the complexity of your Amazon CDK application

海外精选
海外精选的内容汇集了全球优质的亚马逊云科技相关技术内容。同时,内容中提到的“AWS” 是 “Amazon Web Services” 的缩写,在此网站不作为商标展示。
0
0
{"value":"The [AWS Cloud Development Kit (AWS CDK)](https://aws.amazon.com/cdk/) is an open-source software development framework to define your cloud application resources using familiar programming languages. AWS CDK uses the familiarity and expressive power of programming languages for modeling your applications. Constructs are the basic building blocks of AWS CDK apps. A construct represents a “cloud component” and encapsulates everything that [AWS CloudFormation](https://aws.amazon.com/cloudformation/) needs to create the component. Furthermore, [AWS Construct Library](https://docs.aws.amazon.com/cdk/api/v1/docs/aws-construct-library.html) lets you ease the process of building your application using predefined templates and logic. Three levels of constructs exist:\n\n- L1 – These are low-level constructs called Cfn (short for CloudFormation) resources. They’re periodically generated from the [AWS CloudFormation Resource Specification](https://docs.aws.amazon.com/en_en/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html). The name pattern is **Cfn**Xyz, where Xyz is name of the resource. When using these constructs, you must configure all of the resource properties. This requires a full understanding of the underlying CloudFormation resource model and its corresponding attributes.\n- L2 – These represent AWS resources with a higher-level, intent-based API. They provide additional functionality with defaults, boilerplate, and glue logic that you’d be writing yourself with L1 constructs. AWS constructs offer convenient defaults and reduce the need to know all of the details about the AWS resources that they represent. This is done while providing convenience methods that make it simpler to work with the resources and as a result creating your application.\n- L3 – These constructs are called patterns. They’re designed to complete common tasks in AWS, often involving multiple types of resources.\n\nIn this post, I show a sample architecture and how the complexity of an AWS CDK application is reduced by using L2 constructs.\n\n### **Overview of the sample architecture**\nThis solution uses [Amazon API Gateway](https://aws.amazon.com/api-gateway/), [AWS Lambda](https://aws.amazon.com/lambda/), and [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). I implement a simple serverless web application. The application receives a POST request from a user via API Gateway and forwards it to a Lambda function using proxy integration. The Lambda function writes the request body to a DynamoDB table.\n\n![image.png](https://dev-media.amazoncloud.cn/404381830e5f42c18dada58d7d609e86_image.png)\n\nThe sample code can be found on [GitHub](https://github.com/aws-samples/aws-cdk-l2-constructs).\n\n### **Walkthrough**\nYou can follow the instructions in the README file of the GitHub repository to deploy the stack. In the following walkthrough, I explain each logical unit and the differences when implementing it using L1 and L2 constructs. Before each code sample, I’ll show the path in the GitHub repository where you can find its source.\n\n#### **Create the DynamoDB table**\nFirst, I create a DynamoDB table to store the request content.\n\n#### **L1 construct**\nWith L1 constructs, I must define each attribute of a table separately. For the DynamoDB table, these are \n```keySchema```, ```attributeDefinitions```, and ```provisionedThroughput```. They all require detailed ```CloudFormation``` knowledge, for example, how a ```keyType``` is defined.\n\n*lib/level1/database/infrastructure.ts*\n```\nthis.cfnDynamoDbTable = new dynamodb.CfnTable(\n this, \n \"CfnDynamoDbTable\", \n {\n keySchema: [\n {\n attributeName: props.attributeName,\n keyType: \"HASH\",\n },\n ],\n attributeDefinitions: [\n {\n attributeName: props.attributeName,\n attributeType: \"S\",\n },\n ],\n provisionedThroughput: {\n readCapacityUnits: 5,\n writeCapacityUnits: 5,\n },\n },\n);\n```\n#### **L2 construct**\nThe corresponding L2 construct lets me use the default values for readCapacity (5) and writeCapacity (5). To further reduce the complexity, I define the attributes and the partition key simultaneously. In addition, I utilize the dynamodb.AttributeType.STRING enum.\n\n*lib/level2/database/infrastructure.ts*\n```\nthis.dynamoDbTable = new dynamodb.Table(\n this, \n \"DynamoDbTable\", \n {\n partitionKey: {\n name: props.attributeName,\n type: dynamodb.AttributeType.STRING,\n },\n },\n);\n```\n### **Create the Lambda function**\nNext, I create a Lambda function which receives the request and stores the content in the DynamoDB table. The runtime code uses Node.js.\n\n#### **L1 construct**\nWhen creating a Lambda function using L1 construct, I must specify all of the properties at creation time – the business logic code location, runtime, and the function handler. This includes the role for the Lambda function to assume. As a result, I must provide the Attribute Resource Name (ARN) of the role. In the “Granting permissions” sections later in this post, I show how to create this role.\n\n*lib/level1/api/infrastructure.ts*\n\n```\nconst cfnLambdaFunction = new lambda.CfnFunction(\n this, \n \"CfnLambdaFunction\", \n {\n code: {\n zipFile: fs.readFileSync(\n path.resolve(__dirname, \"runtime/index.js\"),\n \"utf8\"\n ),\n },\n role: this.cfnIamLambdaRole.attrArn,\n runtime: \"nodejs16.x\",\n handler: \"index.handler\",\n environment: {\n variables: {\n TABLE_NAME: props.dynamoDbTableArn,\n },\n },\n },\n);\n```\n#### **L2 construct**\nI can achieve the same result with less complexity by leveraging the [NodejsFunction](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html) L2 construct for Lambda function. It sets a default version for Node.js runtime unless another one is explicitly specified. The construct creates a Lambda function with automatic transpiling and bundling of TypeScript or Javascript code. This results in smaller Lambda packages that contain only the code and dependencies needed to run the function, and it uses [esbuild](https://esbuild.github.io/) under the hood. The Lambda function handler code is located in the \n```runtime``` directory of the API logical unit. I provide the path to the Lambda handler file in the ```entry``` property. I don’t have to specify the handler function name, because the ```NodejsFunction``` construct uses the handler name by default. Moreover, a [Lambda execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html) isn’t required to be provided during L2 Lambda construct creation. If no role is specified, then a default one is generated which has permissions for Lambda execution. In the section ‘Granting Permissions’, I describe how to customize the role after creating the construct.\n\n*lib/level2/api/infrastructure.ts*\n\n```\nthis.lambdaFunction = new lambda_nodejs.NodejsFunction(\n this, \n \"LambdaFunction\", \n {\n entry: path.resolve(__dirname, \"runtime/index.ts\"),\n runtime: lambda.Runtime.NODEJS_16_X,\n environment: {\n TABLE_NAME: props.dynamoDbTableName,\n },\n },\n);\n```\n### **Create API Gateway REST API**\nNext, I define the API Gateway REST API to receive POST requests with [Cross-origin resource sharing (CORS) ](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)enabled.\n\n#### **L1 construct**\nEvery step, from creating a new API Gateway REST API, to the deployment process, must be configured individually. With an L1 construct, I must have a good understanding of CORS and the exact configuration of headers and methods.\n\nFurthermore, I must know all of the specifics, such as for the Lambda integration type I must know how to construct the URI.\n\n*lib/level1/api/infrastructure.ts*\n```\nconst cfnApiGatewayRestApi = new apigateway.CfnRestApi(\n this, \n \"CfnApiGatewayRestApi\", \n {\n name: props.apiName,\n },\n);\n\nconst cfnApiGatewayPostMethod = new apigateway.CfnMethod(\n this, \n \"CfnApiGatewayPostMethod\", \n {\n httpMethod: \"POST\",\n resourceId: cfnApiGatewayRestApi.attrRootResourceId,\n restApiId: cfnApiGatewayRestApi.ref,\n authorizationType: \"NONE\",\n integration: {\n credentials: cfnIamApiGatewayRole.attrArn,\n type: \"AWS_PROXY\",\n integrationHttpMethod: \"ANY\",\n uri:\n \"arn:aws:apigateway:\" +\n Stack.of(this).region +\n \":lambda:path/2015-03-31/functions/\" +\n cfnLambdaFunction.attrArn +\n \"/invocations\",\n passthroughBehavior: \"WHEN_NO_MATCH\",\n },\n },\n);\n\nconst CfnApiGatewayOptionsMethod = new apigateway.CfnMethod(\n this,\n \"CfnApiGatewayOptionsMethod\",\n { \n // fields omitted\n },\n);\n\nconst cfnApiGatewayDeployment = new apigateway.CfnDeployment(\n this,\n \"cfnApiGatewayDeployment\",\n {\n restApiId: cfnApiGatewayRestApi.ref,\n stageName: \"prod\",\n },\n);\n```\n#### **L2 construct**\nCreating an API Gateway REST API with CORS enabled is simpler with L2 constructs. I can leverage the \n```defaultCorsPreflightOptions``` property and the construct builds the required options method. To set origins and methods, I can use the ```apigateway.Cors``` enum. To configure the Lambda proxy option, all I need to do is to set the proxy variable in the method to ```true```. A default deployment is created automatically.\n\n*lib/level2/api/infrastructure.ts*\n```\nthis.api = new apigateway.RestApi(\n this, \n \"ApiGatewayRestApi\", \n {\n defaultCorsPreflightOptions: {\n allowOrigins: apigateway.Cors.ALL_ORIGINS,\n allowMethods: apigateway.Cors.ALL_METHODS,\n },\n },\n);\n\nthis.api.root.addMethod(\n \"POST\",\n new apigateway.LambdaIntegration(this.lambdaFunction, {\n proxy: true,\n })\n);\n```\n### **Granting permissions**\nIn the sample application, I must give permissions to two different resources:\n\n1. API Gateway REST API to invoke the Lambda function.\n2. Lambda function to write data to the DynamoDB table.\n\n#### **L1 construct**\nFor both resources, I must define [AWS Identity and Access Management (IAM)](https://aws.amazon.com/iam/) roles. This requires in-depth knowledge of IAM, how policies are structured, and which actions are required. In the following code snippet, I start by creating the policy documents. Afterward, I create a role for each resource. These are provided at creation time to the corresponding constructs as shown earlier.\n\n*lib/level1/api/infrastructure.ts*\n\n```\nconst cfnLambdaAssumeIamPolicyDocument = {\n // fields omitted\n};\n\nthis.cfnLambdaIamRole = new iam.CfnRole(\n this, \n \"cfnLambdaIamRole\", \n {\n assumeRolePolicyDocument: cfnLambdaAssumeIamPolicyDocument,\n managedPolicyArns: [\n \"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\",\n ],\n },\n);\n \nconst cfnApiGatewayAssumeIamPolicyDocument = {\n // fields omitted\n};\n\nconst cfnApiGatewayInvokeLambdaIamPolicyDocument = {\n Version: \"2012-10-17\",\n Statement: [\n {\n Action: [\"lambda:InvokeFunction\"],\n Resource: [cfnLambdaFunction.attrArn],\n Effect: \"Allow\",\n },\n ],\n};\n\nconst cfnApiGatewayIamRole = new iam.CfnRole(\n this, \n \"cfnApiGatewayIamRole\", \n {\n assumeRolePolicyDocument: cfnApiGatewayAssumeIamPolicyDocument,\n policies: [{\n policyDocument: cfnApiGatewayInvokeLambdaIamPolicyDocument,\n policyName: \"ApiGatewayInvokeLambdaIamPolicy\",\n }],\n },\n);\n```\nThe database construct exposes a function to grant write access to any IAM role. The function creates a policy, which allows ```dynamodb:PutItem``` on the database table and adds it as an additional policy to the role.\n\n*lib/level1/database/infrastructure.ts*\n\n```\ngrantWriteData(cfnIamRole: iam.CfnRole) {\n const cfnPutDynamoDbIamPolicyDocument = {\n Version: \"2012-10-17\",\n Statement: [\n {\n Action: [\"dynamodb:PutItem\"],\n Resource: [this.cfnDynamoDbTable.attrArn],\n Effect: \"Allow\",\n },\n ],\n };\n\n cfnIamRole.policies = [{\n policyDocument: cfnPutDynamoDbIamPolicyDocument,\n policyName: \"PutDynamoDbIamPolicy\",\n }];\n}\n```\nAt this point, all permissions are in place, except that Lambda function doesn’t have permissions to write data to the DynamoDB table yet. To grant write access, I call the ```grantWriteData``` function of the ```Database``` construct with the IAM role of the Lambda function.\n\n*lib/deployment.ts*\n\n```\ndatabase.grantWriteData(api.cfnLambdaIamRole)\n```\n\n\n#### **L2 construct**\nCreating an API Gateway REST API with the ```LambdaIntegration``` construct generates the IAM role and attaches the role to the API Gateway REST API method. Giving the Lambda function permission to write to the DynamoDB table can be achieved with the following single line:\n\n*lib/deployment.ts*\n\n```\ndatabase.dynamoDbTable.grantWriteData(api.lambdaFunction);\n```\n#### **Using L3 constructs**\nTo reduce complexity even further, I can leverage L3 constructs. In the case of this sample architecture, I can utilize the ```LambdaRestApi``` construct. This construct uses a default Lambda proxy integration. It automatically generates a method and a deployment, and grants permissions. As a result, I can achieve the same with even less code.\n```\nconst restApi = new apigateway.LambdaRestApi(\n this, \n \"restApiLevel3\", \n {\n handler: this.lambdaFunction,\n defaultCorsPreflightOptions: {\n allowOrigins: apigateway.Cors.ALL_ORIGINS,\n allowMethods: apigateway.Cors.ALL_METHODS\n },\n },\n);\n```\n### **Cleanup**\nMany services in this post are available in the [AWS Free Tier](https://aws.amazon.com/free). However, using this solution may incur costs, and you should tear down the stack if you don’t need it anymore. Cleanup steps are included in the RADME file of the [GitHub repository](https://github.com/aws-samples/---).\n\n### **Conclusion**\nIn this post, I highlight the difference between using L1 and L2 AWS CDK constructs with an example architecture. Leveraging L2 constructs reduces the complexity of your application by using predefined patterns, boiler plate, and glue logic. They offer convenient defaults and reduce the need to know all of the details about the AWS resources they represent, while providing convenient methods that make it simpler to work with the resource. Additionally, I showed how to reduce complexity for common tasks even further by using an L3 construct.\n\nVisit the [AWS CDK documentation](http://aws%20cdk%20documentation/) to learn more about building resilient, scalable, and cost-efficient architectures with the expressive power of a programming language.\n\n#### **About the author:**\n\n![image.png](https://dev-media.amazoncloud.cn/10fe0d3b09be4fca9f11779e9f32a96e_image.png)\n\n**David Boldt**\nDavid Boldt is a Solutions Architect at AWS, based in Hamburg, Germany. David works with customers to enable them with best practices in their cloud journey. He is passionate about the internet of Things and how it can be leveraged to solve different challenges across industries.\n","render":"<p>The <a href=\"https://aws.amazon.com/cdk/\" target=\"_blank\">AWS Cloud Development Kit (AWS CDK)</a> is an open-source software development framework to define your cloud application resources using familiar programming languages. AWS CDK uses the familiarity and expressive power of programming languages for modeling your applications. Constructs are the basic building blocks of AWS CDK apps. A construct represents a “cloud component” and encapsulates everything that <a href=\"https://aws.amazon.com/cloudformation/\" target=\"_blank\">AWS CloudFormation</a> needs to create the component. Furthermore, <a href=\"https://docs.aws.amazon.com/cdk/api/v1/docs/aws-construct-library.html\" target=\"_blank\">AWS Construct Library</a> lets you ease the process of building your application using predefined templates and logic. Three levels of constructs exist:</p>\n<ul>\n<li>L1 – These are low-level constructs called Cfn (short for CloudFormation) resources. They’re periodically generated from the <a href=\"https://docs.aws.amazon.com/en_en/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html\" target=\"_blank\">AWS CloudFormation Resource Specification</a>. The name pattern is <strong>Cfn</strong>Xyz, where Xyz is name of the resource. When using these constructs, you must configure all of the resource properties. This requires a full understanding of the underlying CloudFormation resource model and its corresponding attributes.</li>\n<li>L2 – These represent AWS resources with a higher-level, intent-based API. They provide additional functionality with defaults, boilerplate, and glue logic that you’d be writing yourself with L1 constructs. AWS constructs offer convenient defaults and reduce the need to know all of the details about the AWS resources that they represent. This is done while providing convenience methods that make it simpler to work with the resources and as a result creating your application.</li>\n<li>L3 – These constructs are called patterns. They’re designed to complete common tasks in AWS, often involving multiple types of resources.</li>\n</ul>\n<p>In this post, I show a sample architecture and how the complexity of an AWS CDK application is reduced by using L2 constructs.</p>\n<h3><a id=\"Overview_of_the_sample_architecture_8\"></a><strong>Overview of the sample architecture</strong></h3>\n<p>This solution uses <a href=\"https://aws.amazon.com/api-gateway/\" target=\"_blank\">Amazon API Gateway</a>, <a href=\"https://aws.amazon.com/lambda/\" target=\"_blank\">AWS Lambda</a>, and <a href=\"https://aws.amazon.com/dynamodb/\" target=\"_blank\">Amazon DynamoDB</a>. I implement a simple serverless web application. The application receives a POST request from a user via API Gateway and forwards it to a Lambda function using proxy integration. The Lambda function writes the request body to a DynamoDB table.</p>\n<p><img src=\"https://dev-media.amazoncloud.cn/404381830e5f42c18dada58d7d609e86_image.png\" alt=\"image.png\" /></p>\n<p>The sample code can be found on <a href=\"https://github.com/aws-samples/aws-cdk-l2-constructs\" target=\"_blank\">GitHub</a>.</p>\n<h3><a id=\"Walkthrough_15\"></a><strong>Walkthrough</strong></h3>\n<p>You can follow the instructions in the README file of the GitHub repository to deploy the stack. In the following walkthrough, I explain each logical unit and the differences when implementing it using L1 and L2 constructs. Before each code sample, I’ll show the path in the GitHub repository where you can find its source.</p>\n<h4><a id=\"Create_the_DynamoDB_table_18\"></a><strong>Create the DynamoDB table</strong></h4>\n<p>First, I create a DynamoDB table to store the request content.</p>\n<h4><a id=\"L1_construct_21\"></a><strong>L1 construct</strong></h4>\n<p>With L1 constructs, I must define each attribute of a table separately. For the DynamoDB table, these are<br />\n<code>keySchema</code>, <code>attributeDefinitions</code>, and <code>provisionedThroughput</code>. They all require detailed <code>CloudFormation</code> knowledge, for example, how a <code>keyType</code> is defined.</p>\n<p><em>lib/level1/database/infrastructure.ts</em></p>\n<pre><code class=\"lang-\">this.cfnDynamoDbTable = new dynamodb.CfnTable(\n this, \n &quot;CfnDynamoDbTable&quot;, \n {\n keySchema: [\n {\n attributeName: props.attributeName,\n keyType: &quot;HASH&quot;,\n },\n ],\n attributeDefinitions: [\n {\n attributeName: props.attributeName,\n attributeType: &quot;S&quot;,\n },\n ],\n provisionedThroughput: {\n readCapacityUnits: 5,\n writeCapacityUnits: 5,\n },\n },\n);\n</code></pre>\n<h4><a id=\"L2_construct_50\"></a><strong>L2 construct</strong></h4>\n<p>The corresponding L2 construct lets me use the default values for readCapacity (5) and writeCapacity (5). To further reduce the complexity, I define the attributes and the partition key simultaneously. In addition, I utilize the dynamodb.AttributeType.STRING enum.</p>\n<p><em>lib/level2/database/infrastructure.ts</em></p>\n<pre><code class=\"lang-\">this.dynamoDbTable = new dynamodb.Table(\n this, \n &quot;DynamoDbTable&quot;, \n {\n partitionKey: {\n name: props.attributeName,\n type: dynamodb.AttributeType.STRING,\n },\n },\n);\n</code></pre>\n<h3><a id=\"Create_the_Lambda_function_66\"></a><strong>Create the Lambda function</strong></h3>\n<p>Next, I create a Lambda function which receives the request and stores the content in the DynamoDB table. The runtime code uses Node.js.</p>\n<h4><a id=\"L1_construct_69\"></a><strong>L1 construct</strong></h4>\n<p>When creating a Lambda function using L1 construct, I must specify all of the properties at creation time – the business logic code location, runtime, and the function handler. This includes the role for the Lambda function to assume. As a result, I must provide the Attribute Resource Name (ARN) of the role. In the “Granting permissions” sections later in this post, I show how to create this role.</p>\n<p><em>lib/level1/api/infrastructure.ts</em></p>\n<pre><code class=\"lang-\">const cfnLambdaFunction = new lambda.CfnFunction(\n this, \n &quot;CfnLambdaFunction&quot;, \n {\n code: {\n zipFile: fs.readFileSync(\n path.resolve(__dirname, &quot;runtime/index.js&quot;),\n &quot;utf8&quot;\n ),\n },\n role: this.cfnIamLambdaRole.attrArn,\n runtime: &quot;nodejs16.x&quot;,\n handler: &quot;index.handler&quot;,\n environment: {\n variables: {\n TABLE_NAME: props.dynamoDbTableArn,\n },\n },\n },\n);\n</code></pre>\n<h4><a id=\"L2_construct_96\"></a><strong>L2 construct</strong></h4>\n<p>I can achieve the same result with less complexity by leveraging the <a href=\"https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html\" target=\"_blank\">NodejsFunction</a> L2 construct for Lambda function. It sets a default version for Node.js runtime unless another one is explicitly specified. The construct creates a Lambda function with automatic transpiling and bundling of TypeScript or Javascript code. This results in smaller Lambda packages that contain only the code and dependencies needed to run the function, and it uses <a href=\"https://esbuild.github.io/\" target=\"_blank\">esbuild</a> under the hood. The Lambda function handler code is located in the<br />\n<code>runtime</code> directory of the API logical unit. I provide the path to the Lambda handler file in the <code>entry</code> property. I don’t have to specify the handler function name, because the <code>NodejsFunction</code> construct uses the handler name by default. Moreover, a <a href=\"https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html\" target=\"_blank\">Lambda execution role</a> isn’t required to be provided during L2 Lambda construct creation. If no role is specified, then a default one is generated which has permissions for Lambda execution. In the section ‘Granting Permissions’, I describe how to customize the role after creating the construct.</p>\n<p><em>lib/level2/api/infrastructure.ts</em></p>\n<pre><code class=\"lang-\">this.lambdaFunction = new lambda_nodejs.NodejsFunction(\n this, \n &quot;LambdaFunction&quot;, \n {\n entry: path.resolve(__dirname, &quot;runtime/index.ts&quot;),\n runtime: lambda.Runtime.NODEJS_16_X,\n environment: {\n TABLE_NAME: props.dynamoDbTableName,\n },\n },\n);\n</code></pre>\n<h3><a id=\"Create_API_Gateway_REST_API_115\"></a><strong>Create API Gateway REST API</strong></h3>\n<p>Next, I define the API Gateway REST API to receive POST requests with <a href=\"https://en.wikipedia.org/wiki/Cross-origin_resource_sharing\" target=\"_blank\">Cross-origin resource sharing (CORS) </a>enabled.</p>\n<h4><a id=\"L1_construct_118\"></a><strong>L1 construct</strong></h4>\n<p>Every step, from creating a new API Gateway REST API, to the deployment process, must be configured individually. With an L1 construct, I must have a good understanding of CORS and the exact configuration of headers and methods.</p>\n<p>Furthermore, I must know all of the specifics, such as for the Lambda integration type I must know how to construct the URI.</p>\n<p><em>lib/level1/api/infrastructure.ts</em></p>\n<pre><code class=\"lang-\">const cfnApiGatewayRestApi = new apigateway.CfnRestApi(\n this, \n &quot;CfnApiGatewayRestApi&quot;, \n {\n name: props.apiName,\n },\n);\n\nconst cfnApiGatewayPostMethod = new apigateway.CfnMethod(\n this, \n &quot;CfnApiGatewayPostMethod&quot;, \n {\n httpMethod: &quot;POST&quot;,\n resourceId: cfnApiGatewayRestApi.attrRootResourceId,\n restApiId: cfnApiGatewayRestApi.ref,\n authorizationType: &quot;NONE&quot;,\n integration: {\n credentials: cfnIamApiGatewayRole.attrArn,\n type: &quot;AWS_PROXY&quot;,\n integrationHttpMethod: &quot;ANY&quot;,\n uri:\n &quot;arn:aws:apigateway:&quot; +\n Stack.of(this).region +\n &quot;:lambda:path/2015-03-31/functions/&quot; +\n cfnLambdaFunction.attrArn +\n &quot;/invocations&quot;,\n passthroughBehavior: &quot;WHEN_NO_MATCH&quot;,\n },\n },\n);\n\nconst CfnApiGatewayOptionsMethod = new apigateway.CfnMethod(\n this,\n &quot;CfnApiGatewayOptionsMethod&quot;,\n { \n // fields omitted\n },\n);\n\nconst cfnApiGatewayDeployment = new apigateway.CfnDeployment(\n this,\n &quot;cfnApiGatewayDeployment&quot;,\n {\n restApiId: cfnApiGatewayRestApi.ref,\n stageName: &quot;prod&quot;,\n },\n);\n</code></pre>\n<h4><a id=\"L2_construct_173\"></a><strong>L2 construct</strong></h4>\n<p>Creating an API Gateway REST API with CORS enabled is simpler with L2 constructs. I can leverage the<br />\n<code>defaultCorsPreflightOptions</code> property and the construct builds the required options method. To set origins and methods, I can use the <code>apigateway.Cors</code> enum. To configure the Lambda proxy option, all I need to do is to set the proxy variable in the method to <code>true</code>. A default deployment is created automatically.</p>\n<p><em>lib/level2/api/infrastructure.ts</em></p>\n<pre><code class=\"lang-\">this.api = new apigateway.RestApi(\n this, \n &quot;ApiGatewayRestApi&quot;, \n {\n defaultCorsPreflightOptions: {\n allowOrigins: apigateway.Cors.ALL_ORIGINS,\n allowMethods: apigateway.Cors.ALL_METHODS,\n },\n },\n);\n\nthis.api.root.addMethod(\n &quot;POST&quot;,\n new apigateway.LambdaIntegration(this.lambdaFunction, {\n proxy: true,\n })\n);\n</code></pre>\n<h3><a id=\"Granting_permissions_197\"></a><strong>Granting permissions</strong></h3>\n<p>In the sample application, I must give permissions to two different resources:</p>\n<ol>\n<li>API Gateway REST API to invoke the Lambda function.</li>\n<li>Lambda function to write data to the DynamoDB table.</li>\n</ol>\n<h4><a id=\"L1_construct_203\"></a><strong>L1 construct</strong></h4>\n<p>For both resources, I must define <a href=\"https://aws.amazon.com/iam/\" target=\"_blank\">AWS Identity and Access Management (IAM)</a> roles. This requires in-depth knowledge of IAM, how policies are structured, and which actions are required. In the following code snippet, I start by creating the policy documents. Afterward, I create a role for each resource. These are provided at creation time to the corresponding constructs as shown earlier.</p>\n<p><em>lib/level1/api/infrastructure.ts</em></p>\n<pre><code class=\"lang-\">const cfnLambdaAssumeIamPolicyDocument = {\n // fields omitted\n};\n\nthis.cfnLambdaIamRole = new iam.CfnRole(\n this, \n &quot;cfnLambdaIamRole&quot;, \n {\n assumeRolePolicyDocument: cfnLambdaAssumeIamPolicyDocument,\n managedPolicyArns: [\n &quot;arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole&quot;,\n ],\n },\n);\n \nconst cfnApiGatewayAssumeIamPolicyDocument = {\n // fields omitted\n};\n\nconst cfnApiGatewayInvokeLambdaIamPolicyDocument = {\n Version: &quot;2012-10-17&quot;,\n Statement: [\n {\n Action: [&quot;lambda:InvokeFunction&quot;],\n Resource: [cfnLambdaFunction.attrArn],\n Effect: &quot;Allow&quot;,\n },\n ],\n};\n\nconst cfnApiGatewayIamRole = new iam.CfnRole(\n this, \n &quot;cfnApiGatewayIamRole&quot;, \n {\n assumeRolePolicyDocument: cfnApiGatewayAssumeIamPolicyDocument,\n policies: [{\n policyDocument: cfnApiGatewayInvokeLambdaIamPolicyDocument,\n policyName: &quot;ApiGatewayInvokeLambdaIamPolicy&quot;,\n }],\n },\n);\n</code></pre>\n<p>The database construct exposes a function to grant write access to any IAM role. The function creates a policy, which allows <code>dynamodb:PutItem</code> on the database table and adds it as an additional policy to the role.</p>\n<p><em>lib/level1/database/infrastructure.ts</em></p>\n<pre><code class=\"lang-\">grantWriteData(cfnIamRole: iam.CfnRole) {\n const cfnPutDynamoDbIamPolicyDocument = {\n Version: &quot;2012-10-17&quot;,\n Statement: [\n {\n Action: [&quot;dynamodb:PutItem&quot;],\n Resource: [this.cfnDynamoDbTable.attrArn],\n Effect: &quot;Allow&quot;,\n },\n ],\n };\n\n cfnIamRole.policies = [{\n policyDocument: cfnPutDynamoDbIamPolicyDocument,\n policyName: &quot;PutDynamoDbIamPolicy&quot;,\n }];\n}\n</code></pre>\n<p>At this point, all permissions are in place, except that Lambda function doesn’t have permissions to write data to the DynamoDB table yet. To grant write access, I call the <code>grantWriteData</code> function of the <code>Database</code> construct with the IAM role of the Lambda function.</p>\n<p><em>lib/deployment.ts</em></p>\n<pre><code class=\"lang-\">database.grantWriteData(api.cfnLambdaIamRole)\n</code></pre>\n<h4><a id=\"L2_construct_283\"></a><strong>L2 construct</strong></h4>\n<p>Creating an API Gateway REST API with the <code>LambdaIntegration</code> construct generates the IAM role and attaches the role to the API Gateway REST API method. Giving the Lambda function permission to write to the DynamoDB table can be achieved with the following single line:</p>\n<p><em>lib/deployment.ts</em></p>\n<pre><code class=\"lang-\">database.dynamoDbTable.grantWriteData(api.lambdaFunction);\n</code></pre>\n<h4><a id=\"Using_L3_constructs_291\"></a><strong>Using L3 constructs</strong></h4>\n<p>To reduce complexity even further, I can leverage L3 constructs. In the case of this sample architecture, I can utilize the <code>LambdaRestApi</code> construct. This construct uses a default Lambda proxy integration. It automatically generates a method and a deployment, and grants permissions. As a result, I can achieve the same with even less code.</p>\n<pre><code class=\"lang-\">const restApi = new apigateway.LambdaRestApi(\n this, \n &quot;restApiLevel3&quot;, \n {\n handler: this.lambdaFunction,\n defaultCorsPreflightOptions: {\n allowOrigins: apigateway.Cors.ALL_ORIGINS,\n allowMethods: apigateway.Cors.ALL_METHODS\n },\n },\n);\n</code></pre>\n<h3><a id=\"Cleanup_306\"></a><strong>Cleanup</strong></h3>\n<p>Many services in this post are available in the <a href=\"https://aws.amazon.com/free\" target=\"_blank\">AWS Free Tier</a>. However, using this solution may incur costs, and you should tear down the stack if you don’t need it anymore. Cleanup steps are included in the RADME file of the <a href=\"https://github.com/aws-samples/---\" target=\"_blank\">GitHub repository</a>.</p>\n<h3><a id=\"Conclusion_309\"></a><strong>Conclusion</strong></h3>\n<p>In this post, I highlight the difference between using L1 and L2 AWS CDK constructs with an example architecture. Leveraging L2 constructs reduces the complexity of your application by using predefined patterns, boiler plate, and glue logic. They offer convenient defaults and reduce the need to know all of the details about the AWS resources they represent, while providing convenient methods that make it simpler to work with the resource. Additionally, I showed how to reduce complexity for common tasks even further by using an L3 construct.</p>\n<p>Visit the <a href=\"http://aws%20cdk%20documentation/\" target=\"_blank\">AWS CDK documentation</a> to learn more about building resilient, scalable, and cost-efficient architectures with the expressive power of a programming language.</p>\n<h4><a id=\"About_the_author_314\"></a><strong>About the author:</strong></h4>\n<p><img src=\"https://dev-media.amazoncloud.cn/10fe0d3b09be4fca9f11779e9f32a96e_image.png\" alt=\"image.png\" /></p>\n<p><strong>David Boldt</strong><br />\nDavid Boldt is a Solutions Architect at AWS, based in Hamburg, Germany. David works with customers to enable them with best practices in their cloud journey. He is passionate about the internet of Things and how it can be leveraged to solve different challenges across industries.</p>\n"}
目录
亚马逊云科技解决方案 基于行业客户应用场景及技术领域的解决方案
联系亚马逊云科技专家
亚马逊云科技解决方案
基于行业客户应用场景及技术领域的解决方案
联系专家
0
目录
关闭
contact-us