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

Веригата на отговорността или командната веригае дизайнерски модел, който ви позволява да предавате заявки по верига от 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 са достатъчно общи, за да бъдат преместени на друго място във веригата.

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