|
| 1 | +# YouTube Video Script: Introducing Ganesha — a PHP Circuit Breaker library |
| 2 | + |
| 3 | +**Channel**: [Hand-Rolled Code](channel.md) |
| 4 | +**Project**: [Ganesha](https://github.com/ackintosh/ganesha) — PHP Circuit Breaker library |
| 5 | +**Task**: Introduce the circuit breaker pattern and Ganesha's basic API |
| 6 | +**Estimated duration**: 12–18 minutes |
| 7 | + |
| 8 | +--- |
| 9 | + |
| 10 | +## Scene 1 — Channel intro (camera on screen, terminal open) |
| 11 | + |
| 12 | +> "Hey everyone, welcome to Hand-Rolled Code. This channel is about coding by hand — writing real software with my own hands, no AI autocomplete, no generated boilerplate. Just me and a keyboard. I'm planning to post sessions like this regularly, so if that sounds interesting, feel free to subscribe. |
| 13 | +> |
| 14 | +> Today I want to introduce a PHP library called **Ganesha** — a circuit breaker implementation I've been maintaining. We're going to look at what the circuit breaker pattern is, why it's useful, and how Ganesha implements it. I'll also walk through the basic API so you can start using it in your own projects." |
| 15 | +
|
| 16 | +--- |
| 17 | + |
| 18 | +## Scene 2 — What is the circuit breaker pattern? |
| 19 | + |
| 20 | +> "Let me start with the concept, because the code makes a lot more sense once you understand what problem we're solving. |
| 21 | +> |
| 22 | +> Imagine you have a web application that calls an external payment API. If that API is slow or down, your app starts accumulating requests waiting for a response. Those requests pile up, consuming threads and memory. Eventually your entire application grinds to a halt — not because of your own code, but because of a dependency that's failing. This is called a **cascading failure**. |
| 23 | +> |
| 24 | +> The **circuit breaker pattern** is a way to prevent this. It works just like the electrical circuit breaker in your home. When something goes wrong — too many failures — the circuit 'trips' and you stop sending requests to the broken service. Instead of waiting for a timeout every time, you fail fast and return an error immediately. After some time, you let a test request through to see if the service has recovered. |
| 25 | +> |
| 26 | +> There are three states: |
| 27 | +> - **Closed** — everything is normal, requests go through |
| 28 | +> - **Open** — failures exceeded the threshold, requests are blocked |
| 29 | +> - **Half-Open** — a trial period, one request is allowed through to test recovery |
| 30 | +
|
| 31 | +*Optionally show the diagram image from the README.* |
| 32 | + |
| 33 | +> "Ganesha implements this pattern in PHP." |
| 34 | +
|
| 35 | +--- |
| 36 | + |
| 37 | +## Scene 3 — Installing Ganesha |
| 38 | + |
| 39 | +*Open terminal.* |
| 40 | + |
| 41 | +> "Let me install it. Ganesha is on Packagist, so it's just a Composer command." |
| 42 | +
|
| 43 | +```bash |
| 44 | +composer require ackintosh/ganesha |
| 45 | +``` |
| 46 | + |
| 47 | +> "That's it. No extra extensions required for the basic setup." |
| 48 | +
|
| 49 | +--- |
| 50 | + |
| 51 | +## Scene 4 — Building Ganesha with the Count Strategy |
| 52 | + |
| 53 | +*Open a new PHP file, for example `demo.php`.* |
| 54 | + |
| 55 | +> "Ganesha provides two strategies for detecting failures. Let's start with the simpler one: the **Count strategy**. It trips the circuit when the number of failures reaches a threshold. |
| 56 | +> |
| 57 | +> Here's how you build a Ganesha instance." |
| 58 | +
|
| 59 | +```php |
| 60 | +<?php |
| 61 | +require 'vendor/autoload.php'; |
| 62 | + |
| 63 | +$redis = new Redis(); |
| 64 | +$redis->connect('localhost'); |
| 65 | + |
| 66 | +$ganesha = Ackintosh\Ganesha\Builder::withCountStrategy() |
| 67 | + ->adapter(new Ackintosh\Ganesha\Storage\Adapter\Redis($redis)) |
| 68 | + ->failureCountThreshold(3) |
| 69 | + ->intervalToHalfOpen(10) |
| 70 | + ->build(); |
| 71 | +``` |
| 72 | + |
| 73 | +> "Let me walk through the options: |
| 74 | +> |
| 75 | +> - `failureCountThreshold(3)` — the circuit trips after 3 consecutive failures |
| 76 | +> - `intervalToHalfOpen(10)` — 10 seconds after tripping, Ganesha allows one trial request through |
| 77 | +> |
| 78 | +> The adapter is how Ganesha persists its state. Here I'm using Redis. Ganesha supports several adapters — Redis, Memcached, APCu, and MongoDB." |
| 79 | +
|
| 80 | +--- |
| 81 | + |
| 82 | +## Scene 5 — The basic API: `isAvailable()`, `success()`, `failure()` |
| 83 | + |
| 84 | +> "Ganesha's API is deliberately minimal. There are three methods you need to know." |
| 85 | +
|
| 86 | +```php |
| 87 | +$service = 'payment-api'; |
| 88 | + |
| 89 | +if (!$ganesha->isAvailable($service)) { |
| 90 | + // fail fast — don't even try the request |
| 91 | + throw new RuntimeException('Payment API is not available'); |
| 92 | +} |
| 93 | + |
| 94 | +try { |
| 95 | + // make the actual request |
| 96 | + $result = callPaymentApi(); |
| 97 | + $ganesha->success($service); |
| 98 | +} catch (RuntimeException $e) { |
| 99 | + $ganesha->failure($service); |
| 100 | + throw $e; |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +> "The `$service` string is just a name — it's how Ganesha tracks state per service. You can have as many services as you like, each with its own circuit state. |
| 105 | +> |
| 106 | +> - `isAvailable()` — returns `true` if the circuit is closed, `false` if it's open |
| 107 | +> - `success()` — tell Ganesha the request succeeded |
| 108 | +> - `failure()` — tell Ganesha the request failed |
| 109 | +> |
| 110 | +> That's the entire integration. You wrap your existing call with an `isAvailable()` check and record the outcome." |
| 111 | +
|
| 112 | +--- |
| 113 | + |
| 114 | +## Scene 6 — Watching the circuit trip |
| 115 | + |
| 116 | +> "Let me show what actually happens when failures accumulate." |
| 117 | +
|
| 118 | +```php |
| 119 | +var_dump($ganesha->isAvailable($service)); // bool(true) |
| 120 | + |
| 121 | +$ganesha->failure($service); |
| 122 | +$ganesha->failure($service); |
| 123 | +$ganesha->failure($service); // 3rd failure — threshold reached |
| 124 | + |
| 125 | +var_dump($ganesha->isAvailable($service)); // bool(false) |
| 126 | +``` |
| 127 | + |
| 128 | +> "After the third failure, `isAvailable()` returns false. The circuit is open. Any further calls are blocked immediately — no waiting for timeouts, no wasted resources." |
| 129 | +
|
| 130 | +--- |
| 131 | + |
| 132 | +## Scene 7 — Subscribing to events |
| 133 | + |
| 134 | +> "Ganesha also publishes events when the circuit state changes. This is useful for logging or alerting." |
| 135 | +
|
| 136 | +```php |
| 137 | +$ganesha->subscribe(function (string $event, string $service, string $message): void { |
| 138 | + error_log(sprintf('[Ganesha] %s: %s', $event, $service)); |
| 139 | +}); |
| 140 | +``` |
| 141 | + |
| 142 | +> "There are three events: |
| 143 | +> - `EVENT_TRIPPED` — the circuit just opened |
| 144 | +> - `EVENT_CALMED_DOWN` — the circuit recovered and closed again |
| 145 | +> - `EVENT_STORAGE_ERROR` — the storage backend had a problem |
| 146 | +> |
| 147 | +> Notice that storage errors are handled gracefully — if Redis goes down, Ganesha defaults to returning `true` from `isAvailable()` rather than crashing your application. It fails open, which is usually the right default for a circuit breaker." |
| 148 | +
|
| 149 | +--- |
| 150 | + |
| 151 | +## Scene 8 — Brief mention of the Rate Strategy |
| 152 | + |
| 153 | +> "I mentioned there's a second strategy — the **Rate strategy**. Instead of counting raw failures, it tracks the failure rate as a percentage over a sliding time window. This is better for high-traffic services where a fixed count doesn't scale well. |
| 154 | +
|
| 155 | +```php |
| 156 | +$ganesha = Ackintosh\Ganesha\Builder::withRateStrategy() |
| 157 | + ->adapter(new Ackintosh\Ganesha\Storage\Adapter\Redis($redis)) |
| 158 | + ->failureRateThreshold(50) // trip if 50% of requests fail |
| 159 | + ->minimumRequests(10) // but only after at least 10 requests |
| 160 | + ->timeWindow(30) // measured over a 30-second window |
| 161 | + ->intervalToHalfOpen(10) |
| 162 | + ->build(); |
| 163 | +``` |
| 164 | + |
| 165 | +> "We won't go deeper into this today, but the API is identical — same three methods, same event system." |
| 166 | +
|
| 167 | +--- |
| 168 | + |
| 169 | +## Scene 9 — Tease for Episode 2 |
| 170 | + |
| 171 | +*Open [src/Ganesha/Storage/Adapter/Redis.php](../src/Ganesha/Storage/Adapter/Redis.php), navigate to the `reset()` method.* |
| 172 | + |
| 173 | +> "Before I wrap up — let me show you something I found in the codebase." |
| 174 | +
|
| 175 | +*Scroll to the `reset()` method's TODO comment.* |
| 176 | + |
| 177 | +> "There's a TODO here. The `reset()` method — which is supposed to clear all circuit breaker state — is not implemented for the Redis adapter. If you call `$ganesha->reset()` right now with Redis, nothing happens. |
| 178 | +> |
| 179 | +> In the next video, I'm going to fix this. We'll look at how Redis is storing Ganesha's data, figure out the right approach to delete it all safely, write the implementation, and test it. |
| 180 | +> |
| 181 | +> That's it for today. If you found this useful, give it a thumbs up. See you next time." |
| 182 | +
|
| 183 | +--- |
| 184 | + |
| 185 | +## Notes for Recording |
| 186 | + |
| 187 | +- **Editor**: VS Code with the file tree visible on the left sidebar |
| 188 | +- **Font size**: Increase to at least 18pt for readability on video |
| 189 | +- **Terminal**: Keep it in the same window, use a split pane if possible |
| 190 | +- **Pace**: Pause after each code block to give viewers time to read |
| 191 | +- **For the demo**: Start Docker before recording — `docker-compose up` to have Redis running |
| 192 | +- **Pronunciation tips**: |
| 193 | + - "circuit breaker" → SIR-kit BRAY-ker |
| 194 | + - "cascading" → kas-KAY-ding |
| 195 | + - "threshold" → THRESH-old |
| 196 | + - "Packagist" → PACK-uh-jist |
| 197 | + - "Composer" → kom-POH-zer |
0 commit comments