Monorepo architecture for Python code with Polylith and Poetry
By: Yoel Benitez Fonseca
Review and Corrections by: Robmay S. Garcia
This article explores a technique or methodology for achieving a “monorepo” architecture for code by leveraging the Polylith philosophy, along with a few poetry plugins, for implementation in Python CDK applications.
Requirements
Let’s go ahead and get our dependencies installed for poetry and the plugins:
curl <span>-sSL</span> https://install.python-poetry.org | python3 -poetry self add poetry-multiproject-pluginpoetry self add poetry-polylith-plugincurl <span>-sSL</span> https://install.python-poetry.org | python3 - poetry self add poetry-multiproject-plugin poetry self add poetry-polylith-plugincurl -sSL https://install.python-poetry.org | python3 - poetry self add poetry-multiproject-plugin poetry self add poetry-polylith-plugin
Enter fullscreen mode Exit fullscreen mode
You will also need AWS CDK
installed on your system. I will recommend you to follow the requirements section of the aws cdk workshop site:
npm <span>install</span> <span>-g</span> aws-cdknpm <span>install</span> <span>-g</span> aws-cdknpm install -g aws-cdk
Enter fullscreen mode Exit fullscreen mode
Here I leave some additional readings about poetry and its plugins.multiproject and for polylith.
Starting point
Let’s get started. As a starting point we will be using the final version of the CDK Python Workshop. Simply clone the code from the repository https://github.com/aws-samples/aws-cdk-intro-workshop/tree/master/code/python/main-workshop. Our first commit will be like this and the resulting source tree should looks like:
.├── 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. ├── 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. ├── 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
Enter fullscreen mode Exit fullscreen mode
Now, let’s turn this source tree into a poetry project by executing:
poetry initpoetry initpoetry init
Enter fullscreen mode Exit fullscreen mode
When asked for the main and development dependencies answer no, we will add then later. The result should look like (pyproject.toml
):
<span>[tool.poetry]</span><span>name</span> <span>=</span> <span>"cdk-polylith"</span><span>version</span> <span>=</span> <span>"0.1.0"</span><span>description</span> <span>=</span> <span>""</span><span>authors</span> <span>=</span> <span>[</span><span>"Yoel Benitez Fonseca <ybenitezf@gmail.com>"</span><span>]</span><span>readme</span> <span>=</span> <span>"README.md"</span><span>packages</span> <span>=</span> <span>[{include</span> <span>=</span> <span>"cdk_polylith"</span><span>}]</span><span>[tool.poetry.dependencies]</span><span>python</span> <span>=</span> <span>"^3.10"</span><span>[build-system]</span><span>requires</span> <span>=</span> <span>[</span><span>"poetry-core"</span><span>]</span><span>build-backend</span> <span>=</span> <span>"poetry.core.masonry.api"</span><span>[tool.poetry]</span> <span>name</span> <span>=</span> <span>"cdk-polylith"</span> <span>version</span> <span>=</span> <span>"0.1.0"</span> <span>description</span> <span>=</span> <span>""</span> <span>authors</span> <span>=</span> <span>[</span><span>"Yoel Benitez Fonseca <ybenitezf@gmail.com>"</span><span>]</span> <span>readme</span> <span>=</span> <span>"README.md"</span> <span>packages</span> <span>=</span> <span>[{include</span> <span>=</span> <span>"cdk_polylith"</span><span>}]</span> <span>[tool.poetry.dependencies]</span> <span>python</span> <span>=</span> <span>"^3.10"</span> <span>[build-system]</span> <span>requires</span> <span>=</span> <span>[</span><span>"poetry-core"</span><span>]</span> <span>build-backend</span> <span>=</span> <span>"poetry.core.masonry.api"</span>[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"
Enter fullscreen mode Exit fullscreen mode
Additionally, let’s add a poetry configuration file poetry.toml
to have poetry build the python virtual environment in the project folder before installing the dependencies. The file content should look like:
<span>[virtualenvs]</span><span>path</span> <span>=</span> <span>".venv"</span><span>in-project</span> <span>=</span> <span>true</span><span>[virtualenvs]</span> <span>path</span> <span>=</span> <span>".venv"</span> <span>in-project</span> <span>=</span> <span>true</span>[virtualenvs] path = ".venv" in-project = true
Enter fullscreen mode Exit fullscreen mode
Create a commit with what we already have.
Configuring polylith
Before delving into the code, I strongly recommend you to read the following polylith core concepts: workspace, component, base, project and development project taking into account that the python-polylith is an adaptation of those concepts for Python.
For those of you who are impatient, running the following command (only once) in our repository will create the necessary folder structure for our project:
poetry poly create workspace <span>--name</span><span>=</span><span>"cdk_workshop"</span> <span>--theme</span><span>=</span>loosepoetry poly create workspace <span>--name</span><span>=</span><span>"cdk_workshop"</span> <span>--theme</span><span>=</span>loosepoetry poly create workspace --name="cdk_workshop" --theme=loose
Enter fullscreen mode Exit fullscreen mode
note: The
--name
parameter here will set the base package structure, all the code then will be imported from this namespace, for examplefrom cdk_workshop ...
for more details on this read the official documentation
After running the above command our source tree will look like this, note the new folders created (bases, components, development, and projects):
.├── 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. ├── 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. ├── 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
Enter fullscreen mode Exit fullscreen mode
The workspace.toml
file will configure the behavior of the poetry poly ...
commands
Let’s create a new commit. From now on, we will be moving the old code to this new structure.
Managing project requirements.
Before we go any further let’s install the poetry project:
poetry <span>install</span>poetry <span>install</span>poetry install
Enter fullscreen mode Exit fullscreen mode
note: ignore the warning about the project not containing any element’s
And now we move our dependencies from requirements.txt
and requirements-dev.txt
to the pyproject.toml
format:
poetry add aws-cdk-lib~<span>=</span>2.68poetry add <span>'constructs>=10.0.0,<11.0.0'</span>poetry add cdk-dynamo-table-view<span>==</span>0.2.438poetry add aws-cdk-lib~<span>=</span>2.68 poetry add <span>'constructs>=10.0.0,<11.0.0'</span> poetry add cdk-dynamo-table-view<span>==</span>0.2.438poetry add aws-cdk-lib~=2.68 poetry add 'constructs>=10.0.0,<11.0.0' poetry add cdk-dynamo-table-view==0.2.438
Enter fullscreen mode Exit fullscreen mode
And for dev requirements:
poetry add <span>pytest</span><span>==</span>7.2.2 <span>-G</span> devpoetry add <span>pytest</span><span>==</span>7.2.2 <span>-G</span> devpoetry add pytest==7.2.2 -G dev
Enter fullscreen mode Exit fullscreen mode
Now we can remove requirements.txt
and requirements-dev.txt
files because they will be managed by the pyproject.toml
. The content of the file will now look like:
All the changes will be visible in this commit.
note: Poetry developers recommend to add the
poetry.lock
to the repository. Other developers have reported problems with architecture changes and the.lock
file, so I will leave it up to you to decide if you want to use it or not.
Components
From the polylith documentation (https://polylith.gitbook.io/polylith/architecture/2.3.-component):
A component is an encapsulated block of code that can be assembled together with a base (it’s often just a single base) and a set of components and libraries into services, libraries or tools. Components achieve encapsulation and composability by separating their private implementation from their public interface.
So in CDK term’s our component should be Stacks or Constructs since this are the reusable parts.
In this application we have the HitCounter
construct and the CdkWorkshopStack
stack, lets add them as components to our project:
poetry poly create component --name hit_counterpoetry poly create component --name cdk_workshop_stackpoetry poly create component --name hit_counter poetry poly create component --name cdk_workshop_stackpoetry poly create component --name hit_counter poetry poly create component --name cdk_workshop_stack
Enter fullscreen mode Exit fullscreen mode
We will get a new directory under components
with the name of the workspace (cdk_workshop
) and under this a python package for each of the components. The same has happened to the tests folder (that is why we used --theme=loose
when creating the workspace).
Next, we need to modify pyproject.toml
to recognize this components. Edit and add the following to the package property in the [tool.poetry]
section:
<span>packages</span> <span>=</span> <span>[</span><span>{include</span> <span>=</span> <span>"cdk_workshop/hit_counter"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/cdk_workshop_stack"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span><span>]</span><span>packages</span> <span>=</span> <span>[</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/hit_counter"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/cdk_workshop_stack"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span> <span>]</span>packages = [ {include = "cdk_workshop/hit_counter", from = "components"}, {include = "cdk_workshop/cdk_workshop_stack", from = "components"} ]
Enter fullscreen mode Exit fullscreen mode
To make sure all is fine, run:
poetry <span>install</span> <span>&&</span> poetry run pytest <span>test</span>/poetry <span>install</span> <span>&&</span> poetry run pytest <span>test</span>/poetry install && poetry run pytest test/
Enter fullscreen mode Exit fullscreen mode
if we run poetry poly info
we will see our new components listed under the bricks section
Alright, let’s commit this changes before moving into the code.
The hit_counter
component
Now that we have a HitCounter
construct, we will copy the code in cdk_workshop/hitcounter.py
to components/cdk_workshop/hit_counter/core.py
by executing:
<span>cp </span>cdk_workshop/hitcounter.py components/cdk_workshop/hit_counter/core.pygit <span>rm </span>cdk_workshop/hitcounter.py<span>cp </span>cdk_workshop/hitcounter.py components/cdk_workshop/hit_counter/core.py git <span>rm </span>cdk_workshop/hitcounter.pycp cdk_workshop/hitcounter.py components/cdk_workshop/hit_counter/core.py git rm cdk_workshop/hitcounter.py
Enter fullscreen mode Exit fullscreen mode
The code in this construct will need additional refactoring but we will come back to it later, for now we commit this change as is.
The cdk_workshop_stack
component
We repeat the same process for the CdkWorkshopStack
component, just change the file name and destination as shown below:
cp cdk_workshop/cdk_workshop_stack.py components/cdk_workshop/cdk_workshop_stack/core.pygit rm cdk_workshop/*cp cdk_workshop/cdk_workshop_stack.py components/cdk_workshop/cdk_workshop_stack/core.py git rm cdk_workshop/*cp cdk_workshop/cdk_workshop_stack.py components/cdk_workshop/cdk_workshop_stack/core.py git rm cdk_workshop/*
Enter fullscreen mode Exit fullscreen mode
Now, pay attention to this little but important detail. There is a dependency between both components, cdk_workshop_stack
needs the construct defined in hit_counter
so we need to edit components/cdk_workshop/cdk_workshop_stack/core.py
file to fix the import statement as shown in line 8 of the following snippet:
<span>from</span> <span>constructs</span> <span>import</span> <span>Construct</span><span>from</span> <span>aws_cdk</span> <span>import</span> <span>(</span><span>Stack</span><span>,</span><span>aws_lambda</span> <span>as</span> <span>_lambda</span><span>,</span><span>aws_apigateway</span> <span>as</span> <span>apigw</span><span>,</span><span>)</span><span>from</span> <span>cdk_dynamo_table_view</span> <span>import</span> <span>TableViewer</span><span>from</span> <span>cdk_workshop.hit_counter.core</span> <span>import</span> <span>HitCounter</span><span>...</span><span>from</span> <span>constructs</span> <span>import</span> <span>Construct</span> <span>from</span> <span>aws_cdk</span> <span>import</span> <span>(</span> <span>Stack</span><span>,</span> <span>aws_lambda</span> <span>as</span> <span>_lambda</span><span>,</span> <span>aws_apigateway</span> <span>as</span> <span>apigw</span><span>,</span> <span>)</span> <span>from</span> <span>cdk_dynamo_table_view</span> <span>import</span> <span>TableViewer</span> <span>from</span> <span>cdk_workshop.hit_counter.core</span> <span>import</span> <span>HitCounter</span> <span>...</span>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 ...
Enter fullscreen mode Exit fullscreen mode
Note: Now we are able to use the fully qualified path to the class component like (
cdk_workshop.hit_counter.core
). The path is composed bycdk_workshop
the workspace,hit_counter
the component, andcore
the module inhit_counter
.
Let’s add another commit.
Bases
From the polylith documentation (https://polylith.gitbook.io/polylith/architecture/2.2.-base), bases are the building blocks that exposes a public API to the outside world.
A base has a “thin” implementation which delegates to components where the business logic is implemented.
A base has one role and that is to be a bridge between the outside world and the logic that performs the “real work”, our components. Bases don’t perform any business logic themselves, they only delegate to components.
So, in the context of the AWS CDK application the candidate for a base will be the module that defines the application and do the synthesis, in other words the code that now resides on app.py
.
Let’s add a base to the project:
poetry poly create base <span>--name</span> workshop_apppoetry poly create base <span>--name</span> workshop_apppoetry poly create base --name workshop_app
Enter fullscreen mode Exit fullscreen mode
Like in the case of the components, the previous command, will add a new package but in the bases directory. This time, under the path bases/cdk_workshop/workshop_app
with a module for us to define the code of our base – poetry poly
will add a demo test code too.
We need to alter our package list on pyproject.toml
to add the newly created base to the Python project:
<span>packages</span> <span>=</span> <span>[</span><span>{include</span> <span>=</span> <span>"cdk_workshop/workshop_app"</span><span>,</span> <span>from</span> <span>=</span> <span>"bases"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/hit_counter"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/cdk_workshop_stack"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span><span>]</span><span>packages</span> <span>=</span> <span>[</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/workshop_app"</span><span>,</span> <span>from</span> <span>=</span> <span>"bases"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/hit_counter"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/cdk_workshop_stack"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span> <span>]</span>packages = [ {include = "cdk_workshop/workshop_app", from = "bases"}, {include = "cdk_workshop/hit_counter", from = "components"}, {include = "cdk_workshop/cdk_workshop_stack", from = "components"} ]
Enter fullscreen mode Exit fullscreen mode
Let’s copy the code and fix the imports:
<span>cp </span>app.py bases/cdk_workshop/workshop_app/core.pygit <span>rm </span>app.py<span>cp </span>app.py bases/cdk_workshop/workshop_app/core.py git <span>rm </span>app.pycp app.py bases/cdk_workshop/workshop_app/core.py git rm app.py
Enter fullscreen mode Exit fullscreen mode
The file content should look like:
<span>import</span> <span>aws_cdk</span> <span>as</span> <span>cdk</span><span>from</span> <span>cdk_workshop.cdk_workshop_stack.core</span> <span>import</span> <span>CdkWorkshopStack</span><span>app</span> <span>=</span> <span>cdk</span><span>.</span><span>App</span><span>()</span><span>CdkWorkshopStack</span><span>(</span><span>app</span><span>,</span> <span>"</span><span>cdk-workshop</span><span>"</span><span>)</span><span>app</span><span>.</span><span>synth</span><span>()</span><span>import</span> <span>aws_cdk</span> <span>as</span> <span>cdk</span> <span>from</span> <span>cdk_workshop.cdk_workshop_stack.core</span> <span>import</span> <span>CdkWorkshopStack</span> <span>app</span> <span>=</span> <span>cdk</span><span>.</span><span>App</span><span>()</span> <span>CdkWorkshopStack</span><span>(</span><span>app</span><span>,</span> <span>"</span><span>cdk-workshop</span><span>"</span><span>)</span> <span>app</span><span>.</span><span>synth</span><span>()</span>import aws_cdk as cdk from cdk_workshop.cdk_workshop_stack.core import CdkWorkshopStack app = cdk.App() CdkWorkshopStack(app, "cdk-workshop") app.synth()
Enter fullscreen mode Exit fullscreen mode
The result can be seen in this commit.
If you run poetry poly info
you should see something like this:
Remarks
I suggest the use of a single base for each cdk application, but if more than one is necessary, each base should reuse the stacks and constructs defined in the components.
If you are facing a large CDK project, I recommend maintaining a single component package (a single component in polylith is a python package) for all the constructs, one construct per module. And a component for each Stack, the reason being to maintain a single source of dependencies between the components in the project: construct component -> stack component
assuming the stack’s components do not depend on the others stack components.
Projects
Projects configure Polylith’s deployable artifacts.
In other words, projects define what we deploy, we combine one (or several bases but that’s rare) base and several components into an artifact that allow us to deploy our code.
In polylith the projects live in the projects
folder and they should not contain code unless such code is related to the deployment or building of the artifacts, in other words no python code there.
A CDK application is defined by the cdk.json
file, in our case:
<span>{</span><span> </span><span>"app"</span><span>:</span><span> </span><span>"python3 app.py"</span><span>,</span><span> </span><span>"context"</span><span>:</span><span> </span><span>{</span><span> </span><span>"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"@aws-cdk/core:stackRelativeExports"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"@aws-cdk/aws-rds:lowercaseDbIdentifier"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"@aws-cdk/aws-lambda:recognizeVersionProps"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021"</span><span>:</span><span> </span><span>true</span><span> </span><span>}</span><span> </span><span>}</span><span> </span><span>{</span><span> </span><span>"app"</span><span>:</span><span> </span><span>"python3 app.py"</span><span>,</span><span> </span><span>"context"</span><span>:</span><span> </span><span>{</span><span> </span><span>"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"@aws-cdk/core:stackRelativeExports"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"@aws-cdk/aws-rds:lowercaseDbIdentifier"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"@aws-cdk/aws-lambda:recognizeVersionProps"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021"</span><span>:</span><span> </span><span>true</span><span> </span><span>}</span><span> </span><span>}</span><span> </span>{ "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 } }
Enter fullscreen mode Exit fullscreen mode
Note the content of the "app"
key, we’ve removed app.py
and now we need to do something else, beginning by adding a new project to our polylith repository:
poetry poly create project <span>--name</span> cdk_apppoetry poly create project <span>--name</span> cdk_apppoetry poly create project --name cdk_app
Enter fullscreen mode Exit fullscreen mode
The project name can be anything you need or want, this will be used to build a python package. Now the projects folder have a new subfolder cdk_app
with a pyproject.toml
file on it. In this file is where we combine our bases and components to build the artifact to deploy. Edit this file to add our include statements under the package
property as shown below:
<span>packages</span> <span>=</span> <span>[</span><span>{include</span> <span>=</span> <span>"cdk_workshop/workshop_app"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../bases"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/hit_counter"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../components"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/cdk_workshop_stack"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../components"</span><span>}</span><span>]</span><span>packages</span> <span>=</span> <span>[</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/workshop_app"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../bases"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/hit_counter"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../components"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/cdk_workshop_stack"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../components"</span><span>}</span> <span>]</span>packages = [ {include = "cdk_workshop/workshop_app", from = "../../bases"}, {include = "cdk_workshop/hit_counter", from = "../../components"}, {include = "cdk_workshop/cdk_workshop_stack", from = "../../components"} ]
Enter fullscreen mode Exit fullscreen mode
Note that we’ve added a ../../
to bases and components because this pyproject file is two levels down in the path
Next, we need to add the necessary dependencies form the pyproject.toml
in the root folder, from there we only copy what we need for the bases and components, no dev dependencies.
<span>[tool.poetry.dependencies]</span><span>python</span> <span>=</span> <span>"^3.10"</span><span>aws-cdk-lib</span> <span>=</span> <span>"></span><span>=</span><span>2.68</span><span>,</span><span><</span><span>3.0</span><span>"</span><span> </span><span>constructs</span> <span>=</span> <span>"></span><span>=</span><span>10.0</span><span>.</span><span>0</span><span>,</span><span><</span><span>11.0</span><span>.</span><span>0</span><span>"</span><span> </span><span>cdk-dynamo-table-view</span> <span>=</span> <span>"0.2.438"</span><span>[tool.poetry.dependencies]</span> <span>python</span> <span>=</span> <span>"^3.10"</span> <span>aws-cdk-lib</span> <span>=</span> <span>"></span><span>=</span><span>2.68</span><span>,</span><span><</span><span>3.0</span><span>"</span><span> </span><span>constructs</span> <span>=</span> <span>"></span><span>=</span><span>10.0</span><span>.</span><span>0</span><span>,</span><span><</span><span>11.0</span><span>.</span><span>0</span><span>"</span><span> </span><span>cdk-dynamo-table-view</span> <span>=</span> <span>"0.2.438"</span>[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"
Enter fullscreen mode Exit fullscreen mode
The final result should be something like:
<span>[tool.poetry]</span><span>name</span> <span>=</span> <span>"cdk_app"</span><span>version</span> <span>=</span> <span>"0.1.0"</span><span>description</span> <span>=</span> <span>""</span><span>authors</span> <span>=</span> <span>[</span><span>'Yoel Benitez Fonseca <ybenitezf@gmail.com>'</span><span>]</span><span>license</span> <span>=</span> <span>""</span><span>packages</span> <span>=</span> <span>[</span><span>{include</span> <span>=</span> <span>"cdk_workshop/workshop_app"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../bases"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/hit_counter"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../components"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/cdk_workshop_stack"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../components"</span><span>}</span><span>]</span><span>[tool.poetry.dependencies]</span><span>python</span> <span>=</span> <span>"^3.10"</span><span>aws-cdk-lib</span> <span>=</span> <span>"></span><span>=</span><span>2.68</span><span>,</span><span><</span><span>3.0</span><span>"</span><span> </span><span>constructs</span> <span>=</span> <span>"></span><span>=</span><span>10.0</span><span>.</span><span>0</span><span>,</span><span><</span><span>11.0</span><span>.</span><span>0</span><span>"</span><span> </span><span>cdk-dynamo-table-view</span> <span>=</span> <span>"0.2.438"</span><span>[tool.poetry.group.dev.dependencies]</span><span>[build-system]</span><span>requires</span> <span>=</span> <span>["poetry-core></span><span>=</span><span>1.0</span><span>.</span><span>0</span><span>"]</span><span> </span><span>build-backend</span> <span>=</span> <span>"poetry.core.masonry.api"</span><span>[tool.poetry]</span> <span>name</span> <span>=</span> <span>"cdk_app"</span> <span>version</span> <span>=</span> <span>"0.1.0"</span> <span>description</span> <span>=</span> <span>""</span> <span>authors</span> <span>=</span> <span>[</span><span>'Yoel Benitez Fonseca <ybenitezf@gmail.com>'</span><span>]</span> <span>license</span> <span>=</span> <span>""</span> <span>packages</span> <span>=</span> <span>[</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/workshop_app"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../bases"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/hit_counter"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../components"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/cdk_workshop_stack"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../components"</span><span>}</span> <span>]</span> <span>[tool.poetry.dependencies]</span> <span>python</span> <span>=</span> <span>"^3.10"</span> <span>aws-cdk-lib</span> <span>=</span> <span>"></span><span>=</span><span>2.68</span><span>,</span><span><</span><span>3.0</span><span>"</span><span> </span><span>constructs</span> <span>=</span> <span>"></span><span>=</span><span>10.0</span><span>.</span><span>0</span><span>,</span><span><</span><span>11.0</span><span>.</span><span>0</span><span>"</span><span> </span><span>cdk-dynamo-table-view</span> <span>=</span> <span>"0.2.438"</span> <span>[tool.poetry.group.dev.dependencies]</span> <span>[build-system]</span> <span>requires</span> <span>=</span> <span>["poetry-core></span><span>=</span><span>1.0</span><span>.</span><span>0</span><span>"]</span><span> </span><span>build-backend</span> <span>=</span> <span>"poetry.core.masonry.api"</span>[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"
Enter fullscreen mode Exit fullscreen mode
Running poetry poly info
will show:
As you can see a new column has appeared and the bricks (bases and components) used by the project are marked.
Next, move the cdk.json
file to the project folder
<span>mv </span>cdk.json projects/cdk_app/cdk.json<span>mv </span>cdk.json projects/cdk_app/cdk.jsonmv cdk.json projects/cdk_app/cdk.json
Enter fullscreen mode Exit fullscreen mode
But because we move our app object to the bases/cdk_workshop/workshop_app/core.py
module we need to edit cdk.json
and change the app
entry to:
"app": "python3 -m cdk_workshop.workshop_app.core""app": "python3 -m cdk_workshop.workshop_app.core""app": "python3 -m cdk_workshop.workshop_app.core"
Enter fullscreen mode Exit fullscreen mode
Let’s add a checkpoint here and commit our changes.
cdk project new home
At this point we should be able to deploy our CDK application (theoretically speaking), let’s test that assumption:
<span>cd </span>projects/cdk_apppoetry build-project<span>cd </span>projects/cdk_app poetry build-projectcd projects/cdk_app poetry build-project
Enter fullscreen mode Exit fullscreen mode
This build-project
command will create a dist
directory under projects/cdk_app
containing the python package.
This new directory need to be include in the
.gitignore
file. To make this step simpler, copy the content of the recommended gitignore for python file and add it to the .gitignore in the repository root as shown in this example commit.
This python package contains our CDK app. So, to test our theory we need to created a python virtual env, install this package, and run cdk synth
(under the projects/cdk_app
folder) to see the CloudFormation template:
python3 <span>-m</span> venv .venv<span>source</span> .venv/bin/activatepip <span>install </span>dist/cdk_app-0.1.0-py3-none-any.whlcdk synthpython3 <span>-m</span> venv .venv <span>source</span> .venv/bin/activate pip <span>install </span>dist/cdk_app-0.1.0-py3-none-any.whl cdk synthpython3 -m venv .venv source .venv/bin/activate pip install dist/cdk_app-0.1.0-py3-none-any.whl cdk synth
Enter fullscreen mode Exit fullscreen mode
But wait, we get and error. Something like:
RuntimeError: Cannot find asset at cdk_polylith/projects/cdk_app/lambdaRuntimeError: Cannot find asset at cdk_polylith/projects/cdk_app/lambdaRuntimeError: Cannot find asset at cdk_polylith/projects/cdk_app/lambda
Enter fullscreen mode Exit fullscreen mode
The root cause for this error is that the previous implementation assumed that any cdk command would be execute on the root of the repository but our app has been moved to projects/cdk_app
. To fix this, we need to move the lambda
folder under projects/cdk_app
and run cdk synth
again:
<span>cd</span> ../../<span>mv </span>lambda/ projects/cdk_app/<span>cd </span>projects/cdk_app/cdk synth<span>cd</span> ../../ <span>mv </span>lambda/ projects/cdk_app/ <span>cd </span>projects/cdk_app/ cdk synthcd ../../ mv lambda/ projects/cdk_app/ cd projects/cdk_app/ cdk synth
Enter fullscreen mode Exit fullscreen mode
Now all should work great!!! … ummm no, not really. The idea behind polylith is that all code should live in the components or bases folders.
So, let’s go back, discard these last changes and solve this problem in the polylith way – (don’t forget to exit the venv created for the cdk_app
project).
Include lambda functions code, the polilyth way.
In this project we have 2 lambdas:
./lambda/├── hello.py└── hitcount.py./lambda/ ├── hello.py └── hitcount.py./lambda/ ├── hello.py └── hitcount.py
Enter fullscreen mode Exit fullscreen mode
The plan here is to add to bases (one for each function) to the project. Both are pretty simple, only hitcount.py
have an external dependency to boto3.
Let’s add the bases first:
poetry poly create base --name hello_lambdapoetry poly create base --name hitcounter_lambdapoetry poly create base --name hello_lambda poetry poly create base --name hitcounter_lambdapoetry poly create base --name hello_lambda poetry poly create base --name hitcounter_lambda
Enter fullscreen mode Exit fullscreen mode
Note: If these functions shared code (e.g: something that could be refactored so that they both use it), it would be a good idea to add a new component for this feature.
Next, we add this new bases to the main pyproject.toml
packages property:
<span>packages</span> <span>=</span> <span>[</span><span>{include</span> <span>=</span> <span>"cdk_workshop/workshop_app"</span><span>,</span> <span>from</span> <span>=</span> <span>"bases"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/hello_lambda"</span><span>,</span> <span>from</span> <span>=</span> <span>"bases"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/hitcounter_lambda"</span><span>,</span> <span>from</span> <span>=</span> <span>"bases"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/hit_counter"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span><span>,</span><span>{include</span> <span>=</span> <span>"cdk_workshop/cdk_workshop_stack"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span><span>]</span><span>packages</span> <span>=</span> <span>[</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/workshop_app"</span><span>,</span> <span>from</span> <span>=</span> <span>"bases"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/hello_lambda"</span><span>,</span> <span>from</span> <span>=</span> <span>"bases"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/hitcounter_lambda"</span><span>,</span> <span>from</span> <span>=</span> <span>"bases"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/hit_counter"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span><span>,</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/cdk_workshop_stack"</span><span>,</span> <span>from</span> <span>=</span> <span>"components"</span><span>}</span> <span>]</span>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"} ]
Enter fullscreen mode Exit fullscreen mode
Adding any dependencies too:
poetry add boto3poetry add boto3poetry add boto3
Enter fullscreen mode Exit fullscreen mode
Run poetry install && poetry run pytest test/
to ensure all is correct.
Now, let’s move the code:
<span>mv </span>lambda/hello.py bases/cdk_workshop/hello_lambda/core.py<span>mv </span>lambda/hitcount.py bases/cdk_workshop/hitcounter_lambda/core.py<span>rm</span> <span>-rf</span> lambda/<span>mv </span>lambda/hello.py bases/cdk_workshop/hello_lambda/core.py <span>mv </span>lambda/hitcount.py bases/cdk_workshop/hitcounter_lambda/core.py <span>rm</span> <span>-rf</span> lambda/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/
Enter fullscreen mode Exit fullscreen mode
Let’s add a checkpoint here and commit our changes.
The trick now is to generate a python package for each lambda function and use the bundling options of the lambda cdk construct to inject our code and requirements for the lambdas. Let’s begin by adding the projects for each lambda:
poetry poly create project <span>--name</span> hello_lambda_projectpoetry poly create project <span>--name</span> hitcounter_lambda_projectpoetry poly create project <span>--name</span> hello_lambda_project poetry poly create project <span>--name</span> hitcounter_lambda_projectpoetry poly create project --name hello_lambda_project poetry poly create project --name hitcounter_lambda_project
Enter fullscreen mode Exit fullscreen mode
Similar to the cdk_app
, the projects/hello_lambda_project/pyproject.toml
should reference the corresponding hello_lambda
base:
<span>...</span><span>packages</span> <span>=</span> <span>[</span><span>{include</span> <span>=</span> <span>"cdk_workshop/hello_lambda"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../bases"</span><span>}</span><span>]</span><span>...</span><span>...</span> <span>packages</span> <span>=</span> <span>[</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/hello_lambda"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../bases"</span><span>}</span> <span>]</span> <span>...</span>... packages = [ {include = "cdk_workshop/hello_lambda", from = "../../bases"} ] ...
Enter fullscreen mode Exit fullscreen mode
And, the same for projects/hitcounter_lambda_project/pyproject.toml
for hitcounter_lambda
– including the dependency for boto3
:
<span>packages</span> <span>=</span> <span>[</span><span>{include</span> <span>=</span> <span>"cdk_workshop/hitcounter_lambda"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../bases"</span><span>}</span><span>]</span><span>[tool.poetry.dependencies]</span><span>python</span> <span>=</span> <span>"^3.10"</span><span>boto3</span> <span>=</span> <span>"^1.26.123"</span><span>packages</span> <span>=</span> <span>[</span> <span>{include</span> <span>=</span> <span>"cdk_workshop/hitcounter_lambda"</span><span>,</span> <span>from</span> <span>=</span> <span>"../../bases"</span><span>}</span> <span>]</span> <span>[tool.poetry.dependencies]</span> <span>python</span> <span>=</span> <span>"^3.10"</span> <span>boto3</span> <span>=</span> <span>"^1.26.123"</span>packages = [ {include = "cdk_workshop/hitcounter_lambda", from = "../../bases"} ] [tool.poetry.dependencies] python = "^3.10" boto3 = "^1.26.123"
Enter fullscreen mode Exit fullscreen mode
In the CdkWorkshopStack
file code we change the lambda function definition to:
<span>hello</span> <span>=</span> <span>_lambda</span><span>.</span><span>Function</span><span>(</span><span>self</span><span>,</span><span>"</span><span>HelloHandler</span><span>"</span><span>,</span><span>runtime</span><span>=</span><span>_lambda</span><span>.</span><span>Runtime</span><span>.</span><span>PYTHON_3_9</span><span>,</span><span>code</span><span>=</span><span>_lambda</span><span>.</span><span>Code</span><span>.</span><span>from_asset</span><span>(</span><span>"</span><span>lambda/hello</span><span>"</span><span>,</span><span>bundling</span><span>=</span><span>BundlingOptions</span><span>(</span><span>image</span><span>=</span><span>_lambda</span><span>.</span><span>Runtime</span><span>.</span><span>PYTHON_3_9</span><span>.</span><span>bundling_image</span><span>,</span><span>command</span><span>=</span><span>[</span><span>"</span><span>bash</span><span>"</span><span>,</span> <span>"</span><span>-c</span><span>"</span><span>,</span><span>"</span><span>pip install -r requirements.txt -t</span><span>"</span><span>"</span><span> /asset-output && cp -au . /asset-output</span><span>"</span><span>]</span><span>)</span><span>),</span><span>handler</span><span>=</span><span>"</span><span>cdk_workshop.hello_lambda.core.handler</span><span>"</span><span>,</span><span>)</span><span>hello</span> <span>=</span> <span>_lambda</span><span>.</span><span>Function</span><span>(</span> <span>self</span><span>,</span> <span>"</span><span>HelloHandler</span><span>"</span><span>,</span> <span>runtime</span><span>=</span><span>_lambda</span><span>.</span><span>Runtime</span><span>.</span><span>PYTHON_3_9</span><span>,</span> <span>code</span><span>=</span><span>_lambda</span><span>.</span><span>Code</span><span>.</span><span>from_asset</span><span>(</span> <span>"</span><span>lambda/hello</span><span>"</span><span>,</span> <span>bundling</span><span>=</span><span>BundlingOptions</span><span>(</span> <span>image</span><span>=</span><span>_lambda</span><span>.</span><span>Runtime</span><span>.</span><span>PYTHON_3_9</span><span>.</span><span>bundling_image</span><span>,</span> <span>command</span><span>=</span><span>[</span> <span>"</span><span>bash</span><span>"</span><span>,</span> <span>"</span><span>-c</span><span>"</span><span>,</span> <span>"</span><span>pip install -r requirements.txt -t</span><span>"</span> <span>"</span><span> /asset-output && cp -au . /asset-output</span><span>"</span> <span>]</span> <span>)</span> <span>),</span> <span>handler</span><span>=</span><span>"</span><span>cdk_workshop.hello_lambda.core.handler</span><span>"</span><span>,</span> <span>)</span>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", )
Enter fullscreen mode Exit fullscreen mode
Note the handler
declaration, like in cdk.json
file we are using the package fully qualified namespace to declare our handler. The _lambda.Runtime.PYTHON_3_9.bundling_image
property will build the lambda distribution using a requirements.txt
file that we will generate.
Let’s repeat the process for the hitcounter_lambda
. In components/cdk_workshop/hit_counter/core.py
we change:
<span>handler</span><span>=</span><span>"</span><span>cdk_workshop.hitcounter_lambda.core.handler</span><span>"</span><span>,</span><span>code</span><span>=</span><span>_lambda</span><span>.</span><span>Code</span><span>.</span><span>from_asset</span><span>(</span><span>"</span><span>lambda/hello</span><span>"</span><span>,</span><span>bundling</span><span>=</span><span>BundlingOptions</span><span>(</span><span>image</span><span>=</span><span>_lambda</span><span>.</span><span>Runtime</span><span>.</span><span>PYTHON_3_9</span><span>.</span><span>bundling_image</span><span>,</span><span>command</span><span>=</span><span>[</span><span>"</span><span>bash</span><span>"</span><span>,</span> <span>"</span><span>-c</span><span>"</span><span>,</span><span>"</span><span>pip install -r requirements.txt -t</span><span>"</span><span>"</span><span> /asset-output && cp -au . /asset-output</span><span>"</span><span>]</span><span>)</span><span>),</span><span>runtime</span><span>=</span><span>_lambda</span><span>.</span><span>Runtime</span><span>.</span><span>PYTHON_3_9</span><span>,</span><span>handler</span><span>=</span><span>"</span><span>cdk_workshop.hitcounter_lambda.core.handler</span><span>"</span><span>,</span> <span>code</span><span>=</span><span>_lambda</span><span>.</span><span>Code</span><span>.</span><span>from_asset</span><span>(</span> <span>"</span><span>lambda/hello</span><span>"</span><span>,</span> <span>bundling</span><span>=</span><span>BundlingOptions</span><span>(</span> <span>image</span><span>=</span><span>_lambda</span><span>.</span><span>Runtime</span><span>.</span><span>PYTHON_3_9</span><span>.</span><span>bundling_image</span><span>,</span> <span>command</span><span>=</span><span>[</span> <span>"</span><span>bash</span><span>"</span><span>,</span> <span>"</span><span>-c</span><span>"</span><span>,</span> <span>"</span><span>pip install -r requirements.txt -t</span><span>"</span> <span>"</span><span> /asset-output && cp -au . /asset-output</span><span>"</span> <span>]</span> <span>)</span> <span>),</span> <span>runtime</span><span>=</span><span>_lambda</span><span>.</span><span>Runtime</span><span>.</span><span>PYTHON_3_9</span><span>,</span>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,
Enter fullscreen mode Exit fullscreen mode
Add the required folders (assets folders) to the cdk_app
project.
mkdir -p mkdir -p projects/cdk_app/lambda/{hello,hitcounter}touch projects/cdk_app/lambda/{hello,hitcounter}/requirements.txtmkdir -p mkdir -p projects/cdk_app/lambda/{hello,hitcounter} touch projects/cdk_app/lambda/{hello,hitcounter}/requirements.txtmkdir -p mkdir -p projects/cdk_app/lambda/{hello,hitcounter} touch projects/cdk_app/lambda/{hello,hitcounter}/requirements.txt
Enter fullscreen mode Exit fullscreen mode
Alright, time for a checkpoint and commit our changes.
Ok, let’s try the deploy again. First, we build the lambda packages:
<span>cd </span>projects/hello_lambda_projectpoetry build-project<span>cd</span> ../hitcounter_lambda_project/poetry build-project<span>cd</span> ../../<span>cd </span>projects/hello_lambda_project poetry build-project <span>cd</span> ../hitcounter_lambda_project/ poetry build-project <span>cd</span> ../../cd projects/hello_lambda_project poetry build-project cd ../hitcounter_lambda_project/ poetry build-project cd ../../
Enter fullscreen mode Exit fullscreen mode
Our projects folder structure should look like this:
./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./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./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
Enter fullscreen mode Exit fullscreen mode
We will need to add the .whl
of the lambdas to the respective requirements.txt
files on the cdk_app
project:
<span>cd </span>projects/cdk_app/<span>cp</span> ../hello_lambda_project/dist/<span>*</span>.whl lambda/hello/<span>cp</span> ../hitcounter_lambda_project/dist/<span>*</span>.whl lambda/hitcounter/<span>cd </span>lambda/hello/<span>ls</span> <span>*</span> | find <span>-type</span> f <span>-name</span> <span>"*.whl"</span> <span>></span> requirements.txt<span>cd</span> ../hitcounter/<span>ls</span> <span>*</span> | find <span>-type</span> f <span>-name</span> <span>"*.whl"</span> <span>></span> requirements.txt<span>cd</span> ../../ <span># back to projects/cdk_app</span>poetry build-project <span># need to rebuild since we make changes</span><span>source</span> .venv/bin/activate<span># --force-reinstall is necessary unless we change the package version</span>pip <span>install</span> <span>--force-reinstall</span> dist/cdk_app-0.1.0-py3-none-any.whl<span>cd </span>projects/cdk_app/ <span>cp</span> ../hello_lambda_project/dist/<span>*</span>.whl lambda/hello/ <span>cp</span> ../hitcounter_lambda_project/dist/<span>*</span>.whl lambda/hitcounter/ <span>cd </span>lambda/hello/ <span>ls</span> <span>*</span> | find <span>-type</span> f <span>-name</span> <span>"*.whl"</span> <span>></span> requirements.txt <span>cd</span> ../hitcounter/ <span>ls</span> <span>*</span> | find <span>-type</span> f <span>-name</span> <span>"*.whl"</span> <span>></span> requirements.txt <span>cd</span> ../../ <span># back to projects/cdk_app</span> poetry build-project <span># need to rebuild since we make changes</span> <span>source</span> .venv/bin/activate <span># --force-reinstall is necessary unless we change the package version</span> pip <span>install</span> <span>--force-reinstall</span> dist/cdk_app-0.1.0-py3-none-any.whlcd 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
Enter fullscreen mode Exit fullscreen mode
note:
Runtime.PYTHON_3_9.bundling_image
will fail if any of the packages need a greater version of python.
Now we can deploy again:
<span># from the projects/cdk_app/ with the python virtual env active</span>cdk deploy<span># from the projects/cdk_app/ with the python virtual env active</span> cdk deploy# from the projects/cdk_app/ with the python virtual env active cdk deploy
Enter fullscreen mode Exit fullscreen mode
It is important to note that most of this process is probably part of the DevOps setup, and rarely you will have to do any of this manually. But hey! it is better to know where things come from and be able to fix it than waiting on somebody else to fix it for you.
IMPORTANT, the lambdas will fail complaining that they can not find the handler module event if it is included correctly in the lambda package code. For this to work you’ll need Runtime.PYTHON_3_9
at least
Let’s add the last checkpoint here and commit our changes.
Final considerations
- This monorepo methodology makes it easy to start a new project or change an existing one.
- All your repositories will look consistent with the same structure and elements.
- With all the code in the same repository you can detect if something could potentially break other parts of the system even if they are deployed separately.
- Last but not least, there is a clear separation between the code and the deploy artifacts
I hope this article help you improve your coding skills, make your projects more organized and professional, and save you some time in the future.
Go do something fun with that extra time.
Until the next post,
Take care and happy coding.
Acknowledgments
Thanks to Robmay S. Garcia for the review, corrections and help.
Thanks David Vujic for this excellent tool.
暂无评论内容