Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 21, 2025

Adds final bootcamp chapter teaching layered architecture (Martin Fowler pattern) through contrasting Flask implementations demonstrating tight coupling vs separation of concerns.

Documentation

  • 11.0-overview.md - Chapter intro (10min)
  • 11.1-layers.md - Layered architecture concepts, benefits, trade-offs (30min, 2 exercises)
  • Front-matter metadata for bootcamp analytics
  • Sidebar navigation updated

Examples

Example 1: Tightly Coupled

Single-file Flask app mixing presentation, business logic, and data access:

@app.route("/users", methods=["POST"])
def create_user():
    global NEXT_ID
    payload = request.get_json() or {}
    name = payload.get("name")
    if not name:  # validation in route
        return jsonify({"error": "name required"}), 400
    user = {"id": NEXT_ID, "name": name}  # direct data manipulation
    USERS.append(user)
    return jsonify(user), 201

Example 2: Layered Architecture

Separation into presentation/services/data layers with repository pattern:

# Presentation: routes.py
@bp.route("/users", methods=["POST"])
def add_user():
    return jsonify(create_user(request.get_json() or {})), 201

# Business: user_service.py  
def create_user(payload):
    if not payload.get("name"):
        raise ValueError("name required")
    return repo.add({"name": payload["name"]})

# Data: __init__.py (single swap point)
from .in_memory_repo import get_all, add
# from .sqlite_repo import get_all, add  # switch storage here

Exercise: Swap from in-memory to SQLite storage. Example 1 requires editing route handlers; Example 2 requires changing one import statement.

Incidental

  • Fixed pre-existing typo: "Pair Programing" → "Pair Programming"
  • Added Flask terms to cspell dictionary
  • Gitignore rules for Python artifacts
Original prompt

This section details on the original issue you should resolve

<issue_title>Add final chapter 'Application Development' — Section: 'Layers' (Layered Architecture examples & exercises)</issue_title>
<issue_description>Context

This repository hosts the DevOps Bootcamp curriculum. We need a new final chapter titled "Application Development" containing a section called "Layers" that teaches layered architecture (Martin Fowler pattern) for bootcamp participants learning software design principles.

Problem

There is no chapter or section that teaches layered architecture with practical, runnable examples demonstrating the difference between tightly coupled code and well-separated layers. Participants need a simple, hands-on exercise showing how to refactor a tightly coupled app into presentation, business logic, and data access layers and why it's beneficial.

Proposed Technical Approach (authoritative guidance)

Summary

  • Recommend Python + Flask (minimal dependency, familiar to bootcamp learners).
  • Provide two runnable examples:
    • Example 1: tightly coupled single-file app (routes + business + data together).
    • Example 2: refactored layered app (presentation, business, data) with a repo interface so data backend can be swapped with minimal changes.
  • Provide a hands-on exercise: change storage from in-memory to SQLite. Hard in Example 1 (edit route + logic); easy in Example 2 (implement new repo and swap one import).

Tech stack (recommended)

  • Python 3.8+
  • Flask (single external dependency)
  • pytest (for simple unit tests)
  • sqlite3 (stdlib) — optional when swapping to SQLite
  • Optional: GitHub Actions for CI

High-level folder structures

Example 1 — tightly coupled (single file)

  • example1/
    • requirements.txt
    • app.py
    • README.md
    • tests/
      • test_app.py

Example 2 — layered (package)

  • example2/
    • requirements.txt
    • run.py
    • app/
      • init.py # create_app() factory
      • presentation/
        • routes.py # Flask blueprint
      • services/
        • user_service.py # business logic
      • data/
        • init.py # exposes repository instance
        • in_memory_repo.py # default repo implementation
        • sqlite_repo.py # alternative implementation (exercise)
      • models/
        • user.py
      • db.py # sqlite helper (if used)
    • README.md
    • tests/
      • test_routes.py

Implementation steps — deliverables to create

  1. Boilerplate for both examples
  • Create directories and files as shown above.
  • requirements.txt: "Flask>=2.0,<3.0\npytest>=6.0"
  • Add README.md with run/test commands (below).
  1. Example 1 files (core)
  • example1/requirements.txt

    • Flask>=2.0,<3.0
    • pytest>=6.0
  • example1/app.py (single-file app — minimal)

from flask import Flask, request, jsonify

app = Flask(__name__)

# In-memory "DB"
USERS = [
    {"id": 1, "name": "Alice"},
    {"id": 2, "name": "Bob"}
]
NEXT_ID = 3

@app.route("/users", methods=["GET"])
def list_users():
    return jsonify(USERS)

@app.route("/users", methods=["POST"])
def create_user():
    global NEXT_ID
    payload = request.get_json() or {}
    name = payload.get("name")
    if not name:
        return jsonify({"error": "name required"}), 400
    user = {"id": NEXT_ID, "name": name}
    NEXT_ID += 1
    USERS.append(user)
    return jsonify(user), 201

if __name__ == "__main__":
    app.run(port=5000, debug=True)
  • example1/tests/test_app.py (uses Flask test client)
from app import app
import json

def test_list_users():
    client = app.test_client()
    r = client.get("/users")
    assert r.status_code == 200
    data = r.get_json()
    assert isinstance(data, list)

def test_create_user():
    client = app.test_client()
    r = client.post("/users", json={"name": "Charlie"})
    assert r.status_code == 201
    assert r.get_json()["name"] == "Charlie"
  1. Example 2 files (layered)
  • example2/requirements.txt

    • Flask>=2.0,<3.0
    • pytest>=6.0
  • example2/run.py

from app import create_app

app = create_app()

if __name__ == "__main__":
    app.run(port=5001, debug=True)
  • example2/app/init.py (factory, register blueprint)
from flask import Flask
from .presentation.routes import bp as user_bp

def create_app():
    app = Flask(__name__)
    app.register_blueprint(user_bp)
    return app
  • example2/app/presentation/routes.py (presentation layer)
from flask import Blueprint, request, jsonify
from ..services.user_service import get_users, create_user

bp = Blueprint("users", __name__)

@bp.route("/users", methods=["GET"])
def list_users():
    return jsonify(get_users())

@bp.route("/users", methods=["POST"])
def add_user():
    payload = request.get_json() or {}
    try:
        user = create_user(payload)
    except ValueError as e:
        return jsonify({"error": str(e)}), 400
    return jsonify(user), 201
  • example2/app/services/user_service.py (business layer)
# keep business logic here (validation, business rules)
from ...

</details>

- Fixes liatrio/devops-bootcamp#797

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

Copilot AI changed the title [WIP] Add final chapter on application development with layered architecture Add Chapter 11: Application Development - Layered Architecture Nov 21, 2025
Copilot AI requested a review from jburns24 November 21, 2025 17:46
Copilot finished work on behalf of jburns24 November 21, 2025 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants