Abonelik hataları durum hatalarıdır. Bir müşteri iptal ettikten sonra erişimini korur. Ödemesi gecikmiş bir hesap ödeme yapar ama kilitli kalır. Kartı olmayan birinin denemesi biter ve bundan sonra ne olacağını söyleyen bir kural yoktur. Bunların her biri, kimsenin çizmediği bir geçiştir.
Genellikle durum diyagramı denilen durum makinesi diyagramı, tek bir nesnenin içinde bulunabileceği her durumu ve onu başka bir duruma taşıyan her olayı listeler. Eksik geçişler destek taleplerinde değil, incelemede ortaya çıkar. Bu yazı bir SaaS aboneliği için bir durum diyagramı kuruyor; modeli gerçek bir sistemle karşılaştırabilmeniz için Stripe Billing'in belgelediği durumları kullanıyor ve notasyonu yeri geldikçe açıklıyor.
Ne zaman durum makinesi çizilir
Tek bir nesnenin kuralları olan bir yaşam döngüsü varsa bir tane çizin: bir sipariş, bir abonelik, bir ödeme, bir destek talebi, bir dağıtım. Uyarı işaretleri tanıdıktır:
- Üçten fazla değeri olan ve kodda birçok yerde kontrol edilen bir
statussütunu. - Birbiriyle çelişebilen
is_canceledveis_pausedgibi boolean bayraklar. - Belirli bir durumdan bir şeyin "olup olamayacağı" üzerine tartışmalar.
Birkaç katılımcı arasındaki bir konuşma için durum diyagramı yanlış resimdir; orada bir sekans diyagramı mesajların sırasını gösterir. Çok sayıda kişi ve devir teslim içeren bir iş süreci için de yanlış resimdir; oraya bir BPMN diyagramı daha iyi uyar.
Notasyon
| Öğe | Notasyon | Anlamı |
|---|---|---|
| Durum | Köşeleri yuvarlatılmış dikdörtgen | Nesnenin bir olay gelene kadar kaldığı koşul |
| Başlangıç sözde durumu | Küçük dolu daire | Makinenin başladığı yer |
| Son durum | Bir halkanın içinde dolu daire | Makine bitmiştir |
| Geçiş | event [guard] / action etiketli ok |
Olay gerçekleştiğinde ve koşul doğruysa geçilir |
| Seçim | Eşkenar dörtgen | Çıkan oklardaki koşullar hedefi belirler |
| Bileşik durum | Durumları içeren bir durum | İçindeki her şeyin paylaştığı kurallar |
| İç davranış | entry /, exit /, do / |
Girerken, çıkarken ya da durumdayken çalışır |
Bir geçiş etiketinin her parçası isteğe bağlıdır. invoice paid tek başına bir olaydır, [setting: unpaid] tek başına bir koşuldur ve cancel / revoke access bir eylemi olan bir olaydır. Sözdizimi ve semboller UML 2.5.1 spesifikasyonundan gelir.
Abonelik durumları
Örnek, otomatik kart ödemeleriyle Stripe'ın abonelik genel bakışındaki durumları kullanır:
| Durum | Anlamı | Erişim |
|---|---|---|
incomplete |
İlk ödeme henüz yapılmadı; müşterinin 23 saati var | Yok |
incomplete_expired |
İlk ödeme hiç başarılı olmadı; hiçbir şey faturalanmaz | Yok |
trialing |
Deneme süresinde | Var |
active |
Sorunsuz durumda | Var |
past_due |
Son faturanın ödemesi başarısız oldu ya da denenmedi | Sizin seçiminiz |
unpaid |
Yeniden denemeler tükendi ve ayarlarınız bu sonucu seçti | Yok |
paused |
Deneme ödeme yöntemi olmadan bitti ve duraklatma seçildi | Yok |
canceled |
Sona erdi; Stripe bunu son durum olarak tanımlar | Yok |
Birçok ürün, ödeme yeniden denemeleri sürerken past_due sırasında erişimi korur; bu örnek de öyle yapıyor. Bu kararı diyagrama yazın; insanların unuttuğu karar budur.
Geçişleri kurmak
Her durumu alın ve orada hangi olayların gerçekleşebileceğini sorun.
- Başlangıç.
subscribe [trial]trialing'e;subscribe [no trial]incomplete'e götürür. - İlk ödeme.
incomplete'teninvoice paidactive'e götürür. Ödeme olmadan 23 saat geçerse abonelikincomplete_expired'a geçer ve Stripe faturayı geçersiz kılar. Bu durum son durumdur: yeniden denemek için müşteri yeni bir abonelik başlatır. - Deneme bitişi.
trial endsbir seçime gider.[payment succeeds]active'e; bu örnekte[no payment method]paused'a götürür. Stripe bu durumda ne olacağını seçmenize izin verir: bir fatura oluşturmak (varsayılan), iptal etmek ya da duraklatmak. Üçünü birden değil, gerçekten kullandığınız ayarı çizin. - Yenilemeler.
active'tenrenewal payment failspast_due'ya götürür;invoice paidgeri getirir. - Tükenen denemeler. Bir seçim daha: ayarlarınıza bağlı olarak abonelik
unpaidya dacanceled'a geçer veyapast_due'da kalır.unpaid'den faturanın ödenmesi aboneliğiactive'e döndürür. - Çıkış.
cancel,active'icanceled'a taşır. Dönem sonunda iptal ayrı bir durum değildir: abonelik dönem bitene kadar bir bayraklaactivekalır, sonracanceled'a geçer.
Erişim için bileşik bir durum
Burada üç durum erişim verir: trialing, active ve past_due. Onları tek bir bileşik durumun, Access granted'ın içine çizmek kuralı bir kez yazmanızı sağlar: entry / grant access ve exit / revoke access. Bileşik durumdan çıkan her geçiş, hangi durumdan başlarsa başlasın erişimi kaldırır; böylece hiçbir yol bunu unutamaz. Bileşik durumun içindeki durumlar arasında geçmek, örneğin trialing'den active'e, onu terk etmez ve bu eylemleri çalıştırmaz.
Diyagram içeriği
- Access granted
- trialing
- active
- past_due
- paused
- incomplete
- incomplete_expired
- unpaid
- canceled
- subscribe [trial]
- subscribe [no trial]
- invoice paid
- after 23 h / void invoice
- trial ends
- [payment succeeds]
- [no payment method]
- resume [payment succeeds]
- renewal payment fails
- retries exhausted
- [setting: unpaid]
- [setting: cancel]
- cancel
Nasıl okunur:
- Üst. Başlangıç noktası denemeye göre ayrılır: bileşik durumun içindeki
trialingya da dışındakiincomplete.incomplete'ten ödemeactive'e, ödemesiz geçen 23 saat iseincomplete_expired'a ve sağdaki son duruma götürür. - Orta, Access granted.
trial endsbir seçime ulaşır: ödeme başarılı olursaactive, ödeme yöntemi yoksa soldakipaused. Başarısız bir yenilemeactive'ipast_due'ya taşır, ödeme de geri taşır. - Alt. Yeniden denemeler tükendiğinde ikinci bir seçim aboneliği
unpaidya dacanceled'a gönderir.unpaidhâlâ ödeme yaparakactive'e dönebilir;canceledson durumda biter. - Çerçeveye giren kenarlar erişim verir, çerçeveden çıkan kenarlar erişimi kaldırır.
Diyagramdan koda
Diyagram bir geçiş tablosuna karşılık gelir. Tabloda olmayan her şey reddedilir:
type Status = 'incomplete' | 'incomplete_expired' | 'trialing' | 'active'
| 'past_due' | 'unpaid' | 'paused' | 'canceled';
const transitions: Record<Status, Partial<Record<string, Status>>> = {
incomplete: { invoice_paid: 'active', expired: 'incomplete_expired' },
trialing: { trial_ended_paid: 'active', trial_ended_no_method: 'paused' },
active: { payment_failed: 'past_due', cancel: 'canceled' },
past_due: { invoice_paid: 'active', retries_exhausted: 'unpaid' },
unpaid: { invoice_paid: 'active' },
paused: { resumed: 'active' },
incomplete_expired: {},
canceled: {},
};
function next(status: Status, event: string): Status {
const target = transitions[status][event];
if (!target) throw new Error(`${event} is not allowed in ${status}`);
return target;
}
Olaylar bir ödeme sağlayıcısının webhook'larından geliyorsa sıraya güvenmeyin. Stripe'ın webhook belgeleri, olayların oluşturuldukları sırayla ulaşacağını garanti etmediğini söyler. Her olayı aboneliğin güncel durumunu çekmek için bir neden olarak görün, sonra geçişi o duruma uygulayın.
Yaygın hatalar
- Eksik olaylar. Her durum için her olayı kontrol edin. "Müşteri
past_dueiken iptal ederse ne olur?" diyagramın yanıtlaması gereken bir sorudur. - Durum olarak çizilmiş bayraklar. Dönem sonunda iptal, bir indirim ya da bir koltuk sayısı bir durumun özellikleridir, durum değildir.
- Durum olarak çizilmiş eylemler. "Ödeme hatırlatma e-postası gönder" bir geçişteki eylem ya da bir
entrydavranışıdır, aboneliğin beklediği bir yer değildir. - Son durumun olmaması. Yaşam döngüsünün nerede bittiğini gösterin ki kimse iptal edilmiş bir aboneliği canlandıran kod yazmasın.
- Çakışan ya da boşluk bırakan koşullar. Bir seçimden çıkan koşullar her durumu tam olarak bir kez kapsamalıdır; gerekirse
[else]ekleyin. - Bir diyagramda iki yaşam döngüsü. Aboneliğin ve faturalarının ayrı durum makineleri vardır. Onları birleştirmek yerine olaylarla bağlayın.
Diyagramı onu uygulayan kodun yanında tutun ve ikisini aynı pull request'te inceleyin; eskimeyen mimari diyagramlar yazısında anlatıldığı gibi.
Archboard'da oluşturun
- Google ya da GitHub ile giriş yapın ve bir diyagram oluşturun.
- UML paketinden her abonelik durumu için bir Durum ekleyin.
trialing,activevepast_due'nun etrafına Access granted adlı bir Temel paketi Çerçeve ekleyin.- Başlangıç durumu için Temel paketinden küçük, dolgulu bir Elips, her seçim için bir Eşkenar dörtgen ve her son durum için noktalı daire biçiminde bir İkon ekleyin.
- Durumları
event [guard] / actionetiketli kenarlarla bağlayın. - Bileşik durumun giriş ve çıkış eylemleri için bir UML Not ekleyin.
Örnek 14 diyagram düğümü kullanıyor ve Free planın sınırları içinde kalıyor. MCP üzerinden bağlanan bir yapay zekâ ajanıyla durum enum'ınızı yapıştırıp durumları ona taslak olarak çizdirebilir, sonra her geçişi kendiniz kontrol edebilirsiniz.
SSS
Durum diyagramı ile akış şeması arasındaki fark nedir? Akış şeması bir sürecin adımlarını sırayla gösterir. Durum diyagramı ise bir nesnenin içinde bulunabileceği koşulları ve onu bunlar arasında hangi olayların taşıdığını gösterir; yani sıradaki adımı değil, neye izin verildiğini yanıtlar.
Status sütunumun her değeri bir durum olmalı mı? Davranışı değiştiren her değer olmalı. İki değer her durumda aynı davranıyorsa onları birleştirin; tek bir değer iki davranışı gizliyorsa ikiye bölün.
Webhook'lar bir durum makinesinde nereye oturur? Onlar olaydır. Onları doğrulayın, sağlayıcıdan güncel durumu çekin ve geçişi uygulayın; çünkü olaylar sıra dışı ya da birden fazla kez gelebilir.
Bileşik bir duruma ihtiyacım var mı? Birkaç durum erişim ya da faturalama gibi bir kuralı paylaşıyorsa bir tane kullanın. Birçok okta tekrarlayacağınız bir kuralı tek bir giriş ve tek bir çıkış eylemine dönüştürür.
Geçiş etiketleri ne kadar ayrıntılı olmalı? Olayı alan terimleriyle adlandırın ve yalnızca aynı olayla iki ok çıktığında bir koşul ekleyin. Erişim vermek gibi doğruluk açısından önemli eylemleri diyagrama koyun.