AWS CDK应用带有Polyth Code Architecture
#aws #python #cdk #polylith

在本文中,我们将探讨一种使用Polylith哲学和几个poetry插件来实现代码“ MonorePo”架构的方法,以在Python CDK应用程序中实现这一目标。


要求

当广告上继续安装诗歌和插件:

curl -sSL https://install.python-poetry.org | python3 -
poetry self add poetry-multiproject-plugin
poetry self add poetry-polylith-plugin

我留给您阅读有关诗歌插件的信息,您可以找到有关multiprojectpolylith的作者存储库的详细信息。

另外,您需要在系统上安装aws cdk,我建议您遵循requirements section of the aws cdk workshop site

npm install -g aws-cdk

初始点

我将假定CDK Python研讨会的最终版本,我们将简单地从文件夹https://github.com/aws-samples/aws-cdk-intro-workshop/tree/master/code/python/main-workshop复制代码,这将是我们的first commit,源树应该看起来像:

.
├── app.py
├── cdk.json
├── cdk_workshop
│   ├── cdk_workshop_stack.py
│   ├── hitcounter.py
│   └── __init__.py
├── lambda
│   ├── hello.py
│   └── hitcount.py
├── README.md
├── requirements-dev.txt
├── requirements.txt
└── source.bat

首先,让我们将其变成一个执行诗歌项目:

poetry init

当被要求提供主要和开发依赖性回答否时,因为我们将在以后添加。结果应该看起来像(pyproject.toml):

[tool.poetry]
name = "cdk-polylith"
version = "0.1.0"
description = ""
authors = ["Yoel Benitez Fonseca <ybenitezf@gmail.com>"]
readme = "README.md"
packages = [{include = "cdk_polylith"}]

[tool.poetry.dependencies]
python = "^3.10"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

此外,让我们添加诗歌配置以使诗歌在安装依赖项之前在项目文件夹中构建Python虚拟环境,让我们创建一个带有内容的poetry.toml文件:

[virtualenvs]
path = ".venv"
in-project = true

并加上a commit

配置polylith

在深入研究代码之前,建议阅读polyth的核心概念:workspacecomponentbaseprojectdevelopment project考虑到python-polylith是对Python的这些概念的适应。

运行(一次)我们存储库中的以下命令将为我们的项目创建必要的文件夹结构:

poetry poly create workspace --name="cdk_workshop" --theme=loose

注意:这里的--name参数将设置基本软件包结构,然后将从此名称空间导入所有代码,例如from cdk_workshop ...,请参见oficial documentation

此命令之后,我们的源树看起来像(请注意新文件夹:基础,组件,开发和项目):

.
├── app.py
├── bases
├── cdk.json
├── cdk_workshop
│   ├── cdk_workshop_stack.py
│   ├── hitcounter.py
│   └── __init__.py
├── components
├── development
├── lambda
│   ├── hello.py
│   └── hitcount.py
├── poetry.toml
├── projects
├── pyproject.toml
├── README.md
├── requirements-dev.txt
├── requirements.txt
├── source.bat
└── workspace.toml

workspace.toml将配置poetry poly ...命令的行为

让我们添加this commit。从现在开始,我们将将旧代码移至这个新结构。

项目要求,要求,要求

我们进一步安装诗歌项目:

poetry install

注意:忽略有关该项目不包含任何元素的警告

现在,我们将我们的依赖项从requirements.txtrequirements-dev.txt移动到pyproject.toml格式:

poetry add aws-cdk-lib~=2.68
poetry add 'constructs>=10.0.0,<11.0.0'
poetry add cdk-dynamo-table-view==0.2.438

和开发要求:

poetry add pytest==7.2.2 -G dev

我们现在可以删除requirements.txtrequirements-dev.txt,因为这些将由pyproject.toml管理,新的versiã³n看起来像:

pyproject.toml after adding the requirements

所有这些changes are in this commit

注意:诗歌开发人员建议将poetry.lock添加到存储库中,其他一些则报告了架构更改和.lock文件之间的问题,因此我将留给您在哪里包含此文件。

成分

摘自Polyth文档(https://polylith.gitbook.io/polylith/architecture/2.3.-component):

一个组件是一个封装的代码块,可以与基础组装在一起(通常只是一个基础),以及一组组件和库中的一组组件和库中。组件通过将其私人实施与公共接口分开来实现封装和合成性。

因此,在CDK术语中,我们的组件应为 stacks construct's ,因为这是可重复使用的部分。

在此应用程序中,我们有HitCounter构造和CdkWorkshopStack堆栈,然后添加为我们项目的组件:

poetry poly create component --name hit_counter
poetry poly create component --name cdk_workshop_stack

自动将带有工作区名称(cdk_workshop)的components下的新目录(cdk_workshop),并在此组件下为每个组件提供python包。测试的文件夹也发生了同样的事情(这是我们在创建工作区时放置--theme=loose的方式)。

我们现在需要修改pyproject.toml以识别新组件,编辑并添加以下内容到[tool.poetry]部分中的包装属性:

packages = [
    {include = "cdk_workshop/hit_counter", from = "components"},
    {include = "cdk_workshop/cdk_workshop_stack", from = "components"}
]

要确保一切都很好,请运行:

poetry install && poetry run pytest test/

如果我们现在运行poetry poly info,我们将看到我们的新组件在“砖”部分下列出

poetry poly info

让我们在移动代码之前commit this

hit_counter组件

首先,使用HitCounter构造,我们将将代码从cdk_workshop/hitcounter.py复制到components/cdk_workshop/hit_counter/core.py

cp cdk_workshop/hitcounter.py components/cdk_workshop/hit_counter/core.py
git rm cdk_workshop/hitcounter.py

此构造中的代码需要更多的重构,我们将稍后再回来,现在我们commit this as it is

cdk_workshop_stack组件

CdkWorkshopStack我们重复该过程:

cp cdk_workshop/cdk_workshop_stack.py components/cdk_workshop/cdk_workshop_stack/core.py
git rm cdk_workshop/*

有一个依赖关系,cdk_workshop_stack需要在hit_counter中定义的构造,因此我们需要编辑components/cdk_workshop/cdk_workshop_stack/core.py并在第8行中修复导入:

from constructs import Construct
from aws_cdk import (
    Stack,
    aws_lambda as _lambda,
    aws_apigateway as apigw,
)
from cdk_dynamo_table_view import TableViewer
from cdk_workshop.hit_counter.core import HitCounter

...

注意:现在,我们使用组件中的全途径(cdk_workshop.hit_counter.core),cdk_workshop工作区,hit_counter组件和core Witch是hit_counter中的一个模块,这使得组件cdk_workshop_stack cdk_workshop_stack取决于hit_counter组件,对于第一个组件,我们需要第二个。

这将添加another commit

基地

从Polylith文档(https://polylith.gitbook.io/polylith/architecture/2.2.-base)中, bases 是将公共API暴露于外界的基础。

基地具有“薄”实现,将其委派给了实施业务逻辑的组件。
基地有一个角色,那就是在外界和执行“真实工作”的逻辑之间的桥梁,即我们的组成部分。基础本身没有执行任何业务逻辑,他们只会将组件委派给组件。

因此,在AWS CDK应用程序的上下文中,基础的候选人将是定义应用程序并进行合成的模块,换句话说,现在驻留在app.py上的代码。

让我们为项目添加一个基础:

poetry poly create base --name workshop_app

类似于组件的情况,上一个命令将添加一个新的软件包,但在bases目录中:bases/cdk_workshop/workshop_app带有一个模块供我们定义基础代码-poetry poly将添加一个演示测试代码。<<<<<<<<<<<<<<<< /p>

我们需要在pyproject.toml上更改我们的软件包列表,以将新基础添加到Python项目:

packages = [
    {include = "cdk_workshop/workshop_app", from = "bases"},
    {include = "cdk_workshop/hit_counter", from = "components"},
    {include = "cdk_workshop/cdk_workshop_stack", from = "components"}
]

让我们复制代码并修复导入:

cp app.py bases/cdk_workshop/workshop_app/core.py
git rm app.py

内容应该像:

import aws_cdk as cdk

from cdk_workshop.cdk_workshop_stack.core import CdkWorkshopStack

app = cdk.App()
CdkWorkshopStack(app, "cdk-workshop")

app.synth()

结果可以看到in this commit

如果您运行poetry poly info,则应该看到类似的东西:

Image description

评论

我为每个CDK应用程序提出了一个基础,如果需要多个,则每个基数将使用并重用组件中定义的堆栈和构造。

如果您面对一个大型CDK项目,我建议您对所有构造中的单个组件软件包(Polyth Witch中的单个组件是Python软件包),每个模块一个构造。以及每个堆栈的组件,原因是要维持项目中组件之间的单个依赖源:construct component -> stack component假设堆栈的组件不取决于其他堆栈组件。

项目

项目配置Polyth的可部署工件。

换句话说,项目定义了我们部署的内容,我们将一个(或几个基础)和几个组件组合到一个允许我们部署我们的代码的工件中。

在Polyth中,项目生活在projects文件夹中,除非Sough代码与部署或建造工件有关,否则它们 不应包含代码 那里的代码。

在我们的情况下,cdk.json文件定义了一个CDK应用程序:

{
  "app": "python3 app.py",
  "context": {
    "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
    "@aws-cdk/core:stackRelativeExports": true,
    "@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
    "@aws-cdk/aws-lambda:recognizeVersionProps": true,
    "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true
  }
}

请注意"app"密钥的内容,我们删除了app.py,现在我们需要做其他事情,首先要向我们的Polylith存储库中添加一个新项目:

poetry poly create project --name cdk_app

项目名称可以是您需要或想要的任何东西,这将用于构建Python软件包。现在,项目文件夹在上面有一个带有pyproject.toml文件的新的子文件夹cdk_app,是在此文件中,我们将基础和组件组合在一起以构建用于部署的工件,编辑此文件,并在package属性中添加我们需要的内容:< br>

packages = [
    {include = "cdk_workshop/workshop_app", from = "../../bases"},
    {include = "cdk_workshop/hit_counter", from = "../../components"},
    {include = "cdk_workshop/cdk_workshop_stack", from = "../../components"}
]

请注意,我们在基础和组件中添加了一个../../

我们需要在根文件夹中添加必要的依赖项形式的pyproject.toml,从那里我们只复制了所包括的基础和组件的需求 - 没有dev依赖关系。

[tool.poetry.dependencies]
python = "^3.10"
aws-cdk-lib = ">=2.68,<3.0"
constructs = ">=10.0.0,<11.0.0"
cdk-dynamo-table-view = "0.2.438"

最终结果应该是

[tool.poetry]
name = "cdk_app"
version = "0.1.0"
description = ""
authors = ['Yoel Benitez Fonseca <ybenitezf@gmail.com>']
license = ""

packages = [
    {include = "cdk_workshop/workshop_app", from = "../../bases"},
    {include = "cdk_workshop/hit_counter", from = "../../components"},
    {include = "cdk_workshop/cdk_workshop_stack", from = "../../components"}
]

[tool.poetry.dependencies]
python = "^3.10"
aws-cdk-lib = ">=2.68,<3.0"
constructs = ">=10.0.0,<11.0.0"
cdk-dynamo-table-view = "0.2.438"

[tool.poetry.group.dev.dependencies]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

运行poetry poly info将显示:

poetry poly info

您可以看到一个新列出现,并且标记了项目使用的砖块(基地和组件)。

cdk.json文件移至项目文件夹

mv cdk.json projects/cdk_app/cdk.json

但是,由于我们将应用程序对象移至bases/cdk_workshop/workshop_app/core.py模块,因此我们需要编辑cdk.json并将app条目更改为:


  "app": "python3 -m cdk_workshop.workshop_app.core"

让添加checkpoint here and commit our changes

CDK项目新家

从理论上讲,我们可以部署我们的CDK应用程序,让我们测试以下内容:

cd projects/cdk_app
poetry build-project

这将使用Python软件包在projects/cdk_app中创建一个dist目录。

.gitignore中包含这个需求,以使此更简单的副本在recommended gitignore for python上,并将其添加到存储库根(https://github.com/ybenitezf/cdk_polylith/commit/0c0eda0cf6f2d75e6899a8e18b2374663d4a8b8d)中的.gitignore。

project folder files tree

此Python软件包包含我们的CDK应用程序,因此,为了测试我们的理论,我们需要创建一个Python Virtual Env,安装此软件包并运行cdk synth(在projects/cdk_app文件夹中),并将看到云形式模板:

python3 -m venv .venv
source .venv/bin/activate
pip install dist/cdk_app-0.1.0-py3-none-any.whl
cdk synth

糟糕!!!,我们会出现错误,类似:

RuntimeError: Cannot find asset at cdk_polylith/projects/cdk_app/lambda

的原因是,以前的实现假设任何CDK命令都将在存储库的根部执行,并且我们已将其移至projects/cdk_app,为了解决此问题,我们可以将lambda文件夹移动到projects/cdk_app并再次测试:< br>

cd ../../
mv lambda/ projects/cdk_app/
cd projects/cdk_app/
cdk synth

现在所有的工作都很好!!! ...嗯,不是真的,polyth背后的所有想法是所有代码都活在组件或基础文件夹中。

让我们丢弃这最后的更改,并以polyth的方式解决此问题 - (请记住要退出为cdk_app项目创建的VENV)。

包括Lambda功能代码

所以在这个项目中,我们有2个lambdas:

./lambda/
├── hello.py
└── hitcount.py

这里的计划是添加两个基础(每个功能一个)。 Botch非常简单,只有hitcount.py具有对Boto3的外部依赖。

让我们先添加基础:

poetry poly create base --name hello_lambda
poetry poly create base --name hitcounter_lambda

注意:如果这些函数共享代码,即可以重构的东西,以便他们俩都使用它,最好为此功能添加一个新组件。

,我们将此新基础添加到主pyproject.toml包属性:

packages = [
    {include = "cdk_workshop/workshop_app", from = "bases"},
    {include = "cdk_workshop/hello_lambda", from = "bases"},
    {include = "cdk_workshop/hitcounter_lambda", from = "bases"},
    {include = "cdk_workshop/hit_counter", from = "components"},
    {include = "cdk_workshop/cdk_workshop_stack", from = "components"}
]

,我们将依赖项添加到:

poetry add boto3

运行poetry install && poetry run pytest test/以确保一切正确。

移动代码:

mv lambda/hello.py bases/cdk_workshop/hello_lambda/core.py
mv lambda/hitcount.py bases/cdk_workshop/hitcounter_lambda/core.py
rm -rf lambda/

让添加一个checkpoint here and commit our changes

现在的诀窍是为每个lambda函数生成一个Python软件包,并使用Lambda CDK构造的捆绑选项注入我们对Lambda的代码和要求。首先,为每个lambda添加项目:

poetry poly create project --name hello_lambda_project
poetry poly create project --name hitcounter_lambda_project

我们重复该过程,与cdk_app相同,projects/hello_lambda_project/pyproject.toml应该引用hello_lambda基础:

...

packages = [
    {include = "cdk_workshop/hello_lambda", from = "../../bases"}
]

...

projects/hitcounter_lambda_project/pyproject.toml用于hitcounter_lambda-包括boto3的依赖性:

packages = [
    {include = "cdk_workshop/hitcounter_lambda", from = "../../bases"}
]

[tool.poetry.dependencies]
python = "^3.10"
boto3 = "^1.26.123"

CdkWorkshopStack代码中,我们将lambda函数定义更改为:



        hello = _lambda.Function(
            self,
            "HelloHandler",
            runtime=_lambda.Runtime.PYTHON_3_9,
            code=_lambda.Code.from_asset(
                "lambda/hello",
                bundling=BundlingOptions(
                    image=_lambda.Runtime.PYTHON_3_9.bundling_image,
                    command=[
                        "bash", "-c",
                        "pip install -r requirements.txt -t"
                        " /asset-output && cp -au . /asset-output"
                    ]
                )
            ),
            handler="cdk_workshop.hello_lambda.core.handler",
        )


请注意handler声明,如cdk.json中,我们使用包全名空间来声明我们的处理程序,_lambda.Runtime.PYTHON_3_9.bundling_image将使用我们将生成的unignts.txt捆绑lambda代码。

让重复hitcounter_lambda的过程,在components/cdk_workshop/hit_counter/core.py中,我们更改:


            handler="cdk_workshop.hitcounter_lambda.core.handler",
            code=_lambda.Code.from_asset(
                "lambda/hello",
                bundling=BundlingOptions(
                    image=_lambda.Runtime.PYTHON_3_9.bundling_image,
                    command=[
                        "bash", "-c",
                        "pip install -r requirements.txt -t"
                        " /asset-output && cp -au . /asset-output"
                    ]
                )
            ),
            runtime=_lambda.Runtime.PYTHON_3_9,

添加我们将所需的文件夹(资产文件夹)添加到cdk_app项目。

mkdir -p mkdir -p projects/cdk_app/lambda/{hello,hitcounter}
touch projects/cdk_app/lambda/{hello,hitcounter}/requirements.txt

让添加一个checkpoint here and commit our changes

现在我们再次尝试部署,首先我们构建Lambda的软件包:

cd projects/hello_lambda_project
poetry build-project
cd ../hitcounter_lambda_project/
poetry build-project
cd ../../

我们的项目/文件夹应该具有此结构:

./projects/
├── cdk_app
│   ├── cdk.json
│   ├── dist
│   │   ├── cdk_app-0.1.0-py3-none-any.whl
│   │   └── cdk_app-0.1.0.tar.gz
│   ├── lambda
│   │   ├── hello
│   │   │   └── requirements.txt
│   │   └── hitcounter
│   │       └── requirements.txt
│   └── pyproject.toml
├── hello_lambda_project
│   ├── dist
│   │   ├── hello_lambda_project-0.1.0-py3-none-any.whl
│   │   └── hello_lambda_project-0.1.0.tar.gz
│   └── pyproject.toml
└── hitcounter_lambda_project
    ├── dist
    │   ├── hitcounter_lambda_project-0.1.0-py3-none-any.whl
    │   └── hitcounter_lambda_project-0.1.0.tar.gz
    └── pyproject.toml

我们需要将lambdas的.whl添加到cdk_app项目上的相应的requirements.txt文件:

cd projects/cdk_app/
cp ../hello_lambda_project/dist/*.whl lambda/hello/
cp ../hitcounter_lambda_project/dist/*.whl lambda/hitcounter/
cd lambda/hello/
ls * | find -type f -name "*.whl" > requirements.txt
cd ../hitcounter/
ls * | find -type f -name "*.whl" > requirements.txt
cd ../../ # back to projects/cdk_app
poetry build-project # need to rebuild since we make changes
source .venv/bin/activate
# --force-reinstall is necessary unless we change the package version
pip install --force-reinstall dist/cdk_app-0.1.0-py3-none-any.whl

现在我们可以再次部署:

# from the projects/cdk_app/ with the python virtualenv active
cdk deploy

非常重要的是,大多数此过程可能是DevOps设置的一部分,很少您会手动进行此操作。

重要 lambda的lambda会失败,如果正确包含在lambda软件包代码中,则无法找到处理程序模块事件,为此,您需要Runtime.PYTHON_3_9至少

让我们添加最后一个checkpoint here and commit our changes

最终考虑

所有代码都可以在存储库中找到:https://github.com/ybenitezf/cdk_polylith

  • 如果您以这种方式管理所有项目,更改或启动新项目是一件容易的事:所有存储库看起来都相同并具有相同的元素。
  • 使用同一存储库中的所有代码,您可以查看即使它们分别部署了系统的某些部分也会破坏系统的其他部分。
  • 代码和部署工件之间存在明显的分离

致谢

感谢Sebastian Aurei的修订,更正和帮助。

感谢David Vujic的出色工具。