├── README.md ├── go.mod └── pkg ├── breaker ├── README.md ├── breaker.go └── breaker_test.go └── retry ├── README.md └── retry.go /README.md: -------------------------------------------------------------------------------- 1 | # Cloud Native 2 | 3 | Tulisan ini akan mengulas hasil pembelajaran dari beberapa sumber [#learn-from-books](#learn-from-books). 4 | Dan terinspirasi dari obrolan [The Pursuit of Production-Ready Software: Best Practices and Principles](https://www.youtube.com/watch?v=DR0yj7yEDdw) untuk mengulik lebih jauh. 5 | 6 | Semua pembahasan tersebut mengerucut ke system [Dependability](https://en.wikipedia.org/wiki/Dependability). 7 | Konsep Dependability pertamakali didefinisikan oleh [Jean-Claude Laprie](https://ieeexplore.ieee.org/document/532603) sekitar 35 tahun yang lalu. 8 | Jadi ini bukanlah konsep yang baru. 9 | 10 | Mengutip dari gambar di [wikipedia](https://en.wikipedia.org/wiki/Dependability). Komponen dependability seperti ini: 11 | 12 | * Dependability: 13 | - Attributes 14 | - Availability 15 | - Reliability 16 | - Maintainability 17 | - Safety 18 | - Confidentiality 19 | - Integrity 20 | - Threats 21 | - Faults 22 | - Errors 23 | - Failures 24 | - Means 25 | - Prevention 26 | - Tolerance 27 | - Removal 28 | - Forecasting 29 | 30 | - Availability 31 | 32 | Kemampuan system berkerja dalam suatu waktu secara acak. Biasanya diekspresikan dari berapa banyak request yang diterima oleh system. 33 | Uptime dibagi dengan total time. 34 | 35 | - Reliability 36 | 37 | Kemampuan system berkerja dalam interval waktu. Biasanya diekspresikan dengan mean time beetwen failures (MTBF: total time dibagi denan total failures) atau failure rate (number or failures dibagi total time). 38 | 39 | - Maintainability 40 | 41 | Kemampuan suatu sistem untuk mengalami modifikasi dan perbaikan. Ada beberapa jenis untuk mengukur maintainability. 42 | Mulai dari perhitungan cyclomatic complexity hingga pelacakan total waktu yang diperlukan untuk melakukan perbaikan system 43 | atau mengembalikan ke status sebelumnya. 44 | 45 | Ada 4 kategori teknik yang dapat digunakan untuk mengimprove systems depentability: 46 | 47 | - Fault prevention (Scalability / Loose Coupling) 48 | 49 | Digunakan selama pembuatan system untuk mencegah terjadinya kesalahan. 50 | 51 | - Fault tolerance (Resilence / Loose Coupling) 52 | 53 | Digunakan selama mendesign system dan implementasi untuk mencegah kegagalan karena adanya kesalahan. 54 | 55 | - Fault removal (Manageability) 56 | 57 | Digunakan untuk mengurangi jumlah dan tingkat keparahan(severity) kesalahan. 58 | 59 | - Fault forecasting (Observability) 60 | 61 | Digunakan untuk menidentifikasi keberadaan, penciptaan dan kosekuensi dari kesalahan. 62 | 63 | ### Fault Prevention 64 | 65 | Fault Prevention menjadi dasar dari semuanya. Kenapa? karena disini adalah tahap pembuatan system. 66 | Lalu apa saja yang harus dilakukan: 67 | 68 | - Good Programming Practice 69 | - Pair Programming 70 | - Test-Driven Development 71 | - Code Review Practice 72 | - Language features 73 | Pemilihan bahasa cukup berpengaruh. Setiap bahasa punya paradigma dan karakteristik masing masing. Bisa baca dibuku [Clean Architechture - PARADIGM OVERVIEW](https://learning.oreilly.com/library/view/clean-architecture-a/9780134494272/ch3.xhtml#toclev_13) 74 | - Scalability 75 | Kemampuan dari suatu sistem untuk terus memberikan layanan dalam menghadapai perubahan sesuai dengan permintaan. 76 | - Loose Couplling 77 | Artinya setiap sistem saling terhubung tetapi atar service hanya tau interfacenya saja. 78 | 79 | ### Fault Tolrerance 80 | 81 | Setelah Fault Prevention, maka masuk ke Fault Tolerance. 82 | 83 | Ada beberapa nama lain seperti self-repair, self-healing, resillience. 84 | Semuanya menggamarkan kemampuan sistem untuk mendeteksi kesalahan dan mencegah menjadi kesalahan yang lebih besar. 85 | Biasanya terdiri dari 2 bagian, *error detection* dan *recovery* 86 | 87 | ### Fault Removal 88 | 89 | Selanjutnya adalah Fault Removal adakag proses mengurangi jumlah dan tingkat keparahan (severity) kesalahan. 90 | Bahkan dikondisi ideal pun ada banyak cara yang membuat sistem melakukan kesalahan atau berperilaku tidak semestinya. 91 | Mungkin gagal melakuka tindakan yang diharapkan atau melakukan tindakan yang salah. 92 | 93 | Banyak kesalahan bisa diindentifikasi melalui testing yang memungkinkan untuk kita menverifikasi sistem yang kita buat. 94 | 95 | #### Verification and Testing 96 | 97 | Cuma ada satu cara buat menemukan kesalahan pada software. yaitu testing. 98 | Kalau bukan kita yang nemuin ya user yang menggunakan sistem kita yang mengerikan jika usernya jahat. 99 | Bisa berbahaya jika dia mengambil keuntungan dari kesalahan tersebut. 100 | 101 | Ada 2 pendekatan: 102 | 103 | 1. Static Analisis 104 | Berguna untuk memberikan feedback diawal, memaksa untuk melakukan praktek yang konsisten dan menemukan error yang umum. 105 | Tanpa perlu bantuan manusia. 106 | Biasanya bisa menggunakan tools seperti , codeclimate, dll 107 | 2. Dynamic Analisis 108 | Kalau ini testing yang memerlukan manusia. 109 | 110 | Kunci dari software testing adalah membuat software yang `designed for testability`. 111 | function yang teatabilitynya tinggi adalah function yang memiliki tujuan tunggal dengan input dan output yang terdefinisi dengan baik atau sedikit efek samping. 112 | 113 | #### Manageability 114 | 115 | Kesalahan pada sistem ada karena sistem tidak berkerja sesuai requirement. 116 | Dengan `Designing for manageability` mengizinkan perilaku system diubah tanpa ada perubahan dicode. 117 | Manageable system pada dasarnya seperti sistem yang memiliki `knob` yang memungkinkan kontrol secara real-time untuk menjaga sistem kita tetap aman, bejalan lancar dan sesuai dengan requirement. Misalnya seperti feature flags yang bisa menyalakan atau mematikan fitur atau loading plug-in yang mengubah perilaku. 118 | 119 | ### Fault Forecasting 120 | 121 | Ini adalah tahap terakhir, tahap ini dibangun berdasarkan pengetahuan dari kejadian yang ada dan kumpulan dari solusi solusi yang diterapkan. 122 | Biasanya cuma menebak nebak saja. Tapi dengan membuat system `design for observability` indikator kesalahan bisa ditrack jadi kita bisa memprediksi dengan tepat sebelum berubah menjadi error. 123 | 124 | ## Twelve-Factor App 125 | 126 | Jadi sekitar tahun 2010an, para developer dari heroku menyadari sesuatu karena seringnya mereka melihat development web memiliki masalah yang sama. 127 | Lalu mereka menyusun [The Twelve-Factor App](https://12factor.net/), ini adalah kumpulan aturan main dan panduan yang merupakan metedologi pengembangan web. 128 | Metedologinya sebagai berikut: 129 | 130 | - Gunakan deklaratif format untuk setup automation, guna membantu developer baru yang join project. 131 | - Memiliki `clean contract` pada sistem operasi yang digunakan. 132 | - Cocok digunakan pada cloud platform modern sehingga tidak memerlukan server dan sysadmin. 133 | - Meminimalisir perbedaan antara environment development dan production. 134 | - Dapat `scale up` tanpa perubahan yang signifikan pada tools, architechture atau development practice. 135 | 136 | Jadi apa saja isi dari cloud native: 137 | 138 | 1. Codebase 139 | 140 | > One codebase tracked in revision control, many deploys. 141 | 142 | Satu codebase untuk segala environtment, biasanya development, staging dan production. 143 | 144 | 145 | 2. Dependencies 146 | 147 | > Explicitly declare and isolate (code) dependencies. 148 | 149 | TODO: penjelasan 150 | 151 | 3. Configuration 152 | 153 | > Store configuration in the environment. 154 | 155 | Konfigurasi untuk setiap environment haruslah terpisah dari code, Jangan sampai ada konfigurasi yang dimasukan kedalam code. 156 | Contoh konfigurasi yang tidak boleh dimasukan kedalam code. 157 | 158 | - URL/DSN database atau apapun yang menjadi dependensi ke service kita. 159 | - Segala jenis secret, seperti password atau credential external service. 160 | - environment value, seperti hotname untuk deploy. 161 | 162 | Umumnya konfigurasi diletakan diYAML file dan tidak dimasukan kedalam repository. Tapi ini kurang ideal. Kenapa? 163 | Pertama, bisa jadi tidak sengaja masuk kedalam repository. Lalu, bisa terjadi miskonfigurasi karena lupa melakukan perubahan diproduction. 164 | 165 | Jadi, daripada membuat konfigurasi sebagai code atau sebagai konfigurasi external. 166 | The Twelve-Factor App merekomendasikan agar konfigurasi diletakan sebagai *environment variables*. 167 | 168 | Keuntungan menggunakan *environment variables*: 169 | - Menjadi standard disemua OS dan language agnostic 170 | - Mudah dideploy tanpa melakukan perubahan disisi code 171 | - Mudah untuk diinject kedalam container 172 | 173 | 4. Backing Services 174 | 175 | > Treat backing services as attached resources. 176 | 177 | Backing service seperti datastore, messaging system, SMTP, dll harus diperlakukan sebagai seuatu yang mudah digantikan. 178 | Jadi perubahan disetiap environment hanya perlu ubah disisi *environment variables*. 179 | 180 | 5. Build, Release, Run 181 | 182 | > Strictly separate build and run stages. 183 | 184 | Pada saat deployment, biasanya ada tiga tahap terpisah. 185 | 186 | - Build 187 | Tahap ini biasanya proses automate dalam mengambil code dari spesifik versi, mengambil dependensi dan mencompile menjadi executeable artifact. 188 | Setiap build biasanya dilengkapi dengan identifier yang unik seperti timestamp atau urutan angka build. 189 | - Release 190 | Ditahap ini, setelah code dibuild lalu dimasukan konfigurasi yang menuju ke spesifik target deployment(develeopment, staging, production) 191 | - Run 192 | Tahap ini, proses menjalankan ke target. 193 | 194 | 6. Processes 195 | 196 | > Execute the app as one or more stateless processes. 197 | 198 | Service harus stateless dan tidak membagi apapun, data harus disimpan di datastore bukan diaplikasi. 199 | 200 | 7. Data Isolation 201 | 202 | > Each service manages its own data. 203 | 204 | Setiap service seharusnya memiliki data sendiri dan data hanya bisa diakses melalu API yang sudah didesign. 205 | 206 | 8. Scalability 207 | 208 | > Scale out via the process model. 209 | 210 | Sistem dapat dianggap "scalable" jika kita bisa menambah atau mengurangi kekuatan service sesuai kebutuhan dengan mudah. 211 | 212 | Scaling ada 2 bentuk, yaitu: 213 | 214 | - Vertical Scaling (scale up) 215 | 216 | Menambah resource server / phisical resource. Dijaman cloud computing saat ini sudah cukup mudah untuk menambah resource. 217 | 218 | - Horizontal Scaling 219 | 220 | Menduplikasi system atau service untuk membatasi beban disatu server. 221 | Tapi aplikasi kita haruslah `stateless` karena akan sulit mekalukan scaling jika `state`. 222 | 223 | Apa yang menyebabkan system harus discale? 224 | 225 | Penyebabnya biasanya karena adanya kemacetan "Bottleneck". 226 | Solusi gampangnya ya menaikan resource komponen yang bottleneck (Vertical Scaling). 227 | Tapi cara ini gak akan bisa digunakan selamanya, Resource yang dinaikan pasti ada batasnya seperti batas teknologi dan batas keuangan. 228 | Makin tinggi resource yang digunakan pastinya akan semakin mahal. 229 | 230 | Biasanya apa saja yang komponen-komponen yang bisa bottleneck? 231 | 232 | - CPU 233 | - Memory 234 | - Disk I/O 235 | - Network I/O 236 | 237 | Apa itu stateful dan stateless? 238 | 239 | Stateful itu aplikasi yang service dan datanya disimpan didalam satu container atau lokal. 240 | Aplikasi seperti ini sulit untuk direplica karena datanya juga pasti akan tereplika menjadi 2 sistem berbeda. 241 | 242 | Stateless sebaliknya, sevice dan data terpisah. Sehingga kita bisa menduplikasi service dengan mudah, karena data terpusat diluar dari service aplikasi. 243 | 244 | Keuntungan membuat aplikasi stateless: 245 | 246 | - Scalability 247 | 248 | System akan lebih mudah untuk ditambah atau dikurangi. Karena tidak ada data yang tersimpan didalam service. 249 | Semua data sudah terpusat ditempat lain. 250 | 251 | - Durability 252 | 253 | Karena data sudah terpusat ditempat lain, kita tidak akan kehilangan data ketika service ditambah atau dikurangi. 254 | 255 | - Simplicity 256 | 257 | Tidak perlu ada data yang disync, bisa dengan cepat mereplika system 258 | 259 | - Cacheability 260 | 261 | APIs yang didesign dengan stateless service relatif mudah untuk dicache. 262 | 263 | 264 | Selain itu untuk mencapai sistem yang skalable, kita perlu belajar tentang service architechture. 265 | 266 | Dalam pengembangan sistem biasanya dimulai dengan [The Monolith System Architecture](https://microservices.io/patterns/monolithic.html). 267 | Dimana satu service menjalankan banyak proses secara bersamaan. Banyak fungsi-fungsi didalamnya digabungkan dalam 1 database. 268 | Pada prosesnya monolith system lebih sederhana membuatnya tapi seiring berjalannya waktu akan sulit untuk dimanage dan discale. 269 | 270 | Setelah monolith biasanya system akan berkembang ke [The Microservices System Architecture](https://microservices.io/patterns/microservices.html). 271 | Dimana system akan dipecah pecah sesuai dengan domainnya. Ada pendapat dari Martin Fowler harus [monolith first](https://martinfowler.com/bliki/MonolithFirst.html). Alasannya masuk akal, "Kalau kita gak bisa bikin system monolith yang benar, bagaimana cara kita bikin microservice". 272 | Tapi yang menarik ada bantahan dari [Stevan Tilkov](https://www.innoq.com/blog/st/) - [Don't Start with Monolith](https://martinfowler.com/articles/dont-start-monolith.html). Keduanya menarik untuk dipelajari. 273 | 274 | Lalu muncul perkembangan lagi yang perlu kita pelajari, yaitu [Serverless Architecture](https://martinfowler.com/articles/serverless.html). 275 | Mulai ramai setelah kemunculan AWS Lambda lalu dikuti cloud provider lain seperti GPC Cloud Function yang berlomba-lomba dalam membuat serverless provder. 276 | 277 | Serverless bukannya tanpa server, tetap ada server tapi kita tidak perlu repot-repot mengkonfigurasi disisi server. 278 | Kalau microservice kita perlu memcah system sesuai domain dengan cara serverless kita bisa pecah hingga level function. 279 | Karena itu dikenal dengan FaasS (Function as a Service). 280 | 281 | Diluar dari cloud provider ada juga beberapa project open source yang memungkinkan kita membuat serverless sendiri, seperti [Knative](https://knative.dev/docs/), [OpenFaas](https://www.openfaas.com/), [Kubeless](https://kubeless.io/), dll 282 | 283 | Kelebihan dan Kekurangan menggunakan Serverless Architechture: 284 | 285 | - Kelebihan 286 | 287 | - Operational Management 288 | - Scalability 289 | - Reduce Cost 290 | - Productivity 291 | 292 | - Kekurangan 293 | 294 | - Vendor Lock-In 295 | 296 | Kalau pakai AWS Lambda atau Cloud Function. 297 | Kalo pakai knative, openfass, dll mestinya gak vendor lock-in tapi tetap ada configurasi diawal. 298 | 299 | - Startup latency 300 | 301 | Atau istilah lainnya cold start, karena biasanya kita akan sering mematikan dan menyalakan. 302 | Ketika service tidak dihit, maka server akan idle dan tidak ada resource yang perlu dibayar. 303 | Tapi buruknya akan ada jeda ketika server mulai berkerja lagi. 304 | 305 | - Tidak cocok untuk task yang lama. 306 | 307 | Serverless diperuntukan menjalankan process yang cepat, hit, process, stop. 308 | Kalo dipaksa untuk menjalankan process yang lama bisa saja, tapi hitungannya menjadi lebih mahal. 309 | 310 | - Complexity 311 | 312 | Karena biasanya bentuknya function, jika tidak didokumentasikan dengan baik akan susah sendiri nantinya. 313 | Karena akan lebih banyak function yang dideploy dan tidak terdokumentasi. 314 | 315 | 316 | Sumber Belajar Lain: 317 | 318 | - [Serverless Guide](https://github.com/serverless/guide/blob/master/ebook/dist/guide.pdf) 319 | - [OpenFaaS: From Zero to Serverless in 60 Seconds Anywhere with Alex Ellis](https://www.youtube.com/watch?v=C3agSKv2s_w) 320 | - [What is Knative?](https://www.youtube.com/watch?v=69OfdJ5BIzs) 321 | 322 | 323 | 324 | 325 | 326 | 9. Disposability 327 | 328 | > Maximize robustness with fast startup and graceful shutdown. 329 | 330 | - Service harus meminimalkan waktu untuk `start up` 331 | - Service harus bisa shutting down ketika menerima sinya `SIGTERM`. Stop request yang masuk, Selesaikan semua proses dan tutup semua koneksi. 332 | 333 | Baca juga, [belajar gracefull shutdown](https://github.com/zeihanaulia/go-learn-gracefull-shutdown). 334 | 335 | 10. Development/Production Parity 336 | 337 | > Keep development, staging, and production as similar as possible. 338 | 339 | Perbedaan antara development dan production harus semirip mungkin. 340 | 341 | - Code divergence 342 | Branch development harus kecil dan short-lived, harus segera ditest dan dideploy ke production ASAP. 343 | - Stack divergence 344 | Stack yang digunakan untuk menjalankan service harus sama mulai dari jenis os, versi os, jenis datastore dan versinya, dll 345 | - Personnel divergence 346 | Libatkan pembuat code dalam proses deployment 347 | 348 | 349 | 11. Logs 350 | 351 | > Treat logs as event streams. 352 | 353 | TODO: penjelasan 354 | 355 | 12. Administrative Processes 356 | 357 | > Run administrative/management tasks as one-off processes. 358 | 359 | TODO: penjelasan 360 | 361 | 362 | ## Learn From Books: 363 | 364 | - https://microservices.io/ by Chris Richardson 365 | - [Cloud Native Go](https://learning.oreilly.com/library/view/cloud-native-go/9781492076322/) by [Matthew A. Titmus](https://www.linkedin.com/in/matthew-titmus/) 366 | - [Designing Data-Intensive Applications](https://learning.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/) by [Martin Kleppmann](https://martin.kleppmann.com/) 367 | - [Site Reliability Engineering](https://learning.oreilly.com/library/view/site-reliability-engineering/9781491929117/) by Betsy Beyer, Chris Jones, Niall Richard Murphy, Jennifer Petoff 368 | - [Clean Architecture: A Craftsman’s Guide to Software Structure and Design](https://learning.oreilly.com/library/view/clean-architecture-a/9780134494272/) By Robert C. Martin 369 | - [Cloud Native](https://learning.oreilly.com/library/view/cloud-native/9781492053811/) By Boris Scholl, Trent Swanson, Peter Jausovec 370 | 371 | ## Common Failure 372 | 373 | Asumsi-asumsi yang salah dalam distributed computing, menurut [L Peter Deutsch](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing), 374 | 375 | - The network is reliable: switches fail, routers get misconfigured 376 | - Latency is zero: it takes time to move data across a network 377 | - Bandwidth is infinite: a network can only handle so much data at a time 378 | - The network is secure: don’t share secrets in plain text; encrypt everything 379 | - Topology doesn’t change: servers and services come and go 380 | - There is one administrator: multiple admins lead to heterogeneous solutions 381 | - Transport cost is zero: moving data around costs time and money 382 | - The network is homogeneous: every network is (sometimes very) different 383 | - Services are reliable: services that you depend on can fail at any time 384 | 385 | Maka dari itu kita perlu belajar tentang stability pattern yang bisa digunakan. 386 | 387 | - Stability Pattern 388 | - Circuit Breaker 389 | - Retry 390 | - Debounce & Throttle 391 | - Timeout 392 | 393 | Sebenarnya beberapa hal diatas bisa tercover dengan tools-tools seperti [istio](https://istio.io/latest/docs/tasks/traffic-management/), [nginx](https://www.nginx.com/resources/library/api-traffic-management-101-monitoring-beyond/), dll. Tidak ada salahnya coba membuat dari sisi script jika tidak menggunakan tools tersebut. 394 | 395 | 396 | ## Stability Pattern 397 | 398 | ### Circuit Breaker 399 | 400 | Circuit Breaker (CB), secara otomatis akan memutus jika terjadi kesalahan secara terus menerus. 401 | Untuk mencegah terjadinya kegagalan yang lebih besar dan lebih banyak. 402 | CB digunakan untuk menangkap error dan jika sudah mencapai batasnya akan `open circuit` atau memtutus request karena terlalu banyak error. 403 | CB terinspirasi dari kelistrikan, setiap instalasi listrik biasanya dipasang circuit breaker, 404 | Ketika terjadi konsleting, circute akan terbuka dan mematikan aliran listrik kerumah. 405 | 406 | Sekarang bayangkan jika itu didalam system, Service yang kita buat akan mengquery ke database, lalu databasenya mati atau gagal meresponse. 407 | Jika terjadi cukup lama service kita akan dibanjiri dengan logs error yang sebetulnya tidak berguna. 408 | Ada baiknya jika kita stop saja semua request dari awal hingga database kembali berkerja. 409 | 410 | CB pada dasarnya hanyalah function yang berupa [Adapter Pattern](https://refactoring.guru/design-patterns/adapter) diantara request dan response ke service lain. Function `Breaker` akan membungkus Function `Circuit` untuk menambahkan error handling. 411 | Function `Breaker` memiliki status `open` dan `close`. 412 | Jika status `close` maka function akan berjalan secara normal dengan meneruskan request yang diterima. 413 | Sebaliknya jika status `open` maka function tidak akan meneruskan dan membuat service gagal lebih cepat. 414 | 415 | Dan biasanya akan ada logic auto `close` breaker, Untuk mengecek apakah service sudah berjalan dengan normal. 416 | 417 | Untuk implementasi bisa dilihat [Breaker](https://github.com/zeihanaulia/go-cloud-native-patterns/tree/main/pkg/breaker). 418 | 419 | Beberapa repository dan implementasi circuit breaker: 420 | 421 | - https://github.com/sony/gobreaker 422 | - https://github.com/streadway/handy 423 | - https://github.com/afex/hystrix-go 424 | - https://github.com/go-kit/kit/tree/master/circuitbreaker 425 | 426 | #### Reference 427 | 428 | * https://microservices.io/patterns/reliability/circuit-breaker.html 429 | 430 | ### Retry 431 | 432 | Retry adalah mekanisme pengulangan request ketika terjadi kegagalan ketika membuat request. 433 | Biasanya retry juga memiliki batas mengulang dan juga periode pengulangannya. 434 | 435 | Sama seperti CB, untuk membuat `Retry` juga menggunakan [Adapter Pattern](https://refactoring.guru/design-patterns/adapter). 436 | Function `Retry` akan membungkus `Requestor`, untuk mengandle error dari requestor. 437 | Lalu function `Retry` bisa mengkontrol berapa kali retry hingga akhirnya gagal dan juga delay setiap requestnya. 438 | 439 | Untuk implementasi bisa dilihat [Retry](https://github.com/zeihanaulia/go-cloud-native-patterns/tree/main/pkg/retry). 440 | 441 | Beberapa repository dan implementasi retry: 442 | 443 | - https://github.com/avast/retry-go 444 | - https://github.com/sethvargo/go-retry 445 | - https://github.com/go-kit/kit/blob/master/sd/lb/retry.go 446 | 447 | #### Reference 448 | 449 | * http://thinkmicroservices.com/blog/2019/retry-pattern.html 450 | 451 | ### Debounce && Throttle 452 | 453 | Kedua pattern ini kurang lebih sama, hanya saja cara kerjanya berbeda. 454 | 455 | Throttle menjaga input masuk dalam durasi tertentu, misalnya 100 request per menit. 456 | Debounce hanya menerima input yang sama dalam 1 kali,hanya diawal atau diakhir. 457 | 458 | #### Throttle 459 | 460 | Throttle membatasi jumlah function direquest. Contoh penggunaan: 461 | 462 | - User hanya boleh request dalam 10 kali per detik. 463 | - Membatasi jeda klik per 500 millisecond 464 | - Hanya boleh 3 kali gagal login dalam 24 jam 465 | 466 | Tapi biasanya kita menggunakan Throttle memperhitungkan lonjakan aktifitas yang dapat memenuhi system. 467 | Jika system tidak sanggup, maka akan terjadi kegagalan. 468 | 469 | #### Debounce 470 | 471 | Debounce membatasi jumlah function direquest. Contoh penggunaan: 472 | 473 | - User hanya boleh request dalam 10 kali per detik. 474 | - Membatasi jeda klik per 500 millisecond 475 | - Hanya boleh 3 kali gagal login dalam 24 jam 476 | 477 | Tapi biasanya kita menggunakan Throttle memperhitungkan lonjakan aktifitas yang dapat memenuhi system. 478 | Jika system tidak sanggup, maka akan terjadi kegagalan. 479 | 480 | 481 | 482 | ### Timeout 483 | 484 | Pada dasarnya timeout akan menghentikan proses dalam durasi waktu. 485 | Biasanya ketika ada masalah didalam service seperti query lambat atau konek ke service lain lambat. 486 | Sehingga proses berjalan menjadi lama. 487 | Agar tidak dalam proses terus menerus dan client mengunggu lama ada bagusnya kita kasih durasi akses. 488 | Bukan berarti proses lama itu pasti gagal, bisa jadi memang prosesnya perlu waktu sehingga harus dihandle secara asyc, 489 | bisa dibaca di [long process API](https://github.com/zeihanaulia/go-long-process-api) untuk melihat proof of concept dari handle api yang memproses lama. 490 | 491 | Untuk membuat timeout pada service kita hanya perlu memainkan context 492 | 493 | ```go 494 | ctx := context.Background() 495 | ctxt, cancel := context.WithTimeout(ctx, 10 * time.Second) 496 | defer cancel() 497 | 498 | result, err := SomeFunction(ctxt) 499 | ``` 500 | 501 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zeihanaulia/go-cloud-native-patterns 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /pkg/breaker/README.md: -------------------------------------------------------------------------------- 1 | # Breaker 2 | 3 | ## Implementation 4 | 5 | 1. Definisikan Circuit Type 6 | 7 | ```go 8 | type Circuit func(context.Context) (string, error) 9 | ``` 10 | 11 | 2. Buat function `Breaker` yang menerima circuit dan batas kegagalan. Lalu mengembalikan type `Circuit`. 12 | ```go 13 | func Breaker(circuit Circuit, failureThreshold uint) Circuit { 14 | return func(c context.Context) (string, error) { 15 | response, err := circuit(ctx) 16 | return response, err 17 | } 18 | } 19 | ``` 20 | 21 | 3. Definisikan variable untuk mencatat kegagalan secara berurutan dan waktu request terakhir 22 | ```go 23 | func Breaker(circuit Circuit, failureThreshold uint) Circuit { 24 | var consecutiveFailures int = 0 25 | var lastAttempt = time.Now() 26 | return func(c context.Context) (string, error) { 27 | response, err := circuit(ctx) 28 | lastAttempt = time.Now() 29 | if err != nil { 30 | consecutiveFailures++ 31 | return response, err 32 | } 33 | consecutiveFailures = 0 34 | return response, nil 35 | } 36 | } 37 | ``` 38 | 39 | - Lalu assign `lastAttempt` setelah function `response, err := circuit(ctx)` dijalankan 40 | - Setelah itu check tambahkan nilai `consecutiveFailures` jika error dan buat jadi `0` jika tidak error 41 | ```go 42 | if err != nil { 43 | consecutiveFailures++ 44 | return response, err 45 | } 46 | consecutiveFailures = 0 47 | ``` 48 | 49 | 4. Buat logic untuk membuka breaker jika error lebih dari batas 50 | ```go 51 | func Breaker(circuit Circuit, failureThreshold uint) Circuit { 52 | var consecutiveFailures int = 0 53 | var lastAttempt = time.Now() 54 | return func(c context.Context) (string, error) { 55 | d := consecutiveFailures - int(failureThreshold) 56 | if d >= 0 { 57 | shouldRetryAt := lastAttempt.Add(time.Second * 2 << d) 58 | if !time.Now().After(shouldRetryAt) { 59 | m.RUnlock() 60 | return "", errors.New("service unreachable") 61 | } 62 | } 63 | 64 | response, err := circuit(ctx) 65 | lastAttempt = time.Now() 66 | if err != nil { 67 | consecutiveFailures++ 68 | return response, err 69 | } 70 | consecutiveFailures = 0 71 | return response, nil 72 | } 73 | } 74 | ``` 75 | 76 | - Definisikan variable `d` = `consecutiveFailures - int(failureThreshold)` 77 | - Jika variable `d` lebih besar atau sama dengan 0 78 | - Maka check apakah waktu sekarang setelah nilai `shouldRetryAt`. 79 | - Jika tidak maka `open circuit` atau return error -------------------------------------------------------------------------------- /pkg/breaker/breaker.go: -------------------------------------------------------------------------------- 1 | package breaker 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "time" 7 | ) 8 | 9 | type Circuit func(context.Context) (string, error) 10 | 11 | func Breaker(circuit Circuit, failureThreshold uint) Circuit { 12 | var consecutiveFailures int = 0 13 | var lastAttempt = time.Now() 14 | return func(ctx context.Context) (string, error) { 15 | d := consecutiveFailures - int(failureThreshold) 16 | if d >= 0 { 17 | shouldRetryAt := lastAttempt.Add(time.Second * 2 << d) 18 | if !time.Now().After(shouldRetryAt) { 19 | return "", errors.New("service unreachable") 20 | } 21 | } 22 | 23 | response, err := circuit(ctx) 24 | lastAttempt = time.Now() 25 | if err != nil { 26 | consecutiveFailures++ 27 | return response, err 28 | } 29 | consecutiveFailures = 0 30 | 31 | return response, nil 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/breaker/breaker_test.go: -------------------------------------------------------------------------------- 1 | package breaker 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "math/rand" 8 | "strings" 9 | "sync" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func counter() Circuit { 15 | m := sync.Mutex{} 16 | count := 0 17 | 18 | // Service function. Fails after 5 tries. 19 | return func(ctx context.Context) (string, error) { 20 | m.Lock() 21 | count++ 22 | m.Unlock() 23 | 24 | return fmt.Sprintf("%d", count), nil 25 | } 26 | } 27 | 28 | // failAfter returns a function matching the Circuit type that returns an 29 | // error after its been called more than threshold times. 30 | func failAfter(threshold int) Circuit { 31 | count := 0 32 | 33 | // Service function. Fails after 5 tries. 34 | return func(ctx context.Context) (string, error) { 35 | count++ 36 | 37 | if count > threshold { 38 | return "", errors.New("INTENTIONAL FAIL!") 39 | } 40 | 41 | return "Success", nil 42 | } 43 | } 44 | 45 | func waitAndContinue() Circuit { 46 | return func(ctx context.Context) (string, error) { 47 | time.Sleep(time.Second) 48 | 49 | if rand.Int()%2 == 0 { 50 | return "success", nil 51 | } 52 | 53 | return "Failed", fmt.Errorf("forced failure") 54 | } 55 | } 56 | 57 | // TestFailAfter5 tests that the failAfter5 test function acts as expected. 58 | func TestCircuitBreakerFailAfter5(t *testing.T) { 59 | circuit := failAfter(5) 60 | ctx := context.Background() 61 | 62 | for count := 1; count <= 5; count++ { 63 | _, err := circuit(ctx) 64 | 65 | t.Logf("attempt %d: %v", count, err) 66 | 67 | switch { 68 | case count <= 5 && err != nil: 69 | t.Error("expected no error; got", err) 70 | case count > 5 && err == nil: 71 | t.Error("expected err; got none") 72 | } 73 | } 74 | } 75 | 76 | // TestBreaker tests that the Breaker function automatically closes and reopens. 77 | func TestCircuitBreaker(t *testing.T) { 78 | // Service function. Fails after 5 tries. 79 | circuit := failAfter(5) 80 | 81 | // A circuit breaker that opens after one failed attempt. 82 | breaker := Breaker(circuit, 1) 83 | 84 | ctx := context.Background() 85 | 86 | circuitOpen := false 87 | doesCircuitOpen := false 88 | doesCircuitReclose := false 89 | count := 0 90 | 91 | for range time.NewTicker(time.Second).C { 92 | _, err := breaker(ctx) 93 | 94 | if err != nil { 95 | // Does the circuit open? 96 | if strings.HasPrefix(err.Error(), "service unreachable") { 97 | if !circuitOpen { 98 | circuitOpen = true 99 | doesCircuitOpen = true 100 | 101 | t.Log("circuit has opened") 102 | } 103 | } else { 104 | // Does it close again? 105 | if circuitOpen { 106 | circuitOpen = false 107 | doesCircuitReclose = true 108 | 109 | t.Log("circuit has automatically closed") 110 | } 111 | } 112 | } else { 113 | t.Log("circuit closed and operational") 114 | } 115 | 116 | count++ 117 | if count >= 10 { 118 | break 119 | } 120 | } 121 | 122 | if !doesCircuitOpen { 123 | t.Error("circuit didn't appear to open") 124 | } 125 | 126 | if !doesCircuitReclose { 127 | t.Error("circuit didn't appear to close after time") 128 | } 129 | } 130 | 131 | // TestCircuitBreakerDataRace tests for data races. 132 | func TestCircuitBreakerDataRace(t *testing.T) { 133 | ctx := context.Background() 134 | 135 | circuit := waitAndContinue() 136 | breaker := Breaker(circuit, 1) 137 | 138 | wg := sync.WaitGroup{} 139 | 140 | for count := 1; count <= 20; count++ { 141 | wg.Add(1) 142 | 143 | go func(count int) { 144 | defer wg.Done() 145 | 146 | time.Sleep(50 * time.Millisecond) 147 | 148 | _, err := breaker(ctx) 149 | 150 | t.Logf("attempt %d: err=%v", count, err) 151 | }(count) 152 | } 153 | 154 | wg.Wait() 155 | } 156 | -------------------------------------------------------------------------------- /pkg/retry/README.md: -------------------------------------------------------------------------------- 1 | # Retry 2 | 3 | ## Implementation 4 | 5 | 1. Definisikan type requestor dan function Retry 6 | ```go 7 | type Requestor func(context.Context) (string, error) 8 | 9 | func Retry(requestor Requestor) Requestor { 10 | return func(c context.Context) (string, error) { 11 | response, err := requestor(c) 12 | return response, err 13 | } 14 | } 15 | ``` 16 | 17 | 2. Buat logic retry 18 | ```go 19 | type Requestor func(context.Context) (string, error) 20 | 21 | func Retry(requestor Requestor, retries int, delay time.Duration) Requestor { 22 | return func(c context.Context) (string, error) { 23 | for r := 0; ; r++ { 24 | response, err := requestor(c) 25 | if err == nil || r >= retries { 26 | return response, err 27 | } 28 | 29 | log.Printf("Attempt %d failed; retrying in %v", r + 1, delay) 30 | 31 | select { 32 | case <-time.After(delay): 33 | case <-ctx.Done(): 34 | return "", ctx.Err() 35 | } 36 | } 37 | } 38 | } 39 | ``` 40 | - Definidikan argument `retries int` dan `delay time.Duration` pada function `Retry` 41 | - Buat looping tanpa kondisi karena nanti akan di`break` dengan return 42 | - Jika requestor tidak error atau `r` lebih besar sama dengan `retries`, maka return response dan err 43 | - Jika error biarkan melooping. 44 | ```go 45 | log.Printf("Attempt %d failed; retrying in %v", r + 1, delay) 46 | select { 47 | case <-time.After(delay): 48 | case <-ctx.Done(): 49 | return "", ctx.Err() 50 | } 51 | ``` 52 | 53 | ## Example Usage 54 | 55 | ```go 56 | var count int 57 | 58 | func EmulateTransientError(ctx context.Context) (string, error) { 59 | count++ 60 | 61 | if count <= 3 { 62 | return "intentional fail", errors.New("error") 63 | } else { 64 | return "success", nil 65 | } 66 | } 67 | 68 | func main() { 69 | r := Retry(EmulateTransientError, 5, 2*time.Second) 70 | 71 | res, err := r(context.Background()) 72 | 73 | fmt.Println(res, err) 74 | } 75 | ``` -------------------------------------------------------------------------------- /pkg/retry/retry.go: -------------------------------------------------------------------------------- 1 | package retry 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "time" 7 | ) 8 | 9 | type Requestor func(context.Context) (string, error) 10 | 11 | func Retry(requestor Requestor, retries int, delay time.Duration) Requestor { 12 | return func(ctx context.Context) (string, error) { 13 | for r := 0; ; r++ { 14 | response, err := requestor(ctx) 15 | if err == nil || r >= retries { 16 | return response, err 17 | } 18 | 19 | log.Printf("Attempt %d failed; retrying in %v", r+1, delay) 20 | 21 | select { 22 | case <-time.After(delay): 23 | case <-ctx.Done(): 24 | return "", ctx.Err() 25 | } 26 | } 27 | } 28 | } 29 | --------------------------------------------------------------------------------