Всеки може да напише код, който да работи няколко седмици или месеци, но какво се случва, когато този код вече не е вашият ежедневен фокус и паяжините на времето започват да се промъкват? Ами ако това е код на някой друг? Как добавяте нови функции, когато всеки път трябва да преучите цялата кодова база? Как можете да сте сигурни, че извършването на малка промяна в единия ъгъл няма да наруши нещо другаде?

Сложността и свързването в кода ви могат да ви засмукат в спирала на бавна смърт към евентуалното основно пренаписване. Можете да се опитате да избегнете тази горчива съдба, като използвате архитектурни модели като архитектура, управлявана от събития. Когато изграждате система от дискретни услуги, които комуникират чрез събития, ограничавате сложността на всяка услуга, като намалявате свързването. Всяка услуга може да се поддържа, без да се налага да се докосвате до всички други услуги за всяка промяна в бизнес изискванията.

Но ако не сте внимателни, лесно е да изпаднете в лоши навици, зареждайки събития с твърде много данни и въвеждайки повторно свързване от различен вид. Нека да разгледаме как това може да се случи, като анализираме процеса на плащане на Amazon.com и обсъдим как бихте могли да направите нещата по различен начин.

Какво имам предвид под събитие?

Преди да стигнем до процеса на плащане, позволете ми да конкретизирам какво имам предвид под думата събитие. Събитието има две характеристики: вече се е случило и е от значение за бизнеса. Клиент се е регистрирал, направена е поръчка, добавен е нов продукт - всичко това са примери за събития, които имат бизнес смисъл.

Сравнете това с команда. Командата е директива да се направи нещо, което още не се е случило, като например да направите поръчка или да промените адрес. Често командите и събитията идват по двойки. Например, ако командата PlaceOrder е успешна, събитие OrderPlaced може да бъде публикувано и други услуги могат да реагират на това събитие.

Командите имат само един приемник: кодът, който върши работата, която командата иска да свърши. Например командата PlaceOrder има само един приемник, тъй като има само един парче код, който може да направи поръчката. Тъй като има само един приемник, е доста лесно да промените, модифицирате и развиете командата и кода за обработка в стъпка.

Събитията обаче ще бъдат консумирани от множество абонати. Възможно е да има две, пет или петдесет парчета код, които реагират на събитието OrderPlaced, като обработка на плащания, изпращане на артикули, презареждане на склад и т.н. Тъй като може да има много места, абонирани за събитието, модифицирането на събитието може да има голям ефект на пулсации чрез множество различни системи, както ще видите скоро.

Нека купим нещо

Нека да отидем в Amazon, за да купим Enterprise Integration Patterns от Gregor Hohpe и Bobby Woolf, което е ценно четиво за всеки, който изгражда разпределени системи. Посещавате Amazon, поставяте книгата в пазарската си кошница и след това пристъпвате към плащане. Какво става по-нататък?

Забележка: Действителният процес на плащане на Amazon е по-сложен от представения тук и се променя през цялото време. Този опростен пример ще бъде достатъчно добър, за да илюстрира въпроса, без да се усложнява твърде много.

Докато ви ръководят през процеса на плащане, Amazon ще събере куп информация от вас, за да направи поръчката. Нека разгледаме накратко каква информация ще е необходима, за да бъде завършена вашата поръчка:

  • Артикулите в кошницата ви за пазаруване
  • Адресът за доставка
  • Информация за плащане, включително вид на плащане, адрес за фактуриране и др.

Когато стигнете до края на процеса на плащане, цялата тази информация ще бъде показана за преглед, заедно с бутон „направете поръчката си“. Когато щракнете върху бутона, ще бъде изведено събитие OrderPlaced, съдържащо цялата информация за поръчката, която сте предоставили, заедно с OrderId за уникално идентифициране на поръчката. Събитието може да изглежда по следния начин:

Във вашата подобна на Amazon система ще има абонати за това събитие, които ще влязат в действие, след като бъде публикувано: таксуване на поръчката, коригиране на нивата на запасите, подготовка на артикула за изпращане и изпращане на имейл. Може да има допълнителни абонати, които да управляват програми за лоялност на клиентите, да коригират цените на артикулите въз основа на популярността, да актуализират асоциации „често купувани с“ и безброй други неща. Важното е, че няколко дни по-късно в кутия на прага пристига нова книга.

Така че всичко е страхотно, нали?

Събитие подуване

Това събитие OrderPlaced отделя уеб нивото от обработката от заден план, което ви кара да се чувствате добре със себе си, но крие по-коварно свързване, което може да ви затрудни по-късно. Това е като преяждане на голямо семейно събиране - в момента се чувства добре, но в крайна сметка ще ви боли стомах.

Събитие като това отнема на всяка услуга автономия, защото всички те зависят от услугата по продажби, за да предоставят нужните им данни. Тези различни елементи от данни се заключват заедно в договора за събитие OrderPlaced. Така че, ако доставката иска да добави нова опция за доставка на Amazon Prime, тази информация трябва да бъде добавена към събитието OrderPlaced. Billing иска да поддържа Bitcoin? OrderPlaced трябва да се промени отново. Тъй като службата за продажби е отговорна за събитието OrderPlaced, всяка друга услуга зависи от продажбите.

С всяка промяна в събитието OrderPlaced ще трябва да анализирате всеки абонат, за да видите дали и той трябва да се промени. В крайна сметка може да се наложи да преразпределите цялата система, а това означава да тествате и всички засегнати парчета.

Така че наистина нямате автономни услуги. Имате заплетена мрежа от взаимозависими услуги. Целта на управляваната от събития архитектура беше да раздели системата, така че промените в бизнес изискванията да могат да бъдат приложени само чрез целенасочени промени в изолирани услуги. Но с тлъсто събитие като показаното по-горе, това става невъзможно.

Поздравления, създадохте чудовището на Франкенщайн. По същество търгувахте с монолитна система за управлявана от събития разпределена монолитна система. Ами ако можете да разплетете тези системи, така че да са наистина автономни?

Време е за диета

За да намалите събитието и да го приведете в бойна форма, трябва да го поставите на диета. За целта нека започнем отначало и да анализираме всяка информация в събитието OrderPlaced и да я присвоим на конкретна услуга.

поставяне

OrderId и ShoppingCart са свързани с продажбата на продукта, така че те могат да бъдат собственост на Sales. Адресът за доставка обаче се отнася до изпращането на продуктите до клиента, така че те трябва да бъдат собственост на служба за доставка. Плащането се отнася до събиране на плащане за продуктите, така че нека имаме, че принадлежат към услуга за таксуване.

С тези очертани граници можем да прегледаме процеса на плащане и да видим дали има начин да подобрим нещата.

Отслабване

Трикът за отслабване на нашите събития и намаляване на връзката между услугите е да създадете OrderId предварително. Няма закон, че всички идентификатори трябва да идват от база данни. OrderId може да бъде създаден, когато потребителят стартира процеса на плащане.

Можете да започнете процеса на плащане, като изпратите команда CreateOrder до службата за продажби, за да определите OrderId и елементите в количката:

Следващата стъпка от процеса на плащане беше избор на адрес за доставка. Вместо да добавяте тези данни към събитието OrderPlaced, ами ако вместо това създадете отделна команда?

Можете да изпратите командата StoreShippingAddressForOrder от уеб приложението директно до службата за доставка, която притежава данните. Към този момент поръчката дори не е направена, така че все още не се изпращат пакети. Когато дойде време за изпращане на поръчката, службата за доставка вече ще знае къде да я изпрати.

Ако клиентът никога не завърши поръчката, няма да навреди, че вече сте изпълнили тези стъпки. Всъщност има ценни бизнес прозрения, които може да се получат от анализа на изоставените колички за пазаруване и наличието на процес за връзка с потребители, които са изоставили количките за пазаруване, може да се окаже ценен начин за увеличаване на продажбите.

След това в процеса на плащане трябва да съберете информация за плащане от клиента. Тъй като Плащането е собственост на услугата Billing, можете да изпратите тази команда на Billing:

Услугата за таксуване все още няма да таксува поръчката - просто запишете информацията и изчакайте, докато поръчката бъде направена. Ако вашата организация не иска да понесе риска за сигурността при съхраняване на информация за кредитна карта, плащането може да бъде разрешено сега и да бъде уловено след направена поръчка.

Остава само да направите поръчката. Чрез създаването на OrderId предварително успяхме да премахнем по-голямата част от данните в първоначалното събитие OrderPlaced, като вместо това ги изпратихме на други услуги, които притежават тези части от информацията. Така че службата по продажби вече може да публикува изключително просто събитие OrderPlaced:

Това съкратено събитие OrderPlaced е много по-фокусирано. Всички ненужни съединители са премахнати. След като това събитие бъде публикувано от Sales, Billing ще вземе информацията за плащане, която вече е съхранила, и ще таксува поръчката. Той ще публикува събитие с поръчка, когато кредитната карта бъде успешно таксувана. Услугата за доставка ще се абонира за OrderPlaced from Sales и OrderBilling from Billing и след като получи и двете, ще знае, че може да изпрати продуктите на потребителя.

Нека да разгледаме отново двете версии на събитието OrderPlaced:

Кое събитие би било най-малко рисковано за внедряване в производството? Кое би било по-лесно да се тества? Отговорът е по-малкото събитие, с премахнати всички ненужни съединители.

Бойна форма

Ползата от отслабването на нашите събития е да ги приведем в бойна форма, за да се справим с промените в бизнес изискванията, които със сигурност ще дойдат по линия. Ако искаме да представим доставката на Amazon Prime или да поддържаме Bitcoin като начин на плащане, сега е много по-лесно да го направим, без изобщо да се налага да модифицираме услугата Продажби.

За да поддържаме първоначалната доставка, ние бихме изпратили команда SetShippingTypeForOrder на службата за доставка по време на услугата за плащане. Ще изглежда по следния начин:

Това би била втората команда, която изпращаме до службата за доставка, заедно с StoreShippingAddressForOrder. Добавянето на първоначална доставка ще промени начина, по който службата за доставка подготвя поръчка, но няма причина да се докосва събитието OrderPlaced или някой от кодовете в услугата за продажби.

По подобен начин бихме могли да приложим Bitcoin, загриженост на услугата за таксуване, по няколко различни начина. Бихме могли да добавим свойства на Bitcoin към класа PaymentDetails, използван в командата StoreBillingDetailsForOrder. Или можем да разработим нова команда специално за Биткойн и да я изпратим вместо StoreBillingDetailsForOrder. В този случай Billing няма да публикува OrderBilling, освен ако плащането не е извършено в една от двете форми. В крайна сметка службата за доставка се грижи само за поръчката да е платена. Не се интересува как.

Във всеки случай, поддръжката за биткойн ще бъде осъществена единствено чрез промяна на елементи на услугата за таксуване. Продажбите и изпращането ще останат напълно непроменени и не би трябвало да бъдат тествани или преразпределени. С по-малка площ, засегната от всяка промяна, ние можем да се адаптираме към променящите се бизнес изисквания много по-бързо.

И това беше някакъв смисъл на първо място да се използва управлявана от събития архитектура.

Обобщение

В системите, управлявани от събития, големите събития са миризма на дизайн. Опитайте се събитията да бъдат възможно най-малки. Услугите наистина трябва да споделят само идентификатори и може би клеймо за време, за да посочат кога информацията е ефективна. Ако ви се струва, че повече услуги от това трябва да се споделят между услугите, приемете това като индикация, че може би границите между вашите услуги са грешни. Помислете за вашата архитектура въз основа на това кой трябва да притежава всяка част от данните и поставете тези събития на диета.

За повече информация как да създадете слабо свързани системи, управлявани от събития, разгледайте нашия урок стъпка по стъпка на NServiceBus.

За автора: Дейвид Бойке е разработчик на Particular Software, който поради злополучен афинитет към пица и буритос е много по-лесно да постави събитията си на диета, отколкото той самият.