Използвайте шаблона за проектиране на веригата от отговорност, за да промените поведението на приложението въз основа на промените в конфигурацията
Веригата на отговорността или командната веригае дизайнерски модел, който ви позволява да предавате заявки по верига от Handlers
. Всеки Handler
решава да обработи заявката и да я обогати или предаде на следващия Handler
.
Това ви позволява да имате голяма изолация между всяка стъпка и да избегнете наличието на бизнес логика в средата на някаква техническа логика. Освен това ви дава възможност да пренаредите веригата си, ако това, което трябва да прави приложението ви, се промени.
Това е страхотно, но защо искаме да може да се конфигурира?
Причината за това е да можете бързо да промените поведението на приложението си, без да внедрявате код.
Да кажем, че имаме набор от Handlers
налични за нашата услуга:
Както можете да видите, тук имаме прост конвейер, където свързваме Handlers
, за да обработим заявката. Лесно може да бъде представено чрез конфигурация, която изглежда така:
root: step1 steps: step1: type: handlerImpl1 next: step2 step2: type: handlerImpl2 next: step3 step3: type: handlerImpl3
Но да приемем, че сте в производство и искате да добавите кеш слой, преди да извикате Стъпка 3. Имате късмет, защото Handler
за управление на вашето кеш решение вече съществува за друг конвейер.
Като промените конфигурацията, можете лесно да имате различен тръбопровод с тази стъпка на кеширане:
root: step1 steps: step1: type: handlerImpl1 next: step2 step2: type: handlerImpl2 next: step4 step3: type: handlerImpl3 step4: type: RewriteHandler next: step3
Реален случай на употреба
Да приемем, че изграждате API за търсене. По принцип той получава заявки за търсене и отговаря с отговора.
API е над Elasticsearch,така че това, което направихме, е да извикаме ES директно от API (супер просто). След известно време виждате, че някои водещи заявки за търсене се срещат твърде много, така че решавате да въведете кеш на Redis, преди да извикате ES.
И да кажем, че в даден момент искате да бъдете още по-бързи и да изградите локален кеш във вашето приложение, за да отговаряте супер бързо на тези водещи заявки.
Така че това, което можете да направите, е да имате три Handlers
като по-долу. Всеки Handler
отговаря за едно действие и може да премине към следващата стъпка или не в зависимост от това дали е необходимо или не:
Възможността за конфигуриране също ви позволява да премахнете кеш стъпка по всяко време, без да променяте код. Например, ако видите, че вашият локален кеш консумира твърде много памет и искате да се отървете от него, можете просто да промените конфигурационния си файл и вашите заявки ще могат да отиват директно в кеша на Redis.
Как да го приложите
Можете да преминете към следващия раздел, ако това, което искате, е да видите целия код.
Този раздел ще описва подробно всички части на кода.
Обработете конфигурационния файл
Първата стъпка е да заредите конфигурацията от конфигурационния файл. В този пример ще използвам YAML файл, хостван на gist.
Форматът ще бъде следният:
root: handler1_name steps: handler1_name: type: handlerImpl1 next: handler2_name handler2_name: type: handlerImpl2
И ние ще демаршалираме в нашата структура като първа стъпка от нашата main
функция:
Създайте тръбопровод
Имаме конфигурацията. С това ще създадем самия тръбопровод. Ще извикаме NewPipeline
в нашата main
функция:
pipeline, _ := NewPipeline(pipelineConfig)
Функцията NewPipeline
ще преобразува конфигурационния файл в екземпляр на конвейерна структура с инициализиран Handler
, готов за използване по ваша заявка.
За целта той преобразува StepType
в действително Handler
и извиква функцията init
за всеки Handler
със съществуващ Next
Handler
, ако стъпката има следваща стъпка:
Съпоставянето между използваните StepType
и Handler
се извършва във функцията getHandlerFromType()
:
Създайте манипулатор
Handler
се създава лесно. Той следва този интерфейс:
Функцията Init
се използва най-вече за инициализиране на следващия Handler
за тази стъпка въз основа на конфигурацията.
Функцията Execute
се извиква, за да приложи реалната логика. Това е функцията, в която решаваме дали искаме да преминем към следващата стъпка или не. Както можете да видите, той изисква параметър context
. Това е параметър, който се дава на всеки Handler
и може да бъде обогатен на всяка стъпка. В примера това е *[]string
, но може да бъде указател към всичко.
Пример за Handler
:
Както можете да видите, ние даваме контекста, докато извикваме функцията Execute()
от next
Handler
. Можем също да извършим действие, след като получим резултата от следващата стъпка.
Изпълнете тръбопровода
Последната стъпка е да извикате функцията Execute()
на самия конвейер:
Той отнема стъпката root
и изпълнява свързаната Handler
. По този начин цялата верига ще бъде изпълнена, защото всеки Handler
знае какво следва.
Кодът
Пълното изпълнение е достъпно по-долу. В този пример Handlers
са тривиални, но можете да имате много по-сложна логика върху него и да започнете да свързвате нещата:
Заключение
Както можете да видите, използването на моделана веригата на отговорност или командната верига в Go може да бъде наистина мощно и да ви даде добър начин за отделяне на вашата логика.
А възможността да го конфигурирате извън кода ви дава много потенциални подобрения, ако вашите Handlers
са достатъчно общи, за да бъдат преместени на друго място във веригата.
От опит, ако имате един продукт, който можете да персонализирате за различни клиенти, това ще ви помогне много. В началото ще отделите време, за да развиете манипулаторите си, но в крайна сметка ще имате пълен набор от тях, което ви позволява просто да изберете правилната верига, без да разработвате нищо.