在本文中,我们将探讨一种使用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
我留给您阅读有关诗歌插件的信息,您可以找到有关multiproject和polylith的作者存储库的详细信息。
另外,您需要在系统上安装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的核心概念:workspace,component,base,project和development 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.txt
和requirements-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.txt
和requirements-dev.txt
,因为这些将由pyproject.toml
管理,新的versiã³n看起来像:
所有这些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
,我们将看到我们的新组件在“砖”部分下列出
让我们在移动代码之前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
,则应该看到类似的东西:
评论
我为每个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
将显示:
您可以看到一个新列出现,并且标记了项目使用的砖块(基地和组件)。
将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。
此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的出色工具。