

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 创建或扩展构造
<a name="constructs-best-practices"></a>

## 什么是构造
<a name="construct-definition"></a>

构造是 AWS CDK 应用程序的基本构造块。结构可以表示单个 AWS 资源，例如亚马逊简单存储服务 (Amazon S3) Service 存储桶，也可以是由多个相关资源组成的更高级别的抽象。 AWS 构造的组件可以包括具有相关计算能力的工作队列，或者具有监控资源和控制面板的计划作业。 AWS CDK 包括一个名为 “构造库” 的 AWS 构造集合。该库包含每个 AWS 服务的构造。您可以使用 C [onstruct Hub](https://constructs.dev/search?q=&cdk=aws-cdk&cdkver=2&sort=downloadsDesc&offset=0) 来发现来自 AWS第三方和开源 AWS CDK 社区的其他构造。

## 构造有哪些不同的类型
<a name="construct-types"></a>

有三种不同类型的构造： AWS CDK
+ **L1 构造** — 第 1 层或 L1 结构正是由 CloudFormation —不多也不少定义的资源。您必须自己提供配置所需的资源。这些 L1 结构非常基本，必须手动配置。 L1 结构有一个`Cfn`前缀，直接对应于规范。 CloudFormation 只要 CloudFormation 支持 AWS 服务 这些服务 AWS CDK ，就会立即支持新服务。 [CfnBucket](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.CfnBucket.html)是 L1 构造的一个很好的例子。该类表示一个 S3 存储桶，您必须在其中明确配置所有属性。我们建议您仅在找不到 L1 构造的 L2 或 L3 构造时才使用该构造。
+ **L2 构造** - 第 2 层或 L2 构造具有通用的样板代码和粘合逻辑。这些结构带有方便的默认值，减少了您需要了解的有关它们的知识量。L2 构造使用基于意图 APIs 来构造您的资源，并且通常封装其相应的 L1 模块。L2 构造的一个很好的例子是[存储桶](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.Bucket.html)。该类使用默认属性和方法（例如存储桶）创建一个 S3 [存储桶。 addLifeCycle规则 ()](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-s3.Bucket.html#add-wbr-lifecycle-wbr-rulerule)，它向存储桶添加生命周期规则。
+ **L3 构造** - 第 3 层或 L3 构造称为*模式*。L3 结构旨在帮助您完成中的常见任务 AWS，通常涉及多种资源。它们甚至比 L2 构造更具体、更有主见，并为特定的用例服务。例如，[aws-ecs-patterns. ApplicationLoadBalancedFargateService](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_ecs_patterns.ApplicationLoadBalancedFargateService.html)构造表示一种架构，其中包括使用 Application Load Balancer 的 AWS Fargate 容器集群。另一个例子是 [aws-apigateway。 LambdaRestApi](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.LambdaRestApi.html)构造。该构造表示由 Lambda 函数支持的 Amazon API Gateway API。

随着构造级别的提高，人们对如何使用这些构造做出了更多的假设。这使您可以为高度特定的用例提供具有更有效默认值的接口。

## 如何创建自己的构造
<a name="create-construct"></a>

若要定义自己的构造，必须遵循特定的方法。这是因为所有构造都扩展了 `Construct` 类。`Construct` 类是构造树的构建块。构造是在扩展 `Construct` 基类的类中实现的。所有构造在初始化时都有三个参数：
+ **作用域** — 构造的父构造或所有者，可以是堆栈，也可以是另一个构造，它决定了它在构造树中的位置。通常，您必须传递 `this`（或在 Python 中传递 `self`），它表示当前对象的作用域。
+ **id** - 在此作用域内必须唯一的标识符。标识符充当当前构造中定义的所有内容的命名空间，用于分配唯一标识，例如资源名称和 CloudFormation 逻辑 IDs。
+ **Props** — 一组定义构造初始配置的属性。

以下示例演示了如何定义构造。

```
import { Construct } from 'constructs';

export interface CustomProps {
  // List all the properties 
  Name: string;
}
export class MyConstruct extends Construct {
  constructor(scope: Construct, id: string, props: CustomProps) {
    super(scope, id);

    // TODO
  }
}
```

## 创建或扩展 L2 构造
<a name="create-l2-construct"></a>

L2 构造表示 “云组件”，它封装了创建该 CloudFormation 组件所需的所有内容。L2 构造可以包含一个或多个 AWS 资源，你可以自由地自己定制构造。创建或扩展 L2 构造的好处是，无需重新定义代码即可在 CloudFormation 堆栈中重复使用组件。您只需将构造作为一个类导入。

当与现有构造存在 “是” 关系时，您可以扩展现有构造以添加其他默认功能。最佳做法是重用现有 L2 构造的属性。您可以通过直接在构造函数中修改属性来覆盖属性。

以下示例演示了如何遵循最佳实践，并扩展名为 `s3.Bucket` 的现有 L2 构造。该扩展建立默认属性，如 `versioned`、`publicReadAccess`、`blockPublicAccess`，以确保通过此新构造创建的所有对象（在本例中为 S3 存储桶）将始终设置这些默认值。

```
import * as s3 from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';
export class MySecureBucket extends s3.Bucket {
  constructor(scope: Construct, id: string, props?: s3.BucketProps) {

    super(scope, id, { 
      ...props, 
      versioned: true,
      publicReadAccess: false,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL
    });
  }
}
```

## 创建 L3 构造
<a name="create-l3-construct"></a>

当与现有构造组合存在 “有” 关系时，组合是更好的选择。组合意味着您在其他现有构造的基础上构建自己的构造。您可以创建自己的模式，将所有资源及其默认值封装在一个可以共享的更高级别的 L3 构造中。创建自己的 L3 构造（模式）的好处是，您可以在堆栈中重用组件，而无需重新定义代码。您只需将构造作为一个类导入。这些模式旨在帮助使用者以简洁的方式，利用有限的知识，根据通用模式预置多种资源。

以下代码示例创建了一个名为的 AWS CDK 构造`ExampleConstruct`。您可以使用此构造作为模板来定义云组件。

```
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';

export interface ExampleConstructProps {
  //insert properties you wish to expose
}

export class ExampleConstruct extends Construct {
  constructor(scope: Construct, id: string, props: ExampleConstructProps) {
    super(scope, id);
    //Insert the AWS components you wish to integrate
  }
}
```

以下示例说明如何在 AWS CDK 应用程序或堆栈中导入新创建的构造。

```
import { ExampleConstruct } from './lib/construct-name';
```

以下示例演示了如何实例化从基类扩展的构造实例。

```
import { ExampleConstruct } from './lib/construct-name';

new ExampleConstruct(this, 'newConstruct', {
  //insert props which you exposed in the interface `ExampleConstructProps`
});
```

有关更多信息，请参阅[AWS CDK 研讨会](https://cdkworkshop.com/)文档中的 AWS CDK 研讨会。

## 转义孵化
<a name="escape-hatch"></a>

你可以使用中的逃生舱口 AWS CDK 来上升一个抽象级别，这样你就可以访问较低级别的构造。逃生舱口用于扩展当前版本中未公开 AWS 但在中 CloudFormation可用的要素的构造。

我们建议您在以下情况下使用转义孵化：
+ 可通过 AWS 服务 使用某项功能 CloudFormation，但没有针对该功能的`Construct`构造。
+ 一项 AWS 服务 功能可通过获得， CloudFormation 并且有该服务的`Construct`构造，但这些构造尚未公开该功能。由于`Construct`构造是 “手工” 开发的，因此它们有时可能落后于 CloudFormation 资源结构。

以下示例代码显示了使用转义孵化的常见用例。在此示例中，尚未在更高级别的构造中实现的功能用于添加 `httpPutResponseHopLimit` 以自动缩放 `LaunchConfiguration`。

```
const launchConfig = autoscaling.onDemandASG.node.findChild("LaunchConfig") as CfnLaunchConfiguration; 
            launchConfig.metadataOptions = {
                   httpPutResponseHopLimit: autoscalingConfig.httpPutResponseHopLimit|| 2
            }
```

上述代码示例显示了以下工作流程：

1. 使用 L2 构造来定义您的 `AutoScalingGroup`。L2 构造不支持更新`httpPutResponseHopLimit`，因此您必须使用逃生舱口。

1. 您可以使用该`node.findChild()`方法找到 L2 `AutoScalingGroup` 构造的特定`LaunchConfig`子项并将其转换为`CfnLaunchConfiguration`资源。

1. 您现在可以在 L1 `CfnLaunchConfiguration` 上设置 `launchConfig.metadataOptions` 属性。

## 自定义资源
<a name="custom-resource"></a>

您可以使用自定义资源在模板中编写自定义配置逻辑，这些逻辑将在您创建、更新（如果您更改了自定义资源）或删除堆栈时 CloudFormation 运行。例如，如果您想包含中没有的资源，则可以使用自定义资源 AWS CDK。这样，您仍然可以在一个堆栈中管理所有相关资源。

构建自定义资源涉及编写 Lambda 函数，来响应资源的 CREATE、UPDATE 和 DELETE 生命周期事件。如果您的自定义资源只能进行一次 API 调用，请考虑使用[AwsCustomResource](https://github.com/awslabs/aws-cdk/tree/master/packages/%40aws-cdk/custom-resources)构造。这使得在 CloudFormation 部署期间可以执行任意 SDK 调用。否则，我们建议您编写自己的 Lambda 函数来执行必须完成的工作。

有关自定义资源的更多信息，请参阅 CloudFormation 文档中的[自定义资源](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)。有关如何使用自定义资源的示例，请参阅上的[自定义资源](https://github.com/aws-samples/aws-cdk-examples/tree/master/typescript/custom-resource/)存储库 GitHub。

以下示例说明如何创建自定义资源类来启动 Lambda 函数并发送 CloudFormation 成功或失败信号。

```
import cdk = require('aws-cdk-lib');
import customResources = require('aws-cdk-lib/custom-resources');
import lambda = require('aws-cdk-lib/aws-lambda');
import { Construct } from 'constructs';

import fs = require('fs');

export interface MyCustomResourceProps {
  /**
   * Message to echo
   */
  message: string;
}

export class MyCustomResource extends Construct {
  public readonly response: string;

  constructor(scope: Construct, id: string, props: MyCustomResourceProps) {
    super(scope, id);

    const fn = new lambda.SingletonFunction(this, 'Singleton', {
      uuid: 'f7d4f730-4ee1-11e8-9c2d-fa7ae01bbebc',
      code: new lambda.InlineCode(fs.readFileSync('custom-resource-handler.py', { encoding: 'utf-8' })),
      handler: 'index.main',
      timeout: cdk.Duration.seconds(300),
      runtime: lambda.Runtime.PYTHON_3_6,
    });

    const provider = new customResources.Provider(this, 'Provider', {
      onEventHandler: fn,
    });

    const resource = new cdk.CustomResource(this, 'Resource', {
      serviceToken: provider.serviceToken,
      properties: props,
    });

    this.response = resource.getAtt('Response').toString();
    }
}
```

以下示例显示了自定义资源的主要逻辑。

```
def main(event, context):
    import logging as log
    import cfnresponse
    log.getLogger().setLevel(log.INFO)

    # This needs to change if there are to be multiple resources in the same stack
    physical_id = 'TheOnlyCustomResource'

    try:
        log.info('Input event: %s', event)

        # Check if this is a Create and we're failing Creates
        if event['RequestType'] == 'Create' and event['ResourceProperties'].get('FailCreate', False):
            raise RuntimeError('Create failure requested')

        # Do the thing
        message = event['ResourceProperties']['Message']
        attributes = {
            'Response': 'You said "%s"' % message
        }

        cfnresponse.send(event, context, cfnresponse.SUCCESS, attributes, physical_id)
    except Exception as e:
        log.exception(e)
        # cfnresponse's error message is always "see CloudWatch"
        cfnresponse.send(event, context, cfnresponse.FAILED, {}, physical_id)
```

以下示例显示 AWS CDK 堆栈如何调用自定义资源。

```
import cdk = require('aws-cdk-lib');
import { MyCustomResource } from './my-custom-resource';

/**
 * A stack that sets up MyCustomResource and shows how to get an attribute from it
 */
class MyStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const resource = new MyCustomResource(this, 'DemoResource', {
      message: 'CustomResource says hello',
    });

    // Publish the custom resource output
    new cdk.CfnOutput(this, 'ResponseMessage', {
      description: 'The message that came back from the Custom Resource',
      value: resource.response
    });
  }
}

const app = new cdk.App();
new MyStack(app, 'CustomResourceDemoStack');
app.synth();
```