Event-driven arhitektura omogućava proizvodu da reaguje na činjenice umjesto da zove sljedeći servis i nada se da će cijeli lanac izdržati. Checkout ne treba da čeka da zalihe, email, fakturisanje i analitika redom odgovore. To su posljedice činjenice „narudžba je potvrđena”, i svaka od njih treba da može uspjeti, ponoviti se ili zaostati, a da zahtjev kupca ne povuče sa sobom.
Sadržaj
U CodeWizu dizajniramo evente, biramo broker i gradimo consumere. To pokriva event sourcing tamo gdje je prošlost izvor istine, message brokere za posao koji mora biti isporučen, event streaming kad više timova treba da ponovo pročita iste činjenice, i obradu u realnom vremenu za dijelove proizvoda koje čovjek zaista gleda. Uz broker ostaju outbox, idempotentni consumeri i upozorenje kad neko zaostane. Radimo u Laravelu, Node.js-u i u postojećim sistemima koji već imaju bazu i gomilu cron poslova koji je ispituju umjesto da čuju šta se desilo.
Usluga je za online prodavnice, SaaS proizvode sa integracijama i svaki tok u kojem jedna radnja korisnika mora da se raspline na nekoliko ishoda. Ako imate jedan pozadinski posao i jednog workera, dovoljan je običan red, i to ćemo reći.
Kod teme driven alati otvorenog koda su prvi izbor kada odgovaraju poslu.
Event sourcing: stanje kao niz činjenica
Event sourcing čuva šta se desilo, redom, a trenutni red tretira kao prikaz koji se može ponovo izgraditi. OrderPlaced, PaymentCaptured i ShipmentDispatched su zapis. Presavijanje tog niza daje narudžbu koju pokazujete danas. Alternativa – ažurirati red, pa se nadati da je sporedna tabela sačuvala priču – gubi korake čim neko red „popravi” rukom.
Driven kreće od sistema koji već radi, a ne od praznog šablona.
Kad log zasluži svoje mjesto
- Poslovanje pita „kako je ovo izgledalo u utorak?”, ili to pita revizor.
- Nekoliko consumera treba različit oblik istih činjenica: search indeks, glavnu knjigu, obavijest, ekran skladišta.
- Ispravke su nove činjenice (
RefundIssued), a ne tihe izmjene prošlosti. - Evente možete imenovati jezikom poslovanja. Ako je jedino iskreno ime
RowUpdated, eventa još nemate.
Streamove držimo po aggregateu, dopisujemo uz očekivanu verziju da dva pisca ne mogu prepisati jedan drugog, i snapshot radimo tek kad je stream dovoljno dug da je ponovno čitanje pri svakom učitavanju rasipanje. Event sourcing nije podrazumijevano skladište za svaku tabelu. Ekran podešavanja ne treba log. Gubitak radnje kupca u checkoutu treba, i to je ista granica koju opisujemo u tekstu o tome kako svaka narudžba i radnja kupca ostaju obnovljive.

Message brokeri za event-driven arhitekturu
Message broker prenosi činjenicu od servisa koji ju je zabilježio do servisa koje zanima. Pogrešan izbor rijetko je u vršnim brojkama. U pitanju je trebate li red zadataka, trajni log koji više timova čita svojim tempom, ili malu magistralu niskog kašnjenja. Biramo jedan glavni broker po sistemu i drugi dodajemo samo iz razloga koji umijemo objasniti.
| Broker | Model | Snaga | Na šta paziti |
|---|---|---|---|
| RabbitMQ | Exchangei, redovi, konkurentni consumeri | Raspodjela zadataka, rutiranje, ponavljanja, dead-letter redovi | Poruke se potroše; nisu dugotrajni log koji slobodno ponovo čitate |
| Apache Kafka | Trajni log, particije, consumer grupe | Veliki protok, zadržavanje, mnogo nezavisnih čitalaca, replay | Particije, zadržavanje i consumer lag sada su dio dizajna |
| NATS i JetStream | Subjecti, lagani pub/sub, opcionalni trajni streamovi | Nisko kašnjenje i mali operativni otisak | Manje pratećeg alata kad sama obrada postane teška |
| Redis Streams | Log unutar Redisa, consumer grupe | Redis već imate, a obim je skroman | Nije zamjena za Kafku kad zadržavanje i fan-out narastu |
Kako ih vodimo
- Dead-letter odredište za poruke koje padnu iz razloga koji ponavljanje neće popraviti, plus upozorenje na njegovu dubinu.
- Ograničena ponavljanja sa backoffom. Beskonačna petlja ponavljanja je način da jedna loša poruka zaustavi particiju.
- Consumeri koji su idempotentni, jer je isporuka at least once u svakom brokeru koji vrijedi koristiti.
- Metrike za lag, neuspjehe objave i padove consumera, u istom rangu kao ostatak aplikacije.
Reference prema kojima podešavamo su RabbitMQ dokumentacija, Apache Kafka dokumentacija i NATS dokumentacija. Gdje sami brokeri rade – na jednom VPS-u ili u rasporedu preko više provajdera – slijedi istu procjenu kao ostatak platforme, o čemu smo pisali u tekstu o deploymentu preko DigitalOceana, AWS-a, Hetznera i bare metala.
Event streaming koji preživi restart
Event streaming je dio event-driven arhitekture koji imate kad broker čuva uređen, zadržan log, a consumeri pamte svoju poziciju. Novi servis može krenuti od početka mjeseca i složiti vlastiti read model, bez dogovora sa onima koji su log već čitali. Bag u consumeru može se popraviti, a iste činjenice pročitati ponovo, umjesto da se izgubljeni korak traži po logovima aplikacije. To je drugo obećanje od reda, u kojem poruka nestane čim je neko potvrdi.
Redoslijed, ključevi i replay
- Redoslijed je zagarantovan po ključu, ne globalno. Ključiramo po ID-u aggregatea (narudžba, nalog), tako da eventi jednog entiteta ostanu u nizu na jednoj particiji.
- Consumer grupe dijele posao. Dvije instance billing consumera dijele particije; zasebna grupa za pretragu čita svaku particiju sama.
- Replay je planirana operacija: nova consumer grupa ili reset offseta, prema projekciji koja se može isprazniti i ponovo izgraditi.
- Zadržavanje je odluka o proizvodu. Sedam dana je dovoljno za ponavljanje i kratku ponovnu izgradnju; knjiga koja se mora moći odvrti godinama pripada event storeu, a broker je put isporuke.

Obrada u realnom vremenu koja ne blokira korisnika
Obrada u realnom vremenu znači da čovjek, ili drugi sistem, vidi posljedicu dovoljno brzo za posao koji je u toku. Za poruku u chatu to je djelić sekunde. Za status narudžbe možda sekunda ili dvije. Za noćni obračun to nije obrada u realnom vremenu, a natjerati ga da bude samo dodaje trošak. Budžet kašnjenja upisujemo prije nego što izaberemo alat.
Posljednji skok nije broker
- Zahtjev koji potvrđuje narudžbu upisuje činjenicu i vraća odgovor. Ne zove svaki nizvodni sistem.
- Consumeri ažuriraju read modele i outbox za obavijesti. WebSocket ili server-sent events veza, tamo gdje proizvod treba živ ekran, posljednji je korak i može se ponovo spojiti.
- Živi dashboard čita projekciju, ne write transakciju. Grafikon smije kasniti sekundu; naplata ne smije proći dvaput.
- Pritisak je vidljiv. Ako consumer skladišta zaostane, sajt i dalje prima narudžbe, a neko bude pozvan zbog laga.

Dizajn eventova: imena, verzije i idempotentnost
Event je činjenica u prošlom vremenu, sa stabilnim imenom i payloadom onoga što consumeru treba da bi djelovao. Payload verzijonišemo od prvog izdanja. Dodavanje polja sigurno je za consumere koji nepoznato ignorišu; preimenovanje ili promjena značenja nova je verzija, i obje verzije se proizvode dok se svaki consumer ne preseli.
- Jedinstveni ID eventa i ID komande ili zahtjeva koji ga je izazvao, da replay ne izgleda kao nova činjenica.
- ID aggregatea i njegova verzija, da consumeri odbace sve što stigne van niza koji su već primijenili.
- Bez tajni i bez velikih blobova. Event plaćanja nosi referencu providera i iznos, ne karticu i ne PDF.
- Consumeri pamte ID-eve eventova koje su obradili. Isporuka at least once tada postaje prazan prolaz, a ne dupli email ili dupla naplata.
Plaćanje je oštar slučaj. „Captured” mora biti obrađen jednom čak i kad broker isporuči dvaput, zato idempotentnost stoji uz integraciju gatewaya koju opisujemo u tekstu o integraciji payment gatewaya za online prodavnice.
Transakcioni outbox
Klasičan pad je dvostruki upis: baza potvrdi transakciju, a objava brokeru ne, ili obrnuto. Transakcioni outbox stavlja event u istu transakciju baze kao izmjenu stanja. Zasebni relay čita neobjavljene redove i objavljuje ih. Ako proces umre, objavljuje ponovo. Consumeri su već idempotentni, pa je duplikat bezopasan.
CREATE TABLE outbox (
id uuid PRIMARY KEY,
aggregate_id uuid NOT NULL,
type text NOT NULL,
payload jsonb NOT NULL,
occurred_at timestamptz NOT NULL,
published_at timestamptz
);Relay uzme seriju, objavi, pa postavi published_at. Ako padne nakon objave, a prije tog upisa, sljedeći prolaz pošalje isti event još jednom, i consumer koji pamti ID to proguta. Red koji ostane neobjavljen preko praga je upozorenje, ne iznenađenje koje otkrije kupac. Isti oblik tabele koristimo u PostgreSQL-u za Laravel i za Node.js servise; relay je mali worker, ne drugi framework.
Kada je event-driven pristup pogrešan alat
Eventi dodaju operativnu površinu: lag, loše poruke, verzionisanje, replay. To se isplati kad postoji stvarni fan-out, stvarna potreba za ponovnim čitanjem ili stvarna granica između timova koji ne žele da dijele jedan deploy. Ne isplati se za jedan email nakon registracije. Tada je posao na Redis redu ili redu u bazi, u istoj aplikaciji, cijeli dizajn, sa ponavljanjem i odvojenim mjestom za poruke koje ne mogu da se obrade. Radije vodimo to nego klaster sa jednim consumerom.
Česte greške event-driven arhitekture koje ispravljamo
- Objava nakon commita, u kodu aplikacije, bez outboxa i bez oporavka kad proces padne između.
- Eventi koji nose cijeli red pod imenom poput
Updated, tako da ih niko ne može verzionisati niti reći šta se desilo. - Consumeri koji nisu idempotentni, u paru sa brokerom koji će rado isporučiti dvaput.
- Pretpostavka globalnog redoslijeda preko svih particija, pa otklanjanje toga u produkciji.
- Ponavljanja bez dead-letter puta, tako da jedan loš payload blokira sve iza sebe.
- Zahtjev za „realnim vremenom” koji nikada nije dobio broj, izveden kao sinhroni pozivi svakog nizvodnog servisa.
Kako radimo
- Upoznavanje: popisujemo činjenice do kojih je poslovanju stalo, ko mora reagovati i koliko svaka reakcija smije kasniti.
- Dizajn: imena eventova i payloadi, broker, ključ za redoslijed i mjesta gdje je outbox obavezan.
- Izrada: publisheri, relay, consumeri, dead-letter rukovanje i skladište idempotentnosti.
- Dokaz: proces ugašen usred objave, dupla isporuka, replay u praznu projekciju i consumer koji namjerno zaostane.
- Puštanje: prvo jedan tok, pored starog puta, dok novi consumeri ne budu isti. Onda stari lanac izlazi.
- Podrška: upozorenja na lag i dead-letter, zapisan način da se doda consumer i pravilo verzionisanja za sljedeću izmjenu payloada.
Zašto CodeWiz
- Gradimo obje strane: Laravel ili Node.js kod koji bilježi činjenicu i consumere koji reaguju, tako da ugovor nije teorijski.
- Najmanji broker koji pristaje: Redis ili RabbitMQ kad je to iskreno, Kafka ili NATS kad replay ili kašnjenje to stvarno traže.
- Open-source na prvom mjestu: Kafka, RabbitMQ, NATS i PostgreSQL, na infrastrukturi sa koje možete otići. Nema poreza po poruci kao ulaznice.
- Full-stack: API-ji, baze, redovi i živi ekran jedan su dizajn, jer ih isporučuje isti tim.
- Fer cijene: vrhunski razvoj bez vrhunskih cijena, od tima iz Banje Luke koji radi na daljinu sa klijentima iz regiona i svijeta. Više o CodeWizu.
Često postavljana pitanja
Koliko košta event-driven arhitektura?
Jedan outbox, jedan broker i dva consumera na postojećoj aplikaciji fokusiran su projekat: tabela, relay i jedan tok koji više ne čeka nizvodne servise. Event sourcing nekoliko aggregatea, sa projekcijama koje se mogu odvrti i pričom o zadržavanju, veći je posao. Najveći dio dodatnog vremena je dokaz: testovi pada, vremenski izmjerena ponovna izgradnja i selidba sa starog sinhronog lanca, uključujući sedmicu u kojoj stari i novi put rade paralelno. Procjenu dajemo kad znamo koliko činjenica i consumera ulazi u obim.
Da li da koristimo Kafku ili RabbitMQ?
RabbitMQ koristite kad dijelite zadatke, trebaju vam rutiranje i dead-letter redovi, a duga prošlost vam ne treba za replay. Kafku koristite kad nekoliko nezavisnih consumera mora čitati iste činjenice, ili projekciju treba ponovo izgraditi iz zadržanih podataka. Mnogi proizvodi godinama dobro rade na RabbitMQ-u ili Redis Streamsu.
Moramo li uvesti event sourcing?
Ne. Objava eventa kad se obična transakcija baze zatvori, preko outboxa, dovoljna je za većinu fan-outa. Event sourcing je za slučajeve u kojima je sama prošlost izvor istine. Ispravan relacioni model ne mijenjamo samo da bismo imali streamove.
Kako spriječiti da se poruke izgube?
Event se upisuje u istoj transakciji kao izmjena stanja, relay objavljuje dok broker ne potvrdi, a consumeri bilježe šta su već primijenili. Pad u bilo kojoj tački vodi u ponavljanje. Preostali rizik je consumer koji sprovede nuspojavu, pa padne prije nego što je zabilježi, zato i sama nuspojava mora biti idempotentna.
Možete li ovo uvesti bez prepisivanja aplikacije?
Možemo. Obično umotamo jedan važan tok: izmjena stanja i outbox ulaze u postojeći kod, a jedan novi consumer preuzima jedan nizvodni posao. Ostali sinhroni pozivi ostaju dok taj put ne zasluži isti tretman.
Spremni da reagujete na činjenice umjesto na lance poziva?
Recite nam koja radnja korisnika se danas rasplinjava i koji korak se bojite ponoviti. Predložićemo broker i dizajn eventova koji odgovaraju tom problemu. Kontaktirajte nas da prođemo kroz to, ili pogledajte naše usluge event-driven arhitekture.