Pendahuluan — class yang sama, pintu HTTP
Di Seri 3 kamu sudah punya mental model objek. Tier 2 membawa OOP ke dua arah: perangkat di MicroPython (#51), dan sekarang web API. Flask/FastAPI hanya “pintu”; logika bisnis tetap di class — pola yang sama dengan service di Capstone (#49) dan factory ringan di Factory (#50).
Artikel ini sengaja runnable di PC tanpa wajib install framework dulu: stub HTTP + class service. Di bagian porting, kamu lihat bagaimana stub itu dipasang ke Flask/FastAPI. Tujuannya bukan tutorial framework lengkap, melainkan menjaga batas OOP tetap jelas saat request masuk.
Kalau Capstone melatih CLI dan MicroPython melatih node, di sini kamu melatih adapter: HTTP di tepi, domain di tengah. Ganti Flask dengan FastAPI (atau sebaliknya) seharusnya tidak merombak PerpustakaanService.
Prasyarat: MicroPython OOP (#51) atau minimal Composition (#47) + Capstone (#49) · Mengenal OOP (#40). Opsional: Factory (#50).
Kenapa class di web, bukan view panjang?
| Pendekatan | Gejala | Akibat |
|---|---|---|
| Semua di fungsi route | Validasi + DB + JSON campur | Sulit diuji; route jadi “tuhan” |
| Class service | Route tipis; service punya koleksi/aturan | Bisa demo() tanpa server; ganti framework lebih aman |
Ini composition: aplikasi punya service, service punya item — bukan mewarisi Flask. Inheritance framework mengikat domain ke satu library; composition menjaga service bisa diuji dan diport.
# Anti-pola (jangan): class App(Flask): ...
# Pola OOP: app punya service
class PerpustakaanService:
def __init__(self):
self._items = []
@property
def jumlah(self):
return len(self._items)
class AppShell:
"""Pengganti 'app' tipis — composition, bukan inheritance framework."""
def __init__(self, service):
self.service = service
app = AppShell(PerpustakaanService())
print("service terpasang, jumlah=", app.service.jumlah)
Output:
service terpasang, jumlah= 0
PerpustakaanService.Item + service — domain yang sudah dikenal
Tetap domain perpustakaan agar jembatan ke Capstone jelas (semangat Encapsulation (#43)):
class BukuItem:
def __init__(self, judul, penulis):
self.judul = judul
self.penulis = penulis
def as_dict(self):
return {"judul": self.judul, "penulis": self.penulis}
class PerpustakaanService:
def __init__(self):
self._items = []
def tambah(self, judul, penulis):
if not judul.strip():
raise ValueError("judul wajib")
item = BukuItem(judul.strip(), penulis.strip() or "Anonim")
self._items.append(item)
return item
def daftar(self):
return [i.as_dict() for i in self._items]
svc = PerpustakaanService()
svc.tambah("OOP Python", "Kindo")
print(svc.daftar())
Output:
[{'judul': 'OOP Python', 'penulis': 'Kindo'}]
Stub HTTP — uji tanpa Flask terpasang
Framework boleh diganti; kontrak response yang kita kendalikan:
class BukuItem:
def __init__(self, judul, penulis):
self.judul = judul
self.penulis = penulis
def as_dict(self):
return {"judul": self.judul, "penulis": self.penulis}
class PerpustakaanService:
def __init__(self):
self._items = []
def tambah(self, judul, penulis):
if not judul.strip():
raise ValueError("judul wajib")
item = BukuItem(judul.strip(), penulis.strip() or "Anonim")
self._items.append(item)
return item
def daftar(self):
return [i.as_dict() for i in self._items]
class HttpResponse:
def __init__(self, status, body):
self.status = status
self.body = body
def handle_list(service):
return HttpResponse(200, {"items": service.daftar()})
def handle_create(service, judul, penulis):
try:
item = service.tambah(judul, penulis)
except ValueError as exc:
return HttpResponse(400, {"error": str(exc)})
return HttpResponse(201, item.as_dict())
svc = PerpustakaanService()
print(handle_create(svc, "Flask Ringkas", "Dewi").status)
print(handle_list(svc).body)
print(handle_create(svc, " ", "X").status)
Output:
201
{'items': [{'judul': 'Flask Ringkas', 'penulis': 'Dewi'}]}
400
Satu antarmuka handler, banyak “kerangka” di belakang — selaras Polymorphism (#45).
Factory opsional — jenis item tanpa hutan if di route
Kalau tipe mulai bertambah, pakai factory ringan (pola Factory (#50)), tetap di luar Flask. EbookItem memakai pewarisan sederhana (lihat Inheritance (#44)), tapi keputusan “buku atau ebook” tetap di fungsi factory — bukan di decorator route:
class BukuItem:
def __init__(self, judul, penulis):
self.judul = judul
self.penulis = penulis
def as_dict(self):
return {"jenis": "buku", "judul": self.judul, "penulis": self.penulis}
class EbookItem(BukuItem):
def as_dict(self):
d = super().as_dict()
d["jenis"] = "ebook"
return d
def buat_item(jenis, judul, penulis):
if jenis == "buku":
return BukuItem(judul, penulis)
if jenis == "ebook":
return EbookItem(judul, penulis)
raise ValueError(f"jenis tidak dikenal: {jenis}")
print(buat_item("ebook", "FastAPI Ringkas", "Sari").as_dict())
try:
buat_item("majalah", "X", "Y")
except ValueError as exc:
print("error:", exc)
Output:
{'jenis': 'ebook', 'judul': 'FastAPI Ringkas', 'penulis': 'Sari'}
error: jenis tidak dikenal: majalah
Porting singkat ke Flask / FastAPI
Setelah demo() hijau, pasang service yang sama. Blok di bawah sketsa (bukan wajib dijalankan di audit PC):
# Flask (sketsa)
# from flask import Flask, request, jsonify
# app = Flask(__name__)
# svc = PerpustakaanService()
#
# @app.get("/api/buku")
# def list_buku():
# r = handle_list(svc)
# return jsonify(r.body), r.status
#
# @app.post("/api/buku")
# def create_buku():
# data = request.get_json(force=True) or {}
# r = handle_create(svc, data.get("judul", ""), data.get("penulis", ""))
# return jsonify(r.body), r.status
# FastAPI (sketsa)
# from fastapi import FastAPI
# from fastapi.responses import JSONResponse
# app = FastAPI()
# svc = PerpustakaanService()
#
# @app.get("/api/buku")
# def list_buku():
# r = handle_list(svc)
# return JSONResponse(r.body, status_code=r.status)
#
# # Sketsa: query param biar pendek; body JSON nyata = model Pydantic belakangan
# @app.post("/api/buku")
# def create_buku(judul: str = "", penulis: str = ""):
# r = handle_create(svc, judul, penulis)
# return JSONResponse(r.body, status_code=r.status)
Perhatikan: PerpustakaanService tidak mengimpor Flask/FastAPI. Itu titik OOP-nya. Sketsa di atas sengaja tipis — body JSON nyata bisa pakai model Pydantic belakangan, setelah stub hijau.
Pola Dasar — OOP di pintu web
-
1
Model domain dulu
BukuItem+as_dict()— bukan dict longgar di route. -
2
Service dengan composition
PerpustakaanServicepunya koleksi — pola Composition (#47). -
3
Handler tipis Terjemahkan HTTP <-> service; jangan taruh aturan di decorator saja.
-
4
Uji dengan stub
HttpResponse+demo()— tanpa server, tanpa input menggantung. -
5
Pasang framework Flask atau FastAPI hanya adapter — service tetap sama.
Kode lengkap — perpustakaan_api_oop.py
Simpan dan jalankan: python perpustakaan_api_oop.py.
"""OOP ringan untuk API perpustakaan — stub HTTP (Tier 2 #52).
Pasang ke Flask/FastAPI lewat handler yang sama (lihat sketsa di artikel).
"""
from __future__ import annotations
class BukuItem:
def __init__(self, judul: str, penulis: str) -> None:
self.judul = judul
self.penulis = penulis
def as_dict(self) -> dict:
return {"judul": self.judul, "penulis": self.penulis}
class PerpustakaanService:
def __init__(self) -> None:
self._items: list[BukuItem] = []
def tambah(self, judul: str, penulis: str) -> BukuItem:
if not judul.strip():
raise ValueError("judul wajib")
item = BukuItem(judul.strip(), penulis.strip() or "Anonim")
self._items.append(item)
return item
def daftar(self) -> list[dict]:
return [i.as_dict() for i in self._items]
class HttpResponse:
def __init__(self, status: int, body) -> None:
self.status = status
self.body = body
def handle_list(service: PerpustakaanService) -> HttpResponse:
return HttpResponse(200, {"items": service.daftar()})
def handle_create(service: PerpustakaanService, judul: str, penulis: str) -> HttpResponse:
try:
item = service.tambah(judul, penulis)
except ValueError as exc:
return HttpResponse(400, {"error": str(exc)})
return HttpResponse(201, item.as_dict())
def demo() -> None:
svc = PerpustakaanService()
ok = handle_create(svc, "OOP Python", "Kindo")
bad = handle_create(svc, " ", "X")
listed = handle_list(svc)
print(ok.status, ok.body)
print(bad.status, bad.body)
print(listed.status, listed.body)
if __name__ == "__main__":
demo()
Output yang diharapkan:
201 {'judul': 'OOP Python', 'penulis': 'Kindo'}
400 {'error': 'judul wajib'}
200 {'items': [{'judul': 'OOP Python', 'penulis': 'Kindo'}]}
Kesalahan umum
| Gejala | Penyebab tipikal | Perbaikan |
|---|---|---|
| Route 200 baris | Logika domain di view | Pindahkan ke PerpustakaanService |
| Sulit unit-test | Import Flask di class domain | Service murni; framework hanya di adapter |
| Validasi hilang | Percaya JSON mentah | ValueError di service -> status 400 di handler |
Warisi Flask / FastAPI |
Salah inheritance | Composition: app punya service |
| Over-engineer Pydantic dulu | Terburu schema penuh | Class biasa + stub dulu; schema belakangan |
| Status selalu 200 di FastAPI | Return dict mentah tanpa status_code |
Pakai JSONResponse(..., status_code=r.status) seperti sketsa |
Latihan singkat
- Tambah
handle_cari(service, kata)yang filter judul (case-insensitive). - Ganti pembuatan item lewat fungsi factory sederhana (pola Factory (#50)) untuk tipe
ebook/buku. - Sketsa: satu endpoint yang memanggil service Node dari MicroPython (#51) (mis. status suhu) — tanpa wajib wire MQTT.
FAQ singkat
Harus Flask atau FastAPI?
Keduanya OK. FastAPI nyaman untuk type hint & docs; Flask ringan untuk belajar adapter. OOP-nya sama: service di tengah.
Kenapa tidak class App(Flask)?
Karena domain ikut terikat ke framework. Composition (AppShell / app punya service) membuat unit-test dan ganti pintu HTTP jauh lebih murah — semangat yang sama dengan Composition (#47).
Apakah ini pengganti Capstone?
Bukan. Capstone CLI tetap fondasi. Artikel ini hanya membuka pintu HTTP dengan class yang sama.
Apakah perlu instal package untuk latihan?
Tidak untuk stub. Install Flask/FastAPI saat kamu siap menjalankan sketsa porting.
Type hint di kode lengkap wajib?
Tidak. Type hint membantu FastAPI/IDE; di stub PC cukup untuk kejelasan. Jangan biarkan anotasi menunda demo() hijau.
Dari jalur IoT, mulai di mana?
Kalau kamu baru dari perangkat, selesaikan dulu MicroPython (#51) (pola service punya sensor). Pola yang sama dipakai di capstone greenhouse (#39) — di sini pintu HTTP-nya.
Lanjut ke mana?
Ide berikutnya: seri Web Dev penuh (routing, auth, DB) — belum live sebagai slug hardlink di Kindo. Fondasi OOP-mu sudah siap.
Kesimpulan & langkah berikutnya
Web tidak membatalkan OOP — ia meminta batas yang lebih jelas: domain di class, HTTP di adapter. Stub dulu, framework belakangan. Kalau route sudah “tuhan”, pecah lagi: item, service, handler, baru pintu Flask/FastAPI.
Artikel ini adalah #52 (ini) — pintu web setelah MicroPython (#51) dan Factory (#50). Tier 2 menutup jembatan perangkat + web; seri Web Dev penuh menunggu sebagai backlog terpisah.
Tier 2 progress: langkah #52 (ini) · MicroPython (#51) LIVE · Factory (#50) LIVE · Seri 3 tetap 10/10. Prasyarat: #51 · Capstone (#49) · Composition (#47) · Factory (#50) · OOP (#40).