Introducing the “Dev Re-Dive Series”
In the middle of my term as a full-time software developer at Indigo Ag, our organization executed a complete rewrite of the architecture that powered most of our cloud web applications. Faced with a brand-new technology stack, I made a conscious choice to transform my learning process into a series of internal blog posts. My intentions were twofold. One, I wanted to make sure I both understood and retained the knowledge I was acquiring. Second, I wanted a way to efficiently share my newfound knowledge with other developers struggling to comprehend this new architecture.
The series is one of the proudest accomplishments of my career thus far. Each post got plenty of likes on our wiki, and I received quite a few Slack messages from team members who found them useful. I believe a key element to the success of those posts was my insistence on exploring the “why” behind each new technology, in addition to the “what” and “how.” Much of the information I gained was generally applicable knowledge outside of our particular architecture.
I think those posts deserve to be re-published on the open internet. Of course, I no longer have access to the content on the company’s intranet, nor would I want to disclose Indigo’s proprietary information. Luckily, I have enough mental memory and written notes to re-write the series from the ground up for a more general developer audience. I’m going to work my way through each of the concepts I explored, perhaps diving even deeper into the history and context than I had time to do when I first wrote them.
Each post follows a general structure.
- We start by examining a modern software development library, package, or framework. Usually, we discover that the technology is built on top of other, older technology and concepts.
- Next, we do a bit of historical research that helps understand the terms and concepts we’ve encountered.
- We dive as deep as we can until things start to make sense, then trace our steps in reverse to put the pieces together.
Case study #1: FastAPI
Let’s take a look at FastAPI . According to the home page for the project:
FastAPI is a modern, fast (high-performance), web framework for building APIs with Python 3.8+ based on standard Python type hints.
That is a fairly helpful high-level description. FastAPI is a web framework, which usually means “a big pile of useful tools that makes it easier to build stuff for the web.” In this case, the thing we’d be building is an application programming interface (API), which provides a structured language that other internet-connected software can use to access whatever we put behind the interface.
What this description doesn’t tell us is anything that might hint at what underlying technology powers FastAPI. For that we need to visit the “Features” page, where we find this text:
FastAPI is fully compatible with (and based on) Starlette. So, any additional Starlette code you have, will also work.
FastAPI is actually a sub-class of Starlette. So, if you already know or use Starlette, most of the functionality will work the same way.
Helpful! Well, it would be if we knew what Starlette is. Let’s jump over to the home page for Starlette and read the description:
Starlette is a lightweight ASGI framework/toolkit, which is ideal for building async web services in Python.
ASGI? What on earth is that? Into the rabbit hole we go!
Open the gate(way)!
Consider a web server at its most basic level. A software server is an operating system process running on some internet-connected hardware, listening for requests on a port or other socket interface. When the server receives a request from a client, it executes code that analyzes request and determines the appropriate response data to send back to the client. A simple server implementation might contain all of the code needed to parse requests and generate responses. However, this requires the programmer to edit or clone the server source code any time they want to handle requests for a new kind of resource.
If the server could delegate some of this application-specific logic to other processes, we could simplify the server code itself and more easily share it with others. This is the principle of “separation of concerns” in action!
CGI is a protocol specification first introduced in 1993. The spec defined a standard procedure servers could use to delegate requests to other programs. It’s worth reading the “Purpose” section of RFC 3875 . I’ve paraphrased some key excepts below:
…The Common Gateway Interface (CGI) allows an HTTP server and a CGI script to share responsibility for responding to client requests.
…The server is responsible for managing connection, data transfer, transport and network issues related to the client request, whereas the CGI script handles the application issues, such as data access and document processing.
The RFC includes sections that describe the server’s responsibilities, how to select and invoke a script, and the mechanism and content of the messages exchanged between the server and script. Some of the benefits of this prescriptive approach are:
- The ability to implement a web server without the complexity of application-specific logic.
- The opportunity to share CGI-compliant server implementations among many end users, each of whom can plug in their own CGI scripts.
CGI was a big deal, but it came with some critical limitations with regards to performance. In 1996, the FastCGI specification extended the CGI protocol to support long-running applications that could serve multiple requests and take advantage of multithreaded computation.
Fast foward seven year to 2003. Python had taken the programming world by storm. This resulted in a proliferation of Python-driven web application frameworks, each of which was compatible with a specific subset of web servers. Though these frameworks could all be connected via FastCGI, each diverged enough in the details to cause problems. Seeing the potential for Python to become an even bigger platform for web application development, the maintainers developed a standard programming interface that they called the Python Web Server Gateway Interface, or WSGI. The original PEP document is PEP 3333 , although it was later revised for python3 as . Both documents contain the rationale behind the creation of WSGI:
…The availability and widespread use of such an API in web servers for Python - whether those servers are written in Python (e.g. Medusa), embed Python (e.g. mod_python), or invoke Python via a gateway protocol (e.g. CGI, FastCGI, etc.) - would separate choice of framework from choice of web server, freeing users to choose a pairing that suits them, while freeing framework and server developers to focus on their preferred area of specialization.
The WSGI spec contains the prescriptive interface between both Python web servers and Python web applications. The WSGI server passes a function called start_response
to the web application, along with an argument environ
containing request parameters. The WSGI app reads the parameters, generates a response, and invokes the start_response
function with the response status and body as arguments.
As they did with CGI, programmers quickly ran into the limits of the WSGI specification. Specifically, they were struggling to adopt WSGI in the context of asynchronous protocols such as Websockets, which do not fit as nicely into the synchronous “per-request” model that WSGI prescribes. Meanwhile, PEP 492 codified asynchronous programming capabilities within Python via async/await
syntax. The ability for web apps to concurrently process multiple requests was too powerful to ignore, so a new protocol was born: ASGI. The ASGI specification
built off of WSGI but changed the model such that applications are responding to “events” from the server in an asynchronous event loop.
Starlette and Uvicorn: The fantastical world of modern python web development
There are plenty of ASGI servers and web application frameworks to choose from. Let’s take a look at what FastAPI uses.
We’ve already met Starlette, the ASGI web application framework. Starlette is essentially a big box of tools that make it really easy to build a Python web application capable of receiving events from an ASGI server, processing them asynchronously, and returning the response in a format that the ASGI server understands.
FastAPI bundles Uvicorn as its ASGI web server. Uvicorn is a lightweight webserver that can listen for incoming HTTP requests and delegate them to Python web applications via ASGI events.
FastAPI uses these two core technologies as a base, then adds a bundle of extra features specific for building a RESTful web APIs, such as automatic generation of documentation for your API.
Putting it all together…
Let’s review what we’ve learned:
- A gateway protocol enables a web server to delegate application-specific request processing logic to scripts or processes via a shared language specification.
- ASGI is a gateway protocol that enables Python web applications to asynchronously process requests for Python web servers.
- Starlette is a framework that makes it easy to build ASGI-compatible Python web applications.
- Uvicorn is a lightweight Python web server capable of forwarding web requests to ASGI applications for processing.
- FastAPI is a web framework that bundles Starlette and Uvicorn together with a comprehensive set of tools for building REST APIs.
In the next post of this series, we’ll peel back the curtain and learn how our applications can communicate effectively with a relational database.