Hands-on repository for building a modern, cloud-native data engineering workflow end-to-end: ingestion → messaging → validation → storage → warehouse → transformations → data quality → BI.
The labs progressively evolve a pipeline from local tooling fundamentals (Git/Docker/Kafka) to a production-style, event-driven architecture on Google Cloud Platform (GCP), ending with analytics modeling (dbt), data quality checks (Great Expectations), and dashboarding (Looker Studio).
- Lab 0: Git/GitHub workflow + Docker basics + Apache Kafka fundamentals (local stack)
- Lab 1: Serverless ingestion with Pub/Sub + Cloud Run (producer job → consumer service)
- Lab 2: Production-grade pipeline: Schema Registry (AVRO) + Dead Letter Topic + GCS (JSON/Parquet) + BigQuery + CI/CD
- Lab 3: Analytics & BI layer: dbt medallion architecture + SQL windowing/aggregations + Great Expectations + Looker Studio
┌───────────────┐ ┌──────────────┐ ┌────────────────────┐
│ External API │ ---> │ Pub/Sub │ ---> │ Cloud Run Service │
│ (Reddit/SO) │ │ (AVRO+DLQ) │ │ consumer (push) │
└───────────────┘ └──────┬───────┘ └─────────┬──────────┘
│ │
│ invalid │ valid
v v
┌─────────────┐ ┌───────────────────┐
│ DLQ topic │ │ GCS raw JSON │
└─────────────┘ └───────────────────┘
│
v
┌───────────────────┐
│ GCS processed │
│ Parquet partitioned│
│ (year/month/day/hr)│
└─────────┬─────────┘
v
┌───────────────────┐
│ BigQuery (DWH) │
│ load jobs + tables│
└─────────┬─────────┘
v
┌───────────────────┐
│ dbt bronze/silver/│
│ gold models │
└─────────┬─────────┘
v
┌───────────────────┐
│ Great Expectations │
│ checkpoints/reports│
└─────────┬─────────┘
v
┌───────────────────┐
│ Looker Studio BI │
│ dashboard │
└───────────────────┘
- GCP: Pub/Sub, Cloud Run (Jobs + Services), Cloud Storage (GCS), BigQuery
- Messaging patterns: push subscriptions, schema validation, dead-letter handling
- Formats: AVRO (message encoding/validation), JSON (raw), Parquet (columnar analytics)
- Analytics: dbt (SQL models, lineage/DAG, tests), BigQuery window functions & aggregations
- Data quality: Great Expectations (expectation suites + checkpoints + HTML reports)
- DevOps: Docker, GitHub Actions CI/CD, branch protection rules
- Local tooling: Python,
uv,.editorconfig, linting (pylint), editorconfig-checker
Do NOT commit secrets or credentials:
.env, service account keys likegcp-sa-key.json, API tokens, etc.
Recommended:
- use GCP Secret Manager or GitHub Actions secrets for CI/CD deployments
- ensure sensitive files are in
.gitignore
# examples to keep local-only:
.env
gcp-sa-key.json
.venv/
.idea/
__pycache__/
.ipynb_checkpoints/
- Docker + Docker Compose
- Python (and
uvif you use the lockfile workflow) - Google Cloud CLI (
gcloud) and access to a GCP project - BigQuery enabled in the project
If you use uv:
uv syncuv run python --version- Practiced core Git workflows: branching, merging, pull requests, and conflict resolution.
- Learned Docker basics: images vs containers, Dockerfiles, docker-compose.
- Ran a local Kafka stack (single broker + Zookeeper) and worked with producer/consumer concepts.
From the lab folder (example stack name may differ):
docker compose up -dThen run the demo app (example):
node app.jsStop:
docker compose downBuild a minimal event-driven pipeline on GCP:
- a producer fetches latest items from an API and publishes messages to Pub/Sub
- a consumer receives push messages and processes/logs them on Cloud Run
Producer (Cloud Run Job)
- Python job that fetches the latest StackOverflow questions (tag:
data-engineering) - publishes each item as JSON to a Pub/Sub topic
- deployed as a Cloud Run Job (runs on demand, then exits)
Consumer (Cloud Run Service)
- Flask service with endpoints:
/listening- health check/- Pub/Sub push handler
- decodes Pub/Sub payloads and logs/prints structured question data
- deployed as a Cloud Run Service (scales to zero)
Pub/Sub
- topic is the transport layer between producer and consumer
- push subscription forwards each message to the Cloud Run service endpoint
- consumer acknowledges processing via HTTP response (push delivery semantics)
Authenticate and select project:
gcloud auth login
gcloud config set project "$PROJECT_ID"Load env vars (option):
export $(grep -v '^#' .env | xargs)Run the producer job (example name):
gcloud run jobs execute stackoverflow-producer --region="$GCP_REGION"Consumer is deployed once and stays available (push subscription delivers messages automatically).
Upgrade Lab 1 into a robust pipeline with:
- Schema Registry on Pub/Sub (AVRO validation)
- Dead Letter Topic (DLQ) for invalid messages
- durable storage in GCS:
- RAW JSON
- PROCESSED Parquet (partitioned by time)
- loading into BigQuery (warehouse layer)
- CI/CD automation with GitHub Actions + branch protection
- Producer → Pub/Sub
- producer encodes messages into AVRO
- Pub/Sub validates schema; invalid messages route to DLQ
- Consumer (Cloud Run Service)
- receives Pub/Sub push messages
- decodes AVRO into Python dict
- writes:
- RAW JSON to raw bucket
- PROCESSED Parquet to processed bucket
- organizes Parquet by:
year=YYYY/month=MM/day=DD/hour=HH/
- Loader (Cloud Run Job / local)
- reads processed Parquet hour-folders
- loads into BigQuery using load jobs
- uses an idempotent checkpoint to avoid duplicate loads
- CI/CD
- CI runs on Pull Requests (linting/checks)
- CD runs only when changes merge into
main:- build & push Docker images
- deploy consumer as Cloud Run Service
- deploy producer/loader as Cloud Run Jobs
- (optionally) execute jobs after deployment
- branch protection enforces PR + required checks
export PROJECT_ID="your-project"
export GCP_REGION="europe-west1"
export PUBSUB_TOPIC="stackoverflow-topic-<id>"
export DLQ_TOPIC="stackoverflow-dead-letter-topic"
export RAW_BUCKET="gcs-stackoverflow-raw"
export PROCESSED_BUCKET="gcs-stackoverflow-processed"
export PREFIX="stackoverflow"
export BQ_DATASET="stackoverflow_pipeline"
export BQ_TABLE="stackoverflow_questions"bq rm -f -t "$PROJECT_ID:$BQ_DATASET.$BQ_TABLE"gcloud storage rm "gs://$RAW_BUCKET/$PREFIX/_checkpoints/bq_loader_state.json"gcloud storage rm -r "gs://$RAW_BUCKET/$PREFIX/**"
gcloud storage rm -r "gs://$PROCESSED_BUCKET/$PREFIX/**"Producer:
gcloud run jobs execute stackoverflow-producer --region="$GCP_REGION"Consumer: no manual run (push subscription triggers it).
Loader (local test):
uv run python3 TPIUO_Labos_2/Loader/load_to_bq.pyLoader (Cloud Run Job):
gcloud run jobs execute stackoverflow-bq-loader --region="$GCP_REGION"Row count:
bq query --use_legacy_sql=false \
"SELECT COUNT(*) AS total_rows
FROM \`$PROJECT_ID.$BQ_DATASET.$BQ_TABLE\`"Latest records:
bq query --use_legacy_sql=false \
"SELECT question_id, title, creation_date
FROM \`$PROJECT_ID.$BQ_DATASET.$BQ_TABLE\`
ORDER BY creation_date DESC
LIMIT 10"Turn the raw warehouse table into an analytics-ready model:
- dbt medallion architecture: bronze → silver → gold
- SQL window functions and aggregations for trends (e.g., by time / sentiment)
- Great Expectations to validate quality before BI
- Looker Studio dashboard on top of gold tables
dbt project
- bronze: lightweight views over raw BigQuery tables
- silver: cleaned/standardized tables (dedup, null handling, normalized columns)
- gold: aggregated tables optimized for BI (daily/hourly trends, sentiment counts, rankings)
Great Expectations
- expectation suites for key models (e.g., not-null, uniqueness, accepted values, length checks)
- checkpoints producing HTML reports (pass/fail visibility)
Looker Studio
- dashboard connected to BigQuery (gold layer)
- filters/time controls work efficiently because metrics are pre-aggregated
From the dbt project directory:
dbt debug
dbt run
dbt testDocs / lineage graph:
dbt docs generate
dbt docs serveInitialize and create suites/checkpoints (once), then run:
great_expectations checkpoint run <checkpoint_name>- How to design an event-driven ingestion pipeline using Pub/Sub and serverless compute (Cloud Run).
- Why schemas (AVRO) + DLQ matter in streaming systems for reliability and debuggability.
- How to structure storage as raw vs processed, and why Parquet + partitioning are essential for analytics.
- How to load data into BigQuery in an idempotent, scalable way (load jobs + checkpoints).
- How to apply software engineering best practices in data engineering:
- containerization, CI/CD, branch protection, linting, reproducible environments
- How to build an analytics layer with dbt, validate it with Great Expectations, and serve results in Looker Studio.
- Keep all GCP resources in the same region to reduce latency/cost.
- If some GCP services are blocked on certain networks, switch to an alternative connection.
- This repository is primarily for academic lab submission and reproducibility.