{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.13.0"
    },
    "blog_metadata": {
      "topic": "How to ship full-stack data apps in a weekend without sacrificing maintainability",
      "slug": "how-to-ship-full-stack-data-apps-in-a-weekend-without-sacrif",
      "generated_by": "LinkedIn Post Generator + Azure OpenAI",
      "generated_at": "2026-05-05T21:55:59.839Z"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# How to ship full-stack data apps in a weekend without sacrificing maintainability\n",
        "\n",
        "This notebook turns the blog post into a hands-on validation guide using Python-first examples. The focus is disciplined simplification: a thin API, explicit schema and identity boundaries, lightweight deployment automation, and enough observability to keep a fast prototype maintainable."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%pip install fastapi pydantic uvicorn pandas pyyaml httpx"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "from dataclasses import dataclass, asdict\n",
        "from typing import Dict, List, Any\n",
        "from pathlib import Path\n",
        "import json\n",
        "import sqlite3\n",
        "import textwrap\n",
        "import time\n",
        "\n",
        "import pandas as pd\n",
        "import yaml\n",
        "from fastapi import FastAPI\n",
        "from fastapi.testclient import TestClient\n",
        "from pydantic import BaseModel, Field"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Weekend build thesis and architecture boundaries\n",
        "\n",
        "A maintainable weekend build comes from a small modular architecture with clean seams. The four runtime boundaries are front end, thin API, transactional database, and storage/observability, with identity and secrets treated as cross-cutting concerns from day one.\n",
        "\n",
        "The reference architecture below is represented as Python data so we can inspect and validate the intended flow."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "architecture = {\n",
        "    \"nodes\": [\n",
        "        \"Frontend UI\",\n",
        "        \"Thin API\",\n",
        "        \"Repository Layer\",\n",
        "        \"SQL Database\",\n",
        "        \"Stable DTOs\",\n",
        "        \"App Insights\",\n",
        "        \"CI/CD\",\n",
        "        \"Bicep Deploy\",\n",
        "        \"App Config + Secrets\",\n",
        "    ],\n",
        "    \"edges\": [\n",
        "        (\"Frontend UI\", \"Thin API\"),\n",
        "        (\"Thin API\", \"Repository Layer\"),\n",
        "        (\"Repository Layer\", \"SQL Database\"),\n",
        "        (\"Thin API\", \"Stable DTOs\"),\n",
        "        (\"Thin API\", \"App Insights\"),\n",
        "        (\"CI/CD\", \"Bicep Deploy\"),\n",
        "        (\"Bicep Deploy\", \"App Config + Secrets\"),\n",
        "        (\"App Config + Secrets\", \"Thin API\"),\n",
        "        (\"App Config + Secrets\", \"Frontend UI\"),\n",
        "    ],\n",
        "}\n",
        "\n",
        "print(\"Architecture nodes:\")\n",
        "for node in architecture[\"nodes\"]:\n",
        "    print(\"-\", node)\n",
        "\n",
        "print(\"\\nArchitecture edges:\")\n",
        "for src, dst in architecture[\"edges\"]:\n",
        "    print(f\"- {src} -> {dst}\")\n",
        "\n",
        "required_boundaries = {\"Frontend UI\", \"Thin API\", \"SQL Database\", \"App Insights\"}\n",
        "print(\"\\nRequired boundaries present:\", required_boundaries.issubset(set(architecture[\"nodes\"])))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Service selection criteria and repo structure\n",
        "\n",
        "The smallest maintainable architecture should optimize for team familiarity, deployment simplicity, managed identity support, operational visibility, ease of local development, and minimal platform sprawl. A monorepo can still be maintainable if boundaries are explicit.\n",
        "\n",
        "This cell validates a recommended repo layout."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "recommended_repo = [\"app\", \"api\", \"db\", \"infra\", \"scripts\", \"docs\"]\n",
        "repo_df = pd.DataFrame(\n",
        "    {\n",
        "        \"folder\": recommended_repo,\n",
        "        \"purpose\": [\n",
        "            \"front end\",\n",
        "            \"service layer\",\n",
        "            \"migrations and seed scripts\",\n",
        "            \"Bicep and infrastructure definitions\",\n",
        "            \"bootstrap and automation tasks\",\n",
        "            \"architecture notes and setup docs\",\n",
        "        ],\n",
        "    }\n",
        ")\n",
        "repo_df"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Explicit contracts between UI and API\n",
        "\n",
        "The front end should depend on stable DTOs rather than raw database columns. This example uses a thin FastAPI app with validation, a repository layer, and DTO mapping so the API contract stays stable even if storage field names differ."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "app = FastAPI(title=\"Weekend Data App\")\n",
        "\n",
        "class CreateOrderRequest(BaseModel):\n",
        "    customer_id: int = Field(gt=0)\n",
        "    amount_cents: int = Field(gt=0)\n",
        "\n",
        "class OrderDto(BaseModel):\n",
        "    id: int\n",
        "    customer_id: int\n",
        "    total: str\n",
        "\n",
        "@dataclass\n",
        "class OrderRow:\n",
        "    order_id: int\n",
        "    customer_ref: int\n",
        "    amount_cents: int\n",
        "\n",
        "class OrderRepository:\n",
        "    def __init__(self):\n",
        "        self._next_id = 101\n",
        "\n",
        "    def create(self, customer_id: int, amount_cents: int) -> OrderRow:\n",
        "        row = OrderRow(order_id=self._next_id, customer_ref=customer_id, amount_cents=amount_cents)\n",
        "        self._next_id += 1\n",
        "        return row\n",
        "\n",
        "repo = OrderRepository()\n",
        "\n",
        "@app.get(\"/health\")\n",
        "def health() -> Dict[str, str]:\n",
        "    return {\"status\": \"ok\"}\n",
        "\n",
        "@app.post(\"/orders\", response_model=OrderDto)\n",
        "def create_order(req: CreateOrderRequest) -> OrderDto:\n",
        "    row = repo.create(req.customer_id, req.amount_cents)\n",
        "    return OrderDto(id=row.order_id, customer_id=row.customer_ref, total=f\"{row.amount_cents / 100:.2f}\")\n",
        "\n",
        "client = TestClient(app)\n",
        "health_response = client.get(\"/health\")\n",
        "order_response = client.post(\"/orders\", json={\"customer_id\": 42, \"amount_cents\": 2599})\n",
        "\n",
        "print(\"Health:\", health_response.status_code, health_response.json())\n",
        "print(\"Order:\", order_response.status_code, order_response.json())"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Front-end client contract validation\n",
        "\n",
        "The original post included a TypeScript client. Here we validate the same idea in Python: the client depends only on the stable DTO returned by the API, not on database-shaped responses."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "def create_order_client(customer_id: int, amount_cents: int) -> Dict[str, Any]:\n",
        "    response = client.post(\"/orders\", json={\"customer_id\": customer_id, \"amount_cents\": amount_cents})\n",
        "    if response.status_code != 200:\n",
        "        raise RuntimeError(f\"API error: {response.status_code}\")\n",
        "    return response.json()\n",
        "\n",
        "client_result = create_order_client(7, 1234)\n",
        "print(client_result)\n",
        "print(\"Client sees stable keys:\", sorted(client_result.keys()))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Schema discipline with versioned migrations and seed data\n",
        "\n",
        "Fast apps still need reproducible schema state. The blog used SQL migrations for Azure SQL; for notebook validation we use SQLite with equivalent ideas: a migration tracking table, an orders table, and idempotent seed data."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "migration_sql = \"\"\"\n",
        "BEGIN TRANSACTION;\n",
        "\n",
        "CREATE TABLE IF NOT EXISTS schema_migrations (\n",
        "    version VARCHAR(20) PRIMARY KEY,\n",
        "    applied_utc TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP\n",
        ");\n",
        "\n",
        "CREATE TABLE IF NOT EXISTS orders (\n",
        "    id INTEGER PRIMARY KEY,\n",
        "    customer_id INTEGER NOT NULL,\n",
        "    amount_cents INTEGER NOT NULL CHECK (amount_cents > 0),\n",
        "    created_utc TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP\n",
        ");\n",
        "\n",
        "INSERT INTO schema_migrations (version)\n",
        "SELECT 'v001_init'\n",
        "WHERE NOT EXISTS (SELECT 1 FROM schema_migrations WHERE version = 'v001_init');\n",
        "\n",
        "INSERT INTO orders (id, customer_id, amount_cents)\n",
        "SELECT 1, 42, 2599\n",
        "WHERE NOT EXISTS (SELECT 1 FROM orders WHERE id = 1);\n",
        "\n",
        "COMMIT;\n",
        "\"\"\"\n",
        "\n",
        "conn = sqlite3.connect(\":memory:\")\n",
        "conn.executescript(migration_sql)\n",
        "conn.executescript(migration_sql)\n",
        "\n",
        "migrations_df = pd.read_sql_query(\"SELECT * FROM schema_migrations\", conn)\n",
        "orders_df = pd.read_sql_query(\"SELECT * FROM orders\", conn)\n",
        "\n",
        "print(\"Applied migrations:\")\n",
        "display(migrations_df)\n",
        "print(\"\\nSeeded orders:\")\n",
        "display(orders_df)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Transactional versus derived data\n",
        "\n",
        "A maintainable app keeps transactional data separate from derived or downstream projections. This cell demonstrates the distinction by creating a derived reporting view from the transactional orders table without changing the source schema."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "conn.execute(\"DROP VIEW IF EXISTS order_reporting\")\n",
        "conn.execute(\n",
        "    \"\"\"\n",
        "    CREATE VIEW order_reporting AS\n",
        "    SELECT\n",
        "        id,\n",
        "        customer_id,\n",
        "        amount_cents,\n",
        "        ROUND(amount_cents / 100.0, 2) AS amount_dollars,\n",
        "        created_utc\n",
        "    FROM orders\n",
        "    \"\"\"\n",
        ")\n",
        "report_df = pd.read_sql_query(\"SELECT * FROM order_reporting\", conn)\n",
        "report_df"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Required environment variables and secret boundaries\n",
        "\n",
        "If you deploy this pattern for real, keep secrets out of source code and separate developer, application, and end-user identities.\n",
        "\n",
        "Required variables for a realistic deployment:\n",
        "- `AZURE_SUBSCRIPTION_ID`\n",
        "- `AZURE_RESOURCE_GROUP`\n",
        "- `AZURE_LOCATION`\n",
        "- `APP_NAME`\n",
        "- `KEY_VAULT_URI`\n",
        "- `APPLICATIONINSIGHTS_CONNECTION_STRING`\n",
        "- `SQL_CONNECTION_STRING` or managed identity-based database access settings\n",
        "- `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET` only when a service principal is required for automation"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Infrastructure as code with Bicep\n",
        "\n",
        "The blog used Bicep to provision app hosting, database, Key Vault, and monitoring. Since Bicep is not executable as Python, this cell stores the template as a string and performs lightweight validation on the important outputs and parameters."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "main_bicep = textwrap.dedent(\"\"\"\n",
        "param location string = resourceGroup().location\n",
        "param appName string\n",
        "param sqlAdminLogin string\n",
        "@secure()\n",
        "param sqlAdminPassword string\n",
        "\n",
        "module monitor './modules/monitor.bicep' = { name: 'monitor'; params: { appName: appName location: location } }\n",
        "module vault './modules/vault.bicep' = { name: 'vault'; params: { appName: appName location: location } }\n",
        "module data './modules/sql.bicep' = { name: 'data'; params: { appName: appName location: location sqlAdminLogin: sqlAdminLogin sqlAdminPassword: sqlAdminPassword } }\n",
        "module app './modules/app.bicep' = {\n",
        "  name: 'app'\n",
        "  params: {\n",
        "    appName: appName\n",
        "    location: location\n",
        "    appInsightsConnectionString: monitor.outputs.connectionString\n",
        "    keyVaultUri: vault.outputs.vaultUri\n",
        "    sqlConnectionString: data.outputs.connectionString\n",
        "  }\n",
        "}\n",
        "\n",
        "output apiUrl string = app.outputs.apiUrl\n",
        "output keyVaultUri string = vault.outputs.vaultUri\n",
        "\"\"\")\n",
        "\n",
        "checks = {\n",
        "    \"has_appName_param\": \"param appName string\" in main_bicep,\n",
        "    \"has_secure_sql_password\": \"@secure()\" in main_bicep,\n",
        "    \"outputs_api_url\": \"output apiUrl string\" in main_bicep,\n",
        "    \"outputs_key_vault_uri\": \"output keyVaultUri string\" in main_bicep,\n",
        "    \"uses_modules\": main_bicep.count(\"module \") >= 4,\n",
        "}\n",
        "\n",
        "print(main_bicep)\n",
        "print(\"\\nValidation checks:\")\n",
        "print(json.dumps(checks, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## App host configuration and centralized settings\n",
        "\n",
        "The app host should receive telemetry and secret references at deploy time. This cell validates the presence of the expected app settings in the hosting module."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "app_bicep = textwrap.dedent(\"\"\"\n",
        "param location string\n",
        "param appName string\n",
        "param appInsightsConnectionString string\n",
        "param keyVaultUri string\n",
        "param sqlConnectionString string\n",
        "\n",
        "resource plan 'Microsoft.Web/serverfarms@2022-09-01' = {\n",
        "  name: '${appName}-plan'\n",
        "  location: location\n",
        "  sku: { name: 'B1', tier: 'Basic' }\n",
        "}\n",
        "\n",
        "resource site 'Microsoft.Web/sites@2022-09-01' = {\n",
        "  name: '${appName}-api'\n",
        "  location: location\n",
        "  properties: {\n",
        "    serverFarmId: plan.id\n",
        "    siteConfig: {\n",
        "      appSettings: [\n",
        "        { name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'; value: appInsightsConnectionString }\n",
        "        { name: 'KEY_VAULT_URI'; value: keyVaultUri }\n",
        "        { name: 'SQL_CONNECTION_STRING'; value: sqlConnectionString }\n",
        "      ]\n",
        "    }\n",
        "  }\n",
        "}\n",
        "\n",
        "output apiUrl string = 'https://${site.properties.defaultHostName}'\n",
        "\"\"\")\n",
        "\n",
        "required_settings = [\n",
        "    \"APPLICATIONINSIGHTS_CONNECTION_STRING\",\n",
        "    \"KEY_VAULT_URI\",\n",
        "    \"SQL_CONNECTION_STRING\",\n",
        "]\n",
        "\n",
        "setting_checks = {setting: setting in app_bicep for setting in required_settings}\n",
        "print(app_bicep)\n",
        "print(\"\\nSetting checks:\")\n",
        "print(json.dumps(setting_checks, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Bootstrap automation for fast environment creation\n",
        "\n",
        "A weekend team should be able to stand up an environment with one command. The original example used PowerShell and Azure CLI; here we keep the script as text and validate that it includes resource group creation, Bicep deployment, app settings, and a smoke test."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "bootstrap_script = textwrap.dedent(r'''\n",
        "param(\n",
        "  [string]$ResourceGroup = \"rg-weekend-dataapp\",\n",
        "  [string]$Location = \"eastus\",\n",
        "  [string]$AppName = \"weekenddataapp$((Get-Random -Maximum 9999))\"\n",
        ")\n",
        "\n",
        "az group create --name $ResourceGroup --location $Location | Out-Null\n",
        "$deployment = az deployment group create `\n",
        "  --resource-group $ResourceGroup `\n",
        "  --template-file ./infra/main.bicep `\n",
        "  --parameters appName=$AppName sqlAdminLogin=sqladmin sqlAdminPassword=\"P@ssw0rd123!\" `\n",
        "  --query properties.outputs -o json | ConvertFrom-Json\n",
        "\n",
        "$apiUrl = $deployment.apiUrl.value\n",
        "az webapp config appsettings set --resource-group $ResourceGroup --name \"$AppName-api\" `\n",
        "  --settings \"APP_ENV=weekend\" \"FEATURE_SAMPLE_DATA=true\" | Out-Null\n",
        "\n",
        "Invoke-RestMethod -Uri \"$apiUrl/health\" -Method Get | Out-Null\n",
        "Write-Host \"Smoke test passed at $apiUrl/health\"\n",
        "''')\n",
        "\n",
        "bootstrap_checks = {\n",
        "    \"creates_resource_group\": \"az group create\" in bootstrap_script,\n",
        "    \"deploys_bicep\": \"az deployment group create\" in bootstrap_script,\n",
        "    \"sets_app_settings\": \"az webapp config appsettings set\" in bootstrap_script,\n",
        "    \"runs_smoke_test\": \"Invoke-RestMethod\" in bootstrap_script and \"/health\" in bootstrap_script,\n",
        "}\n",
        "\n",
        "print(bootstrap_script)\n",
        "print(\"\\nBootstrap checks:\")\n",
        "print(json.dumps(bootstrap_checks, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## CI/CD sequence validation\n",
        "\n",
        "A minimal pipeline should authenticate, build, deploy infrastructure, run migrations, deploy code, and smoke test. This cell parses the workflow text and checks that the critical stages are present."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "workflow_yaml = textwrap.dedent(\"\"\"\n",
        "name: weekend-ship\n",
        "\n",
        "on: { push: { branches: [main] } }\n",
        "\n",
        "jobs:\n",
        "  deploy:\n",
        "    runs-on: ubuntu-latest\n",
        "    steps:\n",
        "      - uses: actions/checkout@v4\n",
        "      - uses: actions/setup-python@v5\n",
        "        with: { python-version: '3.12' }\n",
        "      - uses: actions/setup-node@v4\n",
        "        with: { node-version: '20' }\n",
        "      - run: pip install -r api/requirements.txt && npm ci --prefix web\n",
        "      - run: python api/scripts/apply_migrations.py\n",
        "      - run: az deployment group create -g $RG -f infra/main.bicep -p appName=$APP_NAME\n",
        "      - run: az webapp deploy -g $RG -n $APP_NAME-api --src-path api.zip\n",
        "      - run: curl --fail https://$APP_NAME-api.azurewebsites.net/health\n",
        "\"\"\")\n",
        "\n",
        "workflow = yaml.safe_load(workflow_yaml)\n",
        "steps = workflow[\"jobs\"][\"deploy\"][\"steps\"]\n",
        "step_text = json.dumps(steps)\n",
        "workflow_checks = {\n",
        "    \"checkout\": \"actions/checkout\" in step_text,\n",
        "    \"python_setup\": \"actions/setup-python\" in step_text,\n",
        "    \"node_setup\": \"actions/setup-node\" in step_text,\n",
        "    \"migrations\": \"apply_migrations.py\" in step_text,\n",
        "    \"infra_deploy\": \"az deployment group create\" in step_text,\n",
        "    \"app_deploy\": \"az webapp deploy\" in step_text,\n",
        "    \"health_check\": \"/health\" in step_text,\n",
        "}\n",
        "\n",
        "print(workflow_yaml)\n",
        "print(\"\\nWorkflow checks:\")\n",
        "print(json.dumps(workflow_checks, indent=2))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Deployment flow as an operational sequence\n",
        "\n",
        "The deployment story should be explicit: code push, infrastructure deployment, migration, configuration, code deployment, and health validation. This cell models the sequence as structured Python data so it can be reviewed and tested."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "deployment_sequence = [\n",
        "    (\"Developer\", \"CI/CD\", \"Push main\"),\n",
        "    (\"CI/CD\", \"Bicep Deploy\", \"Deploy infrastructure\"),\n",
        "    (\"Bicep Deploy\", \"API App\", \"Configure app settings\"),\n",
        "    (\"CI/CD\", \"SQL DB\", \"Apply migration + seed\"),\n",
        "    (\"CI/CD\", \"API App\", \"Deploy code\"),\n",
        "    (\"CI/CD\", \"API App\", \"GET /health\"),\n",
        "    (\"API App\", \"CI/CD\", \"200 OK\"),\n",
        "]\n",
        "\n",
        "sequence_df = pd.DataFrame(deployment_sequence, columns=[\"from\", \"to\", \"action\"])\n",
        "sequence_df"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Observability from day one\n",
        "\n",
        "Fast shipping is safer when failures are diagnosable. This example simulates structured logs, request traces, and business events for a small order workflow."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "telemetry_events: List[Dict[str, Any]] = []\n",
        "\n",
        "def log_event(kind: str, name: str, **properties: Any) -> None:\n",
        "    telemetry_events.append(\n",
        "        {\n",
        "            \"ts\": round(time.time(), 3),\n",
        "            \"kind\": kind,\n",
        "            \"name\": name,\n",
        "            \"properties\": properties,\n",
        "        }\n",
        "    )\n",
        "\n",
        "log_event(\"trace\", \"request_started\", path=\"/orders\", method=\"POST\")\n",
        "result = create_order_client(99, 5000)\n",
        "log_event(\"business\", \"order_created\", order_id=result[\"id\"], customer_id=result[\"customer_id\"], total=result[\"total\"])\n",
        "log_event(\"metric\", \"health_status\", status=\"ok\")\n",
        "\n",
        "telemetry_df = pd.DataFrame(telemetry_events)\n",
        "telemetry_df"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Storage choices: keep them boring and reversible\n",
        "\n",
        "Blob storage is a good fit for files, exports, reports, and archived payloads, while relational data stays in the transactional database. This cell classifies common assets to validate the storage decision."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "assets = pd.DataFrame(\n",
        "    [\n",
        "        (\"uploaded_invoice.pdf\", \"blob_storage\"),\n",
        "        (\"daily_export.csv\", \"blob_storage\"),\n",
        "        (\"order_record\", \"sql_database\"),\n",
        "        (\"customer_profile\", \"sql_database\"),\n",
        "        (\"archived_payload.json\", \"blob_storage\"),\n",
        "    ],\n",
        "    columns=[\"asset\", \"recommended_store\"],\n",
        ")\n",
        "assets"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Common failure modes and fast fixes\n",
        "\n",
        "The blog highlighted four common failure modes: hardcoded secrets, UI coupled to database shape, no migrations, and too many services too early. This cell turns them into a compact checklist."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "failure_modes = pd.DataFrame(\n",
        "    [\n",
        "        (\"Hardcoded secrets\", \"leaked credentials, blocked deployments\", \"Key Vault, deploy-time injection, managed identity\"),\n",
        "        (\"UI tightly coupled to DB shape\", \"schema changes break front end\", \"stable DTOs, API contracts, repository mapping\"),\n",
        "        (\"No migrations\", \"environment drift\", \"versioned schema changes, repeatable seed scripts, CI/CD execution\"),\n",
        "        (\"Too many services too early\", \"higher cost and slower debugging\", \"collapse to one UI, one API, one DB, one storage, one monitoring path\"),\n",
        "    ],\n",
        "    columns=[\"failure_mode\", \"symptom\", \"fast_fix\"],\n",
        ")\n",
        "failure_modes"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Weekend checklist as a reusable internal standard\n",
        "\n",
        "A platform team can turn this pattern into a standard by making the non-negotiables explicit. This cell encodes the checklist and validates that the core items are present."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "weekend_checklist = {\n",
        "    \"monorepo_structure\": [\"/app\", \"/api\", \"/db\", \"/infra\", \"/scripts\", \"/docs\"],\n",
        "    \"thin_api\": True,\n",
        "    \"versioned_migrations\": True,\n",
        "    \"reproducible_seed_data\": True,\n",
        "    \"key_vault_backed_secrets\": True,\n",
        "    \"separate_identities\": [\"developer\", \"application\", \"end-user\"],\n",
        "    \"bicep_environment_creation\": True,\n",
        "    \"deployment_script\": True,\n",
        "    \"ci_workflow\": True,\n",
        "    \"application_insights\": True,\n",
        "    \"blob_storage\": True,\n",
        "    \"health_endpoint_and_smoke_test\": True,\n",
        "}\n",
        "\n",
        "print(json.dumps(weekend_checklist, indent=2))\n",
        "print(\"\\nChecklist complete:\", all(v if isinstance(v, bool) else len(v) > 0 for v in weekend_checklist.values()))"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Next Steps\n",
        "\n",
        "This notebook validated the core pattern from the post: keep the architecture small, enforce a thin API boundary, use versioned migrations, automate environment creation lightly, and add observability before release. To extend this into a real project, replace the in-memory examples with Azure resources, switch database access to managed identity where possible, move secrets to Key Vault, and wire the Bicep and CI/CD definitions into your actual repository."
      ]
    }
  ]
}