Showing posts with label Developer Experience. Show all posts
Showing posts with label Developer Experience. Show all posts

Friday, February 7, 2025

FOSDEM 25

FOSDEM, a Conference different from any other I've been to before. I'm very happy for the opportunity to talk, share knowledge and chat with the fellow devs there. I also met old and new friends in Brussels, and learned a little bit more about the nice Belgian beer culture. 😀

The FOSDEM event is free and you don't even register to attend (only speakers do that in a call for speakers process). Just come by the University Campus, located not far from the center of the Inner City.

I travelled towards Belgium with the Night Train from Stockholm, Sweden, and arrived the next morning in Hamburg, Germany. From there, I took the DB ICE Train to Köln/Cologne. At some point, the top speed was about 250 km/h. That's fast! From there, another fast train to Brussels.

The variety of topics and the amount of tracks at FOSDEM was mind blowing, with thousands of participants. The overall vibe was very friendly & laid back. I joined Rust focused talks, JavaScript and UX/Design talks. And, of course, the Python room talks.

My talk was in the Python room and was about Python Monorepos and the Polylith Developer Experience. Everything Python related was on FOSDEM Day 2 and I think my presentation went really well! There was a lot of questions afterwards and I got great feedback from the people attending.

The next day I went back the same route as before. I got a couple extra of hours to spend in Hamburg before onboarding the Night Train back home to Stockholm. A great Weekend Trip!

Here's the recording of my talk from FOSDEM 25:

The video was downloaded from fosdem.org.
Licensed under the Creative Commons Attribution 2.0 Belgium Licence.


Resources

Friday, January 3, 2025

Better Python Developer Productivity with RDD

"REPL Driven Development is an interactive development experience with fast feedback loops”

I have written about REPL Driven Development (RDD) before, and I use it in my daily workflow. You will find setups for Python development in Emacs, VS Code and PyCharm in this post from 2022. That's the setup I have used since then.

But there's one particular thing that I missed ever since I begun with RDD in Python. I learned this interactive REPL kind of writing code when developing services and apps with Clojure. When you evaluate code in Clojure - such as function calls or vars - the result of the evaluation nicely pops up as an overlay in your code editor, right next to the actual code. This is great, because that's also where the eyes are and what the brain is currently focused on.

The setup from my previous post will output the evaluated result in a separate window (or buffer, as it is called in Emacs). Still within the code editor, but in a separate view. That works well and has improved my Python Developing Productivity a lot, but I don't like the context switching. Can this Python workflow be improved?

Can I improve this somehow?

I've had that question in the back of my mind for quite some time. My Elisp skills are unfortunately not that great, and I think that has been a blocker for me. I've managed to write my own Emacs config, but that's about it. Until now. During the Christmas Holidays I decided to try learning some Emacs Lisp, to be able to develop something that reminds of the great experience from Clojure development.

I used an LLM find out how to do this, and along the way learned more about what's in the language. I'm not a heavy LLM/GPT/AI user at all. In fact, I rarely use it. But here it made sense to me, and I have used it to learn how to write and understand code in this particular language and environment.

I have done rewrites and a lot of refactoring of the result on my own (my future self will thank me). The code is refactored from a few, quite large and nested Lisp functions into several smaller and logically separated ones. Using an LLM to just get stuff done and move on without learning would be depressing. The same goes for copy-pasting code from StackOverflow, without reflecting on what the code actually does. Don't do that.

Ok, back to the RDD improvements. I can now do this, with new feature that is added to my code editor:

Selecting a variable, and inspecting what it contains by evaluating and displaying the result in an overlay.
Selecting a function, execute it and evaluate the result.

The overlay content is syntax highlighted as Python, and will be rendered into several rows when it's a lot of data.

The actual code evaluation is still performed in the in-editor IPython shell. But the result is extracted from the shell output, formatted and rendered as an overlay. I've chosen to also truncate the result if it's too large. The full result will still be printed in the Python shell anyway.
The Emacs Lisp code does this in steps:

  1. Adding a hook for a specific command (it's the elpy-shell-send-buffer-or-region command). The Emacs shortcut is C-c C-c.
  2. Capture the contents of the Python shell.
  3. Create an overlay with the evaluated result, based on the current cursor position.
  4. Remove the overlay when the cursor moves.

This is very much adapted to my current configuration, and I guess the real world testing of this new addition will be done after the holidays, starting next week. So far, so good!

Future improvements?

I'm currently looking into the possibilities of starting an external IPython or Jupyter/kernel session, and how to connect to it from within the code editor. I think that could enable even more REPL Driven Development productivity improvements.

You'll find the Emacs Lisp code at GitHub, in this repo, where I store my current Emacs setup.

Top Photo by Nicolai Berntsen on Unsplash

Tuesday, August 27, 2024

Simple Kubernetes in Python Monorepos

"Kubernetes, also known as K8s, is an open source system for automating deployment, scaling, and management of containerized applications."
(from kubernetes.io)

Setting up Kubenetes for a set of Microservices can be overwhelming at first sight.

I'm currently learning about K8s and the Ecosystem of tooling around it. One thing that I've found difficult is the actual K8s configuration and how the different parts relate to each other. The YAML syntax is readable, but I also find it hard to understand how to structure it - impossible to edit without having the documentation close at hand. When I first got the opportunity to work with Python code running in Kubernetes, I realized that I have to put extra effort in understanding what's going on in there.

I was a bit overwhelmed by what looked like a lot of repetitive and duplicated configuration. I don't like that. But there's a tool called Kustomize that can solve this, by incrementally constructing configuration objects with snippets of YAML.

Kustomize is about managing Kubernetes objects in a declarative way, by transforming a basic setup with environment-specific transformations. You can replace, merge or add parts of the configuration that is specific for the current environment. It reminds me of how we write reusable Python code in general. The latest version of the Kubernetes CLI - Kubectl - already includes Kustomize. It used to be a separate install.

Microservices

From my experience, the most common way of developing Microservices is to isolate the source code of each service in a separate git repository. Sometimes, shared code is extracted into libraries and put in separate repos. This way of working comes with tradeoffs. With the source code spread out in several repositories, there's a risk of having duplicated source code. Did I mention I don't like duplicated code?

Over time, it is likely that the services will run different versions of tools and dependencies, potentially also different Python versions. From a maintainability and code quality perspective, this can be a challenge.

YAML Duplication

In addition to having the Python code spread out in many repos, a common setup for Kubernetes is to do the same thing: having the service-specific configuration in the same repo as the service source code. I think it makes a lot of sense to have the K8s configuration close to the source code. But with the K8s configuration in separate repos, the tradeoffs are very much the same as for the Python source code.

For the YAML part in specific, it is even likely that the configuration will be duplicated many times across the repos. A lot of boilerplate configuration. This can lead to unnecessary extra work when needing to update something that affects many Microservices.

One solution to the tradeoffs with the source code and the Kubernetes configuration is: Monorepos.

K8s configuration in a Monorepo

A Monorepo is a repository containing source code and multiple deployable artifacts (or projects), i.e. a place where you would have all your Python code and where you would build & package several Microservices from. The purpose of a Monorepo is to simplify code reuse, and to use the same developer tooling setup for all code.

The Polylith Architecture is designed for this kind of workflow (I am the maintainer of the Python tools for the Polylith Architecture).

While learning, struggling and trying out K8s, I wanted to find ways to improve the Configuration Experience by applying the good ideas from the Developer Experience of Polylith. The goal is to make K8s configuration simple and joyful!

Local Development

You can try things out locally with developer tools like Minikube. With Minikube, you will have a local Kubernetes to experiment with, test configurations and to run your containerized microservices. It is possible to dry-run the commands or apply the setup into a local cluster, by using the K8s CLI with the Kustomize configs.

I have added examples of a reusable K8s configuration in the The Python Polylith Example repo. This Monorepo contains different types of projects, such as APIs and event handlers.

The K8s configuration is structured in three sections:

  • A basic setup for all types of deployments, i.e. the config that is common for all services.
  • Service-type specific setup (API and event handler specific)
  • Project-specific setup

All of theses sections have overlays for different environments (such as development, staging and production).

As an alternative, the project-specific configuration could also be placed in the top /kubernets folder.

I can run kubectl apply -k to deploy a project into the Minikube cluster, using the Kustomize configuration. Each section adds things to the configuration that is specific for the actual environment, the service type and the project.

The base, overlays and services are the parts that aren't aware of the project. Those Project-specific things are defined in the project section.

Using a structure like this will make the Kubernetes configuration reusable and with almost no duplications. Changing any common configuration only needs to be done in one place, just as with the Python code - the bricks - in a Polylith Monorepo.

That’s all. I hope you will find the ideas and examples in this post useful.


Resources


Top photo by Justus Menke on Unsplash

Sunday, February 18, 2024

Python Monorepo Visualization

What's in a code repository? Usually you'll find the source code, some configuration and the deployment infrastructure - basically the things needed to develop & deploy something. It might be a service, an app or a library. A Monorepo contains the same things, but for more than one artifact. In there, you will find the code, configurations and infrastructure for several services, apps or libraries.

The main use case for a Monorepo is to share code and configuration between the artifacts (let's call them projects).

These things have to be simple

Sharing code can be difficult. Repos can be out of date. A Monorepo can be overwhelming. With or without a Monorepo, the most common way of sharing code is to package them as libraries that the projects can add as external dependencies. But managing different versions and keeping the projects up-to-date could lead to unexpected and unwanted extra work. Some Monorepos solve this by using symlinks to share code, or custom scripts for copying things into the individual projects during deployment.

Doing that can be messy, I've seen it myself. I was once part of a team that migrated away from a horrible Monorepo, into several smaller single-repo microservices. The tradeoffs: source code spread out in repos with an almost identical structure. Almost is the key here. Also, code and config duplications.

These tradeoffs have a negative impact on the Developer Experience.

The Polylith Architecture has a different take on organizing and sharing code, with a nice developer experience. These things have to be simple. Writing code should be fun. Polylith is Open Source, by the way.

The most basic type of visualization

In a Polylith workspace, the source code lives in two folders named bases and components. The entry points are put in the bases folder, all other code in the components folder. At first look, this might seem very different from a mainstream Python single-project structure. But it isn't really that different. Many Python projects are using a src layout, or have a root folder with the same name as the app itself. At the top, there's probably an entry point named something like app.py or maybe main.py? In Polylith, that one would be put in the bases folder. The rest of the code would be placed in the components folder.

  components/
     .../
       auth
       db
       kafka
       logging
       reporting
       ...
  

You are encouraged to keep the components folder simple, and rather put logically grouped modules (i.e. namespace packages) in separate components than nested structures. This will make code sharing more straightforward than having a folder structure with packages and sub-packages. It is also less risk of code duplication with this kind of structure, because the code isn't hidden in a complex folder structure. As a side effect, you will have a nice overview over the available features: the names of the folders will tell what they do and what's available for reuse. A folder view like this is surprisingly useful.

Visualize with the poly tool

Yes, looking at a folder structure is useful, but you would need to navigate the actual source code to figure out where it is used and which dependencies that are used in there. Along with the Polylith Architecture there is tooling support. For Python, you can use the tooling together with Poetry, Hatch, PDM or Rye.

The poly info command, an overview of code and projects in the Monorepo.

Besides providing commands for creating bases, components and projects there are useful visualization features. The most straightforward visualization is probably poly info. Here, you will get an overview of all the bricks (the logically grouped Python modules, living in the bases and components folders), the different projects in the Workspace and also in which projects the bricks are added.

Third-party libraries & usages

There's a command called poly libs that will display the third-party dependencies that are used in the Workspace (yes, that's what the contents of the Monorepo is called in Polylith). It will display libraries and the usages on a brick-level. In Polylith, a brick is the thing that you share across projects. Bricks are the building blocks of this architecture.

The poly libs command, displaying the third-party dependencies and where they are used.

The building blocks and how they depend on each other

A new thing in the Python tooling is the command called poly deps. It displays the bricks and how they depend on each other. You can choose to display an overview of the entire Workspace, or for an individual project. This kind of view can be helpful when reasoning about code and how to combine bricks into features. Or inspire a team to simplify things and refactor: should we extract code from this brick into a new one here maybe?

A closer look at the bricks used in a project with poly deps.

You can inspect a single brick to visualize the dependencies: where it is used, and what other bricks it uses.

A zoomed-in view, to inspect the usages of a specific brick.

Export the visualizations

The output from these commands is very easy to copy-and-paste into Documentation, a Pull Request or even Slack messages.

poly deps | pbcopy

📚 Docs, examples and videos

Have a look at the the Polylith documentation for more information about getting started. You will also find examples, articles and videos there for a quick start.



Top image made with AI (DALL-E) and manual additions by a Human (me)

Friday, July 21, 2023

Python FastAPI Microservices with Polylith

I like FastAPI. It's is a good choice for developing Python APIs. FastAPI has a modern feel to it, and is also easy to learn. When I think about it, I could say the same about Polylith.

Modern Python Microservices

Building Python services with FastAPI and Polylith is a pretty good combination. With FastAPI, you'll have a modern framework including tools like Pydantic. The simplicity of the Polylith Architecture - using bases & components - helps us build simple, maintainable, testable, and scalable services.

You will have a developer experience without the common Microservice struggles, such as code duplication or maintaining several different (possibly diverging) repositories.

Getting started

Okay, let's build something. I will write a simple CRUD service that will handle messages. Since I'm starting from a clean sheet, I can choose where to begin the actual coding. I'd like to start with writing a function in a message module, and will figure out the solution while coding. The Polylith tooling support (totally optional) will help me add new code according to the structure of architecture. I already have a Workspace prepared, have a look at the docs for how to set up a Polylith Workspace. Full example at: python-polylith-example

Great! Now I have a namespace package that is ready for coding. I'll continue with writing a create function. While working on it, I realize I need a persistent storage. I decide to try out SQLAlchemy and the Session thing.

def create(content: str) -> int:
    with Session.begin() as session:
        data = crud.create(session, content)
        return data.id

Again, I will use the poly tool to create a database component, where I will put all the SQLAlchemy setup along with a model and the function where data is added to the DB.

def create(session: Session, content: str) -> Message:
    data = Message(content=content)

    session.add(data)
    session.flush()

    return data

In this example, I will go for SQLite to keep things simple. Use your favorite data storage here.

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

DATABASE_URL = "sqlite:///./sql_app.db"
DATABASE_ARGS = {"check_same_thread": False}

engine = create_engine(DATABASE_URL, connect_args=DATABASE_ARGS)
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

I now have the basics done and am ready to write the endpoint.

I could (of course) also have started from the endpoint, if I wanted to. Polylith allows you to postpone design decisions until you are ready for it.

Before continuing, I will add the components to the packages section of the workspace pyproject.toml:

packages = [
    {include = "my_namespace/database", from = "components"},
    {include = "my_namespace/message", from = "components"},
]

The Base Plate

API endpoints are recommended to have in what is called a base in Polylith. The idea is very much like LEGO, when building things with bricks and place them on a base plate. Yes, the FastAPI endpoint is the base plate. I will create a new base with the poly tool, and add the FastAPI code in there. It will import the message component I wrote before, and consume the create function.

@app.post("/message/", response_model=int)
def create(content: str = Body()):
    return message.create(content)

Just as with the components, I will add the base to the packages section of the pyproject.toml:

{include = "my_namespace/message_api", from = "bases"},

I can now try out the API in the browser (I have already added the FastAPI dependency). Developing & trying out code is normally done from the development area (i.e. the root of the workspace). This is where you have all the things set up, all code and all dependencies in one single virtual environment. This is a really good thing for a nice Developer Experience.

Reusing existing code

I realize that I need something that will log what is happening in the service. Luckily, there's already a log component there that was added before, for a different service. I'll go ahead and import the logger into my endpoint and start logging.

✨ This is where Polylith really shines. ✨ The components that you develop are ready to be re-used already from start. There's no need to extract code into libraries or doing something additional. All previosly written components are already there, and you just add them to your project. You might think that the logger is a silly little thing? Yes, it is. But this is only a simple example of code that is shared between projects in a Polylith Workspace.

The Project

There's one important part missing, and that is the project. A project is the artifact that will be deployed. It is a package of all project-specific code, along with project-specific configuration. Using the poly tool and adding the base to the project-specific pyproject.toml. Note the relative path to the bases folder.

packages = [
    {include = "my_namespace/message_api", from = "../../bases"},
]

I'm feeling lazy and will use the poly tool to add the other needed bricks for this specific project (components and bases are often referred to as bricks). The poly tool will analyze the imports and add only the ones needed for this project when running the poetry poly sync command. There's also a check command available that will report any missing bricks.

You still need to add the third party dependencies, though. There's limits to the magic of the poly tool 🪄 🧙. To keep track on what is needed in the bricks, you can actually run poetry poly check again. It will notify about the usage of SQLAlchemy in the bricks. You can use the poly check command during development to guide you in the dependencies actually needed for this specific project.

Add sqlalchemy to the [tool.poetry.dependencies] section manually. You can update the lock-file for the project by running the builtin Poetry command:

poetry lock --directory projects/message_fastapi_project

All project-specific dependencies should now be set up properly! When packaging the project, simply run the poetry build-project (with --directory projects/my_message_fastapi_project if you run the command from the workspace root). You can use the built wheel to deploy the FastAPI service. It contains all the needed code, packaged and re-arranged without any relative paths.

What's in my Workspace?

Check the state of the workspace with poetry poly info command. You will get a nice overview of the added projects, bricks and the usage of bricks in each project.

In this article, I have written about the usage of Polylith when developing services. Adding new services is a simple thing when working in a Polylith workspace, and the tooling is there for a nice Developer Experience. Even if it sometimes might feel like a superpower, it's basically only about keeping things simple. Don't hesitate to reach out if you have feedback or questions.

Additional info

Full example at: python-polylith-example
Docs about Polylith: Python tools for the Polylith Architecture

Top photo by Yulia Matvienko on Unsplash

Sunday, April 30, 2023

Dad Jokes & Python Developer Experience

"What do you call a Frenchman wearing sandals?"

"Philippe Flop." 🥁 👴 😆
Developer Experience, what does that even mean?

For me, a huge part is to quickly being able to write, try out and experiment with code. In Python, that could be about having the dependencies and paths already set up, to easily run snippets in a REPL or in the IDE, and without it crashing because of some missing dependencies or configs.

Testable code

When I develop a new feature, I usually want to run & evaluate the code very early in the process. Maybe I'm working on parsing something, transforming or filtering out data: that's when I want to evaluate the code, while writing and figuring out how to solve the actual problem. I do this to verify the output and get ideas about how to refactor the code. I practice a thing called REPL Driven Development, and have written about it before. The workflow is a form of TDD and the refactoring part comes natural with this kind of workflow.

Reusable & Composable code

Another important thing for me is to have already existing code nearby and available to use again. I usually think of Python code as building blocks, like LEGO, and try to have that in mind when writing new functions and adding features. I have learned that from functional programming and it is helping me to solve problems using a step-by-step approach.

With building blocks, code is composable and is ready for re-use from start.

"What did Yoda say when he saw himself in 4K?"

"HDMI." 🥁 👴 😆

Delay Design Decisions

I don't think it is that important to put code in the right place from start (such as what kind of service or app, domain, repo or a folder). At least it isn't as important as writing code that can easily be moved from one place to another. Very related to the building blocks & LEGO way, mentioned above. This approach will likely lead to less wasted time & less design-upfront planning. It is easier to figure it out the proper place to put the code eventually, while iterating and developing. In my opinion, it is perfectly alright to rename or move around code while developing. It shouldn't be a difficult thing to do that.

I usually like to dive into a feature and start coding, rather than starting off by deciding name of a repo, or what type of service the feature should exist in. These things can be left to decide later, when learning more about what to actually build.

"Where do dads store their dad jokes?"

"In the Dadabase." 🥁 👴 😆

A Developer Friendly Architecture

The Polylith Architecture targets all of these things. During the development you have all code available for experimentation and you compose already existing code with new when building features. All code in a Polylith workspace is referenced as bricks, and you use them just as when building something with LEGO. Pick the ones you need along with writing new ones, and combine them into features. A brick can be used in several apps, services or projects as it is called in Polylith.

During development, you have one single virtual environment, with all paths and dependencies already set up. You also have a separate section especially made for trying out and experimenting. It is very similar to the scratch files in PyCharm, but they are versioned and included in the repo. These ones are very useful for evaluating code.

You can immediately dive in to writing code and push the decision about deployment into the future if you like. Add the project to the Polylith workspace, using a simple command, when you are ready for it.

A Dad Joke microservice

To get some insights in how Polylith and the Python tooling changes the way you develop and improves the Developer Experience, I have recorded an improvised video with live coding. Phew, it is difficult to speak & code at the same time. 😅

In this video, I'm taking the first steps into developing some kind of Dad Joke Service and use the Development Environment of Polylith to figure out how to build it.



Top photo by Toa Heftiba on Unsplash