Лучшая client-side
архитектура
Антон Плешивцев
twitter.com/allaud
github.com/allaud
aviasales.ru
Что мы разрабатываем?
Frontend-приложение
•
•
•
•

Поисковая форма
Поисковая выдача (1000+ билетов)
Фильтры (15 и более критериев)
Календарь цен (цены на год)
Frontend-приложение
Legacy
• Внутренний
framework
• 10 000 строк кода
• Виджет ориентированная
архитектура
Проблемы старого подхода
•
•
•
•
•

Много собственного кода
Велосипеды
Изобрели AMD
Отсутствие тестов
Высокий порог вхождения
«Жидкое приложение»

Поисковая
форма
«Жидкое приложение»
Выдача

Поиск

Поисковая
форма
«Жидкое приложение»
Состояние

Фильтры

Выдача

Поиск
Фильтры
Поисковая
форма
«Жидкое приложение»
Состояние

Фильтры

Выдача

Фидбек
Поиск
Билеты
Поисковая
форма

Агенства

История
«Жидкое приложение»
Состояние

Фильтры

Выдача

Фидбек
Поиск
Календарь
Билеты
Поисковая
форма

Агенства

История
«Жидкое приложение»
Состояние

Фильтры

Выдача

Фидбек
Поиск
Календарь
Билеты
Поисковая
форма

Агенства

История
Использовать jQuery-плагины?
JQuery jQueryUIJformer Hyjack SelectjQuery Sparkline
sasmSelect jQuery PluginjQuery Plugin BoilerplatejQuery Floater
PluginjQuery Queue plugin Apprise Jquery-toast message-plugin
Websanova Color Picker safeSubmit Plugin JratingjQuery Opineo
PluginFollow & Tweet WidgetjQuery Corner Gallery InputNotes
GmapPicNet Table FilterJquery UI datepickerActivity
IndicatorTitle AlertFix Scrollbar HeightBetter Check Boxes with
jQuery and CSSBetterTooltip
Frontend frameworks
•
•
•
•
•

Google Closure library
Backbone.js
AngularJS
EmberJS
…
First try
•
•
•
•

Backbone
RequireJS
HandleBars
Jasmine
Выводы
•
•
•
•

Стало лучше
Порог вхождения понизился
AMD-модули полезны
Функций недостаточно
Next try
• AngularJS
• Удобные тесты
• Почти нет кастомных
решений
• Очень высокая
скорость разработки
Приложение aviasales.
Как декомпозировать?
• Разбить на виджеты
• Разбить на модели
• Разбить на отдельные
приложения
Frontend-приложение
Как спроектировать?
Поисковая форма

Выдача

Фильтры
Как спроектировать?
Форма

История
поисков

Календарь

Цена

Отели

Время

Билеты

Авиакомпании
Как организовать общение между частями
приложения?

Календарь

Цена

Отели

Время

Билеты

?

Авиакомпании
Как организовать общение между частями
приложения?

Календарь

Цена

Отели

Время

Билеты

SERVICE

Авиакомпании
Как организовать общение между частями
приложения?
«Выдача с фильтрами»
Календарь

Цена

Отели

Время

Билеты

Авиакомпании
Как организовать модули?
Отели
Календарь

Время
Цена

Билеты
Как организовать модули?
Календарь
Отели
Билеты

Dependency
Injection

Цена
Время
Как собрать воедино?
Ядро

Ядро
Форма

История
поисков

Календарь

Цена

Отели

Время

Билеты

Авиаком

Форма Поиска

Выдача
Как собрать воедино?
Ядро
Выдача

Форма Поиска

Форма

История
поисков

Билеты

Фильтры

Цена

Запись
Календарь

Отели

Список
билетов

Время

…
ng-controller directive
CoreController

SearchResultsController

SearchFormController

Form

History

Results

Filters

Price

Record
Calendar

Hotels

Tickets

Time

…
Взаимодействие контроллеров
SearchResultsController

{
tickets: []
sort: function(){…}
}

Tickets
{
sorting: ‘price’
tickets: []
sort: function(){…}
}

{
filters: {}
tickets: []
}
{

Results

reset: function(){…}
filters: {}

Filters
}

Price
{
visible: [1000, 10 000]
reset: function(){}
filters: {}
}
Много небольших работающих
приложений
Приложение «Список билетов»
TicketsController

TicketFactory

BaseTicket
FiltrableTicket
RoundTripTicket

OnewayTicket

TourTicket
Шаблоны (было)
NANO("templates.searches.tickets.widgets.proposals", function(NANO){
return NANO.templates('<%= j render 'nano_ui_templates/searches/tickets/widgets/proposals' %>', {
"@data-ticket-id": "ticket.id",
"@data-source": "source",
".ticket_proposal": {
"gate<-proposals": {
".gate_name": "gate.name",
".gate_select_button a@href": "gate.url",
".gate_select_button a@data-gate": "gate.id",
".gate_price": "gate.price",
".gate_payment_methods .payment_method": {
"method<-gate.payment_methods": {
"@class+": " #{method}"
}
}
}
}
});
});
Шаблоны (стало)
%li.proposal-carousel{'ng-repeat' => 'proposal in ticket.proposals_for_carousel()'}
%a.agency_offer{'ng-click' => 'buy_ticket(ticket, proposal)'}
%span {{ proposal.name() | cut:15 }}
%span.price
%span {{ proposal.price() | current_price:search.currency }}
Unobtrusive javascript
layout.find(".yes").bind("click", function(){
self.toggle(layout, "yes");
});
layout.find(".no").bind("click", function(){
self.toggle(layout, "no");
});
layout.find("#new_feedback").submit(function(){
return self._submit();
});
Angular way
<form class="feedback" ng-submit="submit()">
<div class="yes" ng-click='success(true)'>Да</div>
<div class="no" ng-click='success(false)'>Нет</div>
...
</form>
Итоги
• Теперь делать фиксы и фичи могут
все
• Ускорили разработку в разы
• Части приложения используются в
других проектах
• Ядро поддерживает сообщество
About
Антон Плешивцев
!

twitter.com/allaud
github.com/allaud
https://www.facebook.com/ant.pl.3
!

aviasales.ru
Happydev presentation angular

Happydev presentation angular