Bir worker production’da KeyError fırlatmaya başladı. Sebep kendi kodu değildi — yukarıdaki bir servis, olayın payload’ından bir alanı düşüren bir değişiklik shiplemişti ve kuyruk bozuk mesajı tek kelime etmeden taşımıştı. Hata, kötü mesaj değildi. Hata, onu sınırda kontrol eden hiçbir şeyin olmamasıydı.
Kuyruk byte taşır, tip değil
Bir request body tipli bir handler’a çarpar; yanlış şekildeyse framework onu kapıda reddeder. Kuyruk sana bunun hiçbirini vermez. Payload opak JSON’dur — broker byte taşır ve içine asla bakmaz. Üretici ne koyduysa, tüketici onu alır, geçerli olsun olmasın. Tek bir servisin içinde sahip olduğun tip güvenliği, veri telden geçtiği an buharlaşır.
Yani bir mesajın şekli hakkında bir garanti istiyorsan, onu kendin eklemelisin — kenarda, verinin girdiği ve çıktığı yerde.
Verinin girdiği ve çıktığı yerde doğrula
İki kenar var ve farklı hataları yakalarlar:
- Üretici tarafında, yayınlamadan önce — birincil olan. Payload’ı üretirken doğrula. Geçersiz veri kuyruğa hiç girmez, hata senkron olarak gerçekten hatası olan serviste ortaya çıkar ve bir kez yakalanır — mesaj her tüketiciye yayılmadan önce. Başarısız olmanın ucuz yeri burası.
- Tüketici tarafında, alırken — güvenlik ağı. Tüketirken yine doğrula. Bu, üretici tarafının yakalayamayacağını yakalar: kontrol etmediğin üreticilerden gelen mesajlar — başka bir takım, eski bir deploy edilmiş sürüm, elle hazırlanmış bir replay. Burada kalan mesaj zehirli bir mesajdır; handler’ı döngüde çökertmek yerine onu bir dead-letter kuyruğuna yönlendir.
Bilmeye değer bir incelik: çoğu kuyruk runtime’ında “anında reddet” kancası yoktur, yani tüketici-tarafı bir ret genelde dead-letter’a gitmeden önce retry eder — ve geçersiz veri retry’de geçerli olmaz. Bu boşa iştir; üretici tarafının değerli olmasının sebebi tam olarak budur. Tüketici tarafı emniyet kemeridir, direksiyon değil.
Şema envelope’la değil, mesajla yaşar
Doğruladığın şey, mesaj türü başına bir şemadır — olayın kimliğiyle anahtarlanır, taşıma envelope’una cıvatalanmaz. Onu bağımsız sürümlenen bir registry’de tut; tüketicileri kırmadan dikkatle evrimleştirdiğin aynı şema, runtime’da zorladığın şema olur. Tek sözleşme, hem değişim-anında (bu düzenleme birini kırıyor mu?) hem çalışma-anında (bu mesaj ona uyuyor mu?) kontrol edilir.
Bu, bir mükerrer teslimatı no-op yapmanın yapısal kuzeni: ikisi de ne geldiğini kontrol etmediğini kabul eder ve garantiyi göndericiye güvenmek yerine kendi sınırına koyar.
Ne zaman buna değmez
Tek bir servisin iç kuyruğu — tek üretici, tek tüketici, birlikte deploy — buna ihtiyaç duymaz. Tip sistemi zaten iki ucu da kapsar; kenar doğrulaması törendir. İkinci, bağımsız deploy edilen bir üretici ya da tüketici var olduğu an ödemeye başlar — farklı bir takım, farklı bir dil, farklı bir sürüm temposu. O an aynı zamanda tipsiz payload’ın sessizce en kırılgan sözleşmen hâline geldiği andır.
İlgili: BabelQueue schema-validation spec’i URN-başına şemayı ve nerede zorlandığını tanımlar; babelqueue-registry o şemaları tutar — kontrolü çalıştıran ise onun bqschema aracıdır.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.