A few months ago, I decided to challenge myself to transition from a data analytics background to data engineering. So far, I've built two impactful real-world projects that taught me something genuinely useful: a GitHub ETL pipeline that extracts repositories and loads them into a SQLite database (scheduled via GitHub Actions), and an RSS pipeline that ingests articles and stores them in a Kestra database (orchestrated hourly).
Having grasped the fundamentals of ETL, I wanted to try something new—while practicing what I'd already learned. I've always been fascinated by machine learning, but I'd held back, assuming it required complex math. Not anymore.
Recently, I built a churn prediction model for a fictional telecom company I call 'Northline Mobile' (I prefer fictional scenarios because they help me understand concepts through real-world applications). The model was trained on data from 7,043 customers, including contract type (1-year vs. month-to-month), tenure, monthly charges, add-ons, and whether each customer ultimately churned. I cross-validated it on unseen customer records.
The model achieved 81% accuracy, and building it taught me the end-to-end process of model development. Admittedly, I didn't understand every complex line of code—I lean toward intuitive, drag-and-drop interfaces—but I grasped the core building blocks, which I'll explain with a simplified architecture below.
So, from a machine learning perspective, this felt like a win: my model worked.
But there was one glaring problem: it wasn't actually useful.
Imagine a Northline employee needing a prediction. They'd have to ask me to manually open Jupyter, load the right notebook, run cells in the correct order, and call predict_churn() themselves. The model existed, but I was the only one who knew how to use it. No one else at Northline could simply send customer data and get a prediction back, and the model couldn't communicate with any other application.
This article addresses that gap.
Recently, I've come to understand the difference between having a model and having a service.
A model sitting in a notebook is accessible only to its creator. But turning it into a service makes it available to everyone—other teams, applications, dashboards, and systems—without them needing to know or care how predictions are generated.
It turns out that building the machine learning model was the easiest part; making it useful is another crucial element of the puzzle. In the following sections, I'll walk through how I turned my churn classifier into a FastAPI service that other software can actually call.
From Notebook to Service
To move from a static model to a deployable service, I needed to address several key steps: packaging the model, exposing it via an API, and ensuring it could handle incoming requests reliably.
Step 1: Exporting the Model
The first step was to save my trained model (e.g., using joblib or pickle) so it could be loaded outside the notebook environment. I also needed to export any preprocessing artifacts (like encoders or scalers) to ensure consistency between training and inference.
Step 2: Building a FastAPI Application
I created a simple FastAPI app with two endpoints:
- GET /health: To check if the service is alive.
- POST /predict: To accept customer data (as JSON), preprocess it, run the model, and return the churn prediction along with probability.
FastAPI automatically generates interactive documentation at /docs, making it easy for other teams to explore and test the API.
Step 3: Running Locally with Uvicorn
Using uvicorn, I started the service locally and confirmed it responded correctly to test requests. This validated the full pipeline—from HTTP request to model inference and back.
Step 4: Dockerizing for Deployment
To make the service portable and easy to deploy anywhere (cloud, on-prem, or Kubernetes), I containerized it with Docker. The Dockerfile included installing dependencies and copying the necessary model files.
Lessons Learned
This process taught me several practical lessons:
- Model and service are distinct: The model itself is just an artifact; the service is what makes it actionable.
- API design matters: Clear request/response schemas and error messages are essential for adoption.
- Deployment is a skill: Understanding Docker, FastAPI, and API patterns bridges the gap between data science and real-world operations.
What's Next?
With a working FastAPI service, I can now extend it further—adding authentication, logging, or integrating with a front-end dashboard. For example, Northline Mobile could embed this API into their customer management system to flag at-risk customers in real time.
In 2026, the MLOps landscape has matured significantly, with tools like MLflow and Kserve offering standard serving solutions. But building a simple FastAPI wrapper remains an essential, foundational skill—it gives you full control and a deep understanding of the serving layer.
Ultimately, this journey reinforced that building a model is only half the battle; deploying it as a usable service is where real value is created. If you're in a similar position, I encourage you to take that extra step—your model deserves to be used.
