A Ghost Story About State Management (With a Happy Ending)
Our tale begins in 2010, when JavaScript frameworks were as elegant as a Geocities page. Along came Knockout.js—the Christopher Lee of reactivity—introducing observables that tracked dependencies like a paranormal investigator. Frontend devs whispered: “It’s alive!”
But like any good horror franchise, the story evolved:
- 2014: Angular 1.x’s dirty checking (state management via exorcism)
- 2016: React’s “everything re-renders” approach (the zombie apocalypse strategy)
- 2018: SolidJS arrives, wielding Signals like a chainsaw through dependency trees
- 2023: Angular 16 embraces Signals (the prodigal son returns… with better timing)
Meanwhile, Python backends were stuck in 1999:
# Traditional "haunted house" state management
state = {"value": 0}
listeners = []
def update(value):
state["value"] = value
for callback in listeners: # Manual hauntings
try:
callback()
except:
pass # Ghosts don't crash, right?
We became the haunted house operators of software—manually rattling chains (callbacks) to spook listeners (clients). Until...
The Sรฉance That Actually Worked: reaktiv
Enter our hero—a library that channels 13 years of frontend wisdom into Python:
from reaktiv import Signal, Effect
# Modern Python spirit board
job_status = Signal("idle") # "running", "paused", "dead"
eff = Effect(lambda: print(f"Job status: {job_status.get()}"))
eff.schedule()
job_status.set("running") # Prints automatically ๐ป
This isn’t just syntactic sugar—it’s a sรฉance with software history. Let’s build something that would make Knockout.js’s creators proud...
Raising the Dead: A Job System That Doesn’t Suck
Scenario: Users want to control long-running tasks. Your requirements:
- Start/pause/cancel jobs ๐ฎ
- Live progress updates ๐
- No race conditions ๐️๐จ
Traditional approach? You’d need:
- Thread locks (❌)
- Custom pub/sub (❌)
- A PhD in concurrency (❌)
Let’s do it the reaktiv way:
import asyncio
from reaktiv import Signal, Effect
from fastapi import FastAPI, WebSocket
import uvicorn
# === The Beating Heart ===
job_status = Signal("idle") # "running", "paused", "dead"
job_progress = Signal(0)
user_commands = Signal({"type": None}) # Our telepathy channel
# === The Brain (Such As It Is) ===
async def job_zombie():
"""Rises from the dead when users poke it"""
while job_progress.get() < 100 and job_status.get() != "dead":
if job_status.get() == "paused":
await asyncio.sleep(1) # Pretend to work
continue
# Actual "work"
await asyncio.sleep(0.1)
job_progress.update(lambda x: x + 1) # 100x overpriced
# Check for divine intervention
cmd = user_commands.get()
if cmd["type"] == "pause":
job_status.set("paused")
elif cmd["type"] == "murder":
job_status.set("dead")
# === The Nervous System ===
app = FastAPI(title="Job Circus")
@app.post("/start")
async def awaken_the_beast():
if job_status.get() == "idle":
job_status.set("running")
asyncio.create_task(job_zombie()) # Unleash hell
return {"status": job_status.get()}
@app.post("/pause")
async def performance_anxiety():
user_commands.set({"type": "pause"}) # Whisper to the void
return {"status": "panic-acknowledged"}
# === The Screaming ===
active_connections = set()
@app.websocket("/ws")
async def scream_into_void(websocket: WebSocket):
"""Where progress goes to die"""
await websocket.accept()
active_connections.add(websocket)
async def howl():
await websocket.send_json({
"status": job_status.get(),
"progress": job_progress.get(),
"sanity": job_progress.get() # Coincidence?
})
# Black magic wiring
eff= Effect(howl)
eff.schedule()
try:
while True:
await asyncio.sleep(1) # Eternal damnation
finally:
active_connections.remove(websocket) # Sweet release
# === Pull the Lever, Kronk! ===
if __name__ == "__main__":
config = uvicorn.Config(app, port=8000, log_level="info")
server = uvicorn.Server(config)
asyncio.run(server.serve())
Why This Doesn’t Suck
1. Real-Time Voodoo
Change job_status
→ WebSockets auto-scream → Users actually know what’s happening.
No more “90% complete” for 3 hours (unless you want to prank users).
2. User Control That Doesn’t Lie
/pause
injects commands via Signals → No more “I swear I clicked stop!”
Signals handle concurrency better than my therapist handles my anxiety.
3. Observability Without the PhD
Add monitoring in 2 lines:
# Log every status change
eff = Effect(lambda: print(f"[SPY] Status: {job_status.get()}"))
eff.schedule()
4. Scales Like a Vampire Army
Handle 10k connections like it’s a slow Tuesday. No polling. No wasted CPU cycles.
How to Torture-Test This
- Start the service (requires Python 3.10+):
pip install reaktiv fastapi uvicorn websockets
python job_horror.py
- Start a job:
curl -X POST http://localhost:8000/start
- Watch the existential dread:
websocat ws://localhost:8000/ws
Output:
{"status": "running", "progress": 54, "sanity": 54}
{"status": "running", "progress": 55, "sanity": 55}
- Trigger mid-life crisis:
curl -X POST http://localhost:8000/pause
The Future Is Reactive (And Slightly Unhinged)
reaktiv
isn’t just another library—it’s your ticket to building backends that feel alive. No more:
- Manual state juggling ๐คน
- Ghost updates ๐ป
- Race conditions ๐️๐จ
These patterns can be used to build:
- CI/CD Pipelines that pause when your tests start crying
- ML Training dashboards where you tweak params live
- Payment Systems that auto-pause during fraud detection
So next time you’re:
- Writing yet another polling loop ๐
- Debugging zombie threads ๐ง
- Explaining why “pause” didn’t pause ๐
Remember—your backend doesn’t have to be a horror movie.
Unless you want it to be. ๐
Get Started:
pip install reaktiv
GitHub: https://github.com/buiapp/reaktiv
Your backend’s afterlife begins today.
Tidak ada komentar:
Posting Komentar