- PVSM.RU - https://www.pvsm.ru -
Недавно, в процессе разработки клиентской части веб-приложения, возникла необходимость определять метки рекламной кампании, приведшей пользователя на сайт.
Изначально, задача показалась весьма линейной — посмотреть тут, потом там, взять что-то по приоритету и передать дальше. Но в процессе выяснилось, что некоторые метки могут появляться асинхронно, и, следовательно, их нужно уметь «ждать».
Усложнение задачи привело к желанию упростить код, участвующий в ее решении.
На примере решения такой задачи, данный пост пытается показать, как проектирование и over engineering может помочь вам в разработке гибких и легко изменяемых приложений.
Метки, которые мы будем искать — это метки UTM [1].
Источники меток — это те места, в которых мы ищем метки. По условию, они имеют приоритет поиска. В нашей задаче источниками являются:
Алгоритм чтения меток зависит от приоритета источника и изначально выглядит так:
В разрабатываемом приложении используется паттерн внедрения зависимостей [2], поэтому компоненты приложения, с некоторыми оговорками, представлены как сервисы. В решении задачи будут участвовать следующие:
Назовем их соответственно cookies, query и utm.
Если с функциональностью cookies и query все достаточно понятно, то как быть с utm? Стоит ли реализовать алгоритм получения меток непосредственно внутри utm или абстрагироваться, и вынести реализацию алгоритма за пределы сервиса?
Алгоритм можно сильно упростить, если:
Соответственно источниками должны быть наши сервисы cookies и query.
Но как быть, если у cookies геттер значения печеньки называется getCookie, а у query геттер параметра — getQueryParameter?
Другими словами, нам понадобиться использовать паттерн адаптер [3].
В результате появятся следующие сервисы-обертки:
Сервис utm будет иметь метод addSource, который принимает в себя объект с интерфейсом источника меток и приоритетом для этого источника. Так же конструктор сервиса принимает в себя объект javascript, расширяющий дефолтные настройки сервиса.
На данной диаграмме представлена связь сервиса utm с репозиторием cookies:
В конфиге сервисов все это выглядело бы так:
cookies:
path: ‘/src/service/cookies/cookies.js’
query:
path: ‘/src/service/query/query.js’
cookies-utm-adapter:
path: ‘/src/service/utm/cookies-utm-adapter.js’
deps:
calls: [[‘setCookieService’, [‘@cookies’]]
query-utm-adapter:
path: ‘/src/service/utm/query-utm-adapter.js’
deps:
calls: [[‘setQueryService’, [‘@query’]]
query-utm-adapter-backside:
path: ‘src/service/utm/query-utm-adapter-backside.js’
deps:
calls: [[‘setQueryService’, [‘@query’]]
utm:
path: ‘src/service/utm/utm.js’
deps:
args: [
parameters: [
name: ‘utm_source’
required: true
,
name: ‘utm_content’
required: false
,
name: 'utm_term'
required: false
]
]
calls: [
[‘addSource’, [‘@cookies-utm-adapter’, 0]],
[‘addSource’, [‘@query-utm-adapter’, 1]],
[‘addSource’, [‘@query-utm-adapter-backside’, 2]]
]
* в данном примере конфиг представлен на coffeescript, символы @ означают ссылку на инстанс сервиса. Похожий формат конфигов используется в компоненте Symfony Dependency Injection [4].
Представим, что мы все это дело реализовали и закодили. Теперь все работает, но работает синхронно. Как же быть с «ожиданием» каких-то меток?
Пару слов о реализации.
Выбранное структурное решение имеет два логических уровня:
Для реализации асинхронного решения нам нужно внести изменения, как минимум, на первом уровне.
На уровне сервиса utm мы поменяем реализацию цикла опроса источников:
На уровне адаптеров мы добавим возвращение promise для тех меток, которые стоит ждать. Например, кука __utmz, которая выставляется после инициализации библиотеки ga.js (analytics.js) и может позволить определить некоторые метки.
Иногда, в начале проектирования решаемой задачи мы не всегда представляем себе ее сложность и все подводные камни. И вот в такие моменты хочется сделать максимально просто, но чрезмерное дробление кода наводит на мысли об over engineering и вообще немного пугает. В данном случае, «правильность» проектирования принесла свои плоды и значительно упростила дальнейшие модификации логики приложения.
Спасибо за внимание! Надеюсь, кому-нибудь поможет.
Автор: gobwas
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/48025
Ссылки в тексте:
[1] UTM: https://support.google.com/analytics/answer/1033863?hl=ru
[2] паттерн внедрения зависимостей: http://ru.wikipedia.org/wiki/%D0%92%D0%BD%D0%B5%D0%B4%D1%80%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B7%D0%B0%D0%B2%D0%B8%D1%81%D0%B8%D0%BC%D0%BE%D1%81%D1%82%D0%B8
[3] паттерн адаптер: http://ru.wikipedia.org/wiki/%D0%90%D0%B4%D0%B0%D0%BF%D1%82%D0%B5%D1%80_(%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)
[4] Symfony Dependency Injection: http://symfony.com/doc/current/components/dependency_injection/index.html
[5] async.js: https://github.com/caolan/async
[6] $.when: http://api.jquery.com/jQuery.when/
[7] Источник: http://habrahabr.ru/post/201674/
Нажмите здесь для печати.