The модел на публикуване-абониране (или pub / sub, за кратко) е модел за съобщения Ruby on Rails, при който изпращачите на съобщения (издатели) не програмират съобщенията да бъдат изпращани директно до конкретни получатели (абонати). Вместо това програмистът „публикува“ съобщения (събития), без да има знания за абонати.
По същия начин абонатите изразяват интерес към едно или повече събития и получават само съобщения, които представляват интерес, без каквото и да е знание от издателите.
За да постигне това, посредник, наречен „посредник за съобщения“ или „събитийна шина“, получава публикувани съобщения и след това ги препраща към тези абонати, които са регистрирани да ги получат.
С други думи, pub-sub е модел, използван за комуникация на съобщения между различни системни компоненти, без тези компоненти да знаят нищо за идентичността на другия.
Този модел на дизайн не е нов, но не е често използван от Разработчици на релси . Има много инструменти, които помагат да се включи този модел на дизайн във вашата кодова база, като например:
Всички тези инструменти имат различни основни реализации на pub-sub, но всички те предлагат едни и същи основни предимства за приложението Rails.
Честа практика е, но не е най-добрата практика да имате някои модели мазнини или контролери във вашето приложение Rails.
Моделът pub / sub може лесно помогне разлагат мазнини модели или контролери .
Като много преплетени обратни обаждания между моделите е добре познат код миризма , и малко по малко тя плътно свързва моделите, което ги прави по-трудни за поддържане или разширяване.
Например a Post
модел може да изглежда по следния начин:
# app/models/post.rb class Post # ... field: content, type: String # ... after_create :create_feed, :notify_followers # ... def create_feed Feed.create!(self) end def notify_followers User::NotifyFollowers.call(self) end end
И Post
контролер може да изглежда по следния начин:
# app/controllers/api/v1/posts_controller.rb class Api::V1::PostsController Както можете да видите, Post
model има обратни извиквания, които плътно свързват модела с двете Feed
модел и User::NotifyFollowers
услуга или загриженост. Чрез използване на всеки шаблон pub / sub, предишният код може да бъде префакториран, за да бъде нещо като следното, което използва Wisper:
# app/models/post.rb class Post # ... field: content, type: String # ... # no callbacks in the models! end
Издатели публикувайте събитието с обекта на полезен товар, който може да е необходим.
# app/controllers/api/v1/posts_controller.rb # corresponds to the publisher in the previous figure class Api::V1::PostsController Абонати абонирайте се само за събитията, на които желаете да отговорите.
# app/listener/feed_listener.rb class FeedListener def post_create(post) Feed.create!(post) end end
# app/listener/user_listener.rb class UserListener def post_create(post) User::NotifyFollowers.call(self) end end
Автобус за събития регистрира различните абонати в системата.
# config/initializers/wisper.rb Wisper.subscribe(FeedListener.new) Wisper.subscribe(UserListener.new)
В този пример моделът pub-sub напълно елиминира обратните обаждания в Post
модел и помогна на моделите да работят независимо един от друг с минимални знания един за друг, осигурявайки свободно свързване. Разширяването на поведението до допълнителни действия е само въпрос на свързване към желаното събитие.
Принцип на единна отговорност (SRP)
The Принцип на единна отговорност е наистина полезно за поддържане на чиста основа на кода. Проблемът при спазването му е, че понякога отговорността на класа не е толкова ясна, колкото би трябвало да бъде. Това е особено често, когато става въпрос за MVC (като Rails).
Модели трябва да се справят с упоритост, асоциации и не много други неща.
Контролери трябва да обработва потребителски заявки и да бъде обвивка около бизнес логиката (Service Objects).
Обекти на услугата трябва да капсулира една от отговорностите на бизнес логиката, да осигури входна точка за външни услуги или да действа като алтернатива на моделните проблеми.
Благодарение на способността си да намалява свързването, моделът за проектиране на pub-sub може да се комбинира с обекти за обслужване с една отговорност (SRSO), за да помогне за капсулирането на бизнес логиката и да забрани на бизнес логиката да се прокрадва нито в моделите, нито в контролерите. Това поддържа кодовата база чиста, четлива, поддържаема и мащабируема.
Ето пример за някаква сложна бизнес логика, реализирана с помощта на шаблона pub / sub и обектите на услугата:
Издател
# app/service/financial/order_review.rb class Financial::OrderReview include Wisper::Publisher # ... def self.call(order) if order.approved? publish(:order_create, order) else publish(:order_decline, order) end end # ...
Абонати
# app/listener/client_listener.rb class ClientListener def order_create(order) # can implement transaction using different service objects Client::Charge.call(order) Inventory::UpdateStock.call(order) end def order_decline(order) Client::NotifyDeclinedOrder(order) end end
Използвайки шаблона за публикуване-абониране, кодовата база се организира в SRSO почти автоматично. Освен това внедряването на код за сложни работни потоци се организира лесно около събития, без да се жертва четливостта, поддръжката или мащабируемостта.
Тестване
Чрез декомпозиране на мастните модели и контролери и наличието на много SRSO, тестването на кодовата база се превръща в много, много по-лесен процес. Това е особено в случая, когато става въпрос за тестване на интеграция и междумодулна комуникация. Тестването трябва просто да гарантира, че събитията са публикувани и приети правилно.
Wisper има тестващ скъпоценен камък което добавя RSpec съвпадения, за да улесни тестването на различни компоненти.
В предишните два примера (Post
пример и Order
пример) тестването трябва да включва следното:
Издатели
# spec/service/financial/order_review.rb describe Financial::OrderReview do it 'publishes :order_create' do @order = Fabricate(:order, approved: true) expect { Financial::OrderReview.call(@order) }.to broadcast(:order_create) end it 'publishes :order_decline' do @order = Fabricate(:order, approved: false) expect { Financial::OrderReview.call(@order) }.to broadcast(:order_decline) end end
Абонати
# spec/listeners/feed_listener_spec.rb describe FeedListener do it 'receives :post_create event on PostController#create' do expect(FeedListner).to receive(:post_create).with(Post.last) post '/post', { content: 'Some post content' }, request_headers end end
Има обаче някои ограничения за тестване на публикуваните събития, когато издателят е контролерът.
Ако искате да преминете допълнително, тестването на полезния товар също ще ви помогне да поддържате още по-добра основа на кода.
Както можете да видите, тестването на шаблони за дизайн на pub-sub е просто. Въпросът е само да се гарантира, че различните събития са правилно публикувани и получени.
производителност
Това е по-скоро a възможен предимство. Самият шаблон за дизайн на публикуване-абониране няма съществено присъщо влияние върху производителността на кода. Както при всеки инструмент, който използвате в кода си, инструментите за внедряване на pub / sub могат да имат голям ефект върху производителността. Понякога това може да е лош ефект, но понякога може да бъде много добър.
Първо, пример за лош ефект: Редис е „усъвършенстван кеш и съхранение на ключ-стойност. Често се нарича сървър за структура на данни. ' Този популярен инструмент поддържа шаблона pub / sub и е много стабилен. Ако обаче се използва на отдалечен сървър (не на същия сървър, на който е внедрено приложението Rails), това ще доведе до огромна загуба на производителност поради натоварване на мрежата.
От друга страна, Wisper има различни адаптери за асинхронно обработване на събития, като Wisper-целулоид , Wisper-Sidekiq и Wisper-Activejob . Тези инструменти поддържат асинхронни събития и изпълнения с резба. което, ако се прилага по подходящ начин, може значително да увеличи ефективността на приложението.
Долния ред
Ако се стремите към допълнителната миля в изпълнението, шаблонът pub / sub може да ви помогне да го достигнете. Но дори и да не намерите подобрение на производителността с този модел на проектиране на Rails, той все пак ще помогне да се организира кода и да се направи по-поддържаем. В крайна сметка, кой може да се притеснява за работата на кода, който не може да се поддържа или който първоначално не работи?
Недостатъци на Pub-Sub внедряването
Както при всички неща, има и някои възможни недостатъци на модела pub-sub.
Разхлабено съединение (Негъвкаво семантично свързване)
Най-голямата от силните страни на кръчмата / подложката също са най-големите слабости. Структурата на публикуваните данни (полезния товар) трябва да бъде добре дефинирана и бързо става доста негъвкава. За да модифицирате структурата на данните на публикувания полезен товар, е необходимо да знаете за всички Абонати и или да ги модифицирате, или да се уверите, че модификациите са съвместими с по-стари версии. Това прави рефакторинга на издателския код много по-труден.
Ако искате да избегнете това, трябва да бъдете изключително предпазливи при определянето на полезния товар на издателите. Разбира се, ако имате страхотен тестов пакет, който тества полезния товар, както е споменато по-рано, не е нужно да се притеснявате много за срива на системата, след като промените полезния товар или името на събитието на издателя.
Стабилност на шината за съобщения
Издателите нямат познания за статуса на абоната и обратно. Използвайки прости инструменти pub / sub, може да не е възможно да се гарантира стабилността на самата шина за съобщения и да се гарантира, че всички публикувани съобщения са правилно поставени на опашка и доставени.
Нарастващият брой съобщения, които се обменят, води до нестабилност в системата при използване на прости инструменти и може да не е възможно да се осигури доставка до всички абонати без някои по-сложни протоколи. В зависимост от това колко съобщения се обменят и параметрите на производителността, които искате да постигнете, може да помислите да използвате услуги като RabbitMQ , PubNub , Тласкач , CloudAMQP , IronMQ или много много други алтернативи. Тези алтернативи предоставят допълнителна функционалност и са по-стабилни от Wisper за по-сложни системи. Те обаче изискват и допълнителна работа за изпълнение. Можете да прочетете повече за това как работят брокерите на съобщения тук
Безкрайни цикли на събития
Когато системата се задвижва изцяло от събития, трябва да бъдете особено предпазливи, за да нямате цикли на събития. Тези цикли са точно като безкрайните цикли, които могат да се случат в кода. Те обаче са по-трудни за откриване преди време и те могат да спрат вашата система. Те могат да съществуват без ваше известие, когато има много събития, публикувани и абонирани в системата.
Заключение на урока по Rails
Моделът за публикуване-абониране не е сребърен куршум за всичките ви проблеми с Rails и миризмите на код, но е наистина добър модел на дизайн, който помага при отделянето на различни системни компоненти и го прави по-поддържаем, четим и мащабируем.
Когато се комбинира с обекти за обслужване с една отговорност (SRSO), pub-sub може наистина да помогне с капсулиране на бизнес логика и предотвратяване на проникването на различни бизнес опасения в моделите или контролерите.
Печалбата в производителността след използване на този модел зависи най-вече от използвания инструмент, но в някои случаи печалбата може да се подобри значително и в повечето случаи със сигурност няма да навреди на производителността.
Използването на шаблона pub-sub обаче трябва да се проучи и планира внимателно, защото с голямата сила на разхлабеното свързване идва и голямата отговорност на поддържане и рефакторинг слабо свързани части.
Тъй като събитията могат лесно да излязат извън контрол, обикновена библиотека pub / sub може да не гарантира стабилността на посредника за съобщения.
И накрая, има опасност от въвеждане на безкрайни цикли на събития, които остават незабелязани, докато не стане твърде късно.
Използвам този модел вече почти година и ми е трудно да си представя да пиша код без него. За мен това е лепилото, което кара фоновите задачи, обектите за обслужване, притесненията, контролерите и моделите да комуникират чисто и да работят заедно като чар.
Надявам се, че сте научили толкова, колкото и аз от прегледа на този код, и че се чувствате вдъхновени да дадете шанс на Publish-Subscribe да направи приложението Rails страхотно.
И накрая, огромна благодарност @krisleech за страхотната му работа по изпълнение Wisper .