ݺߣ

ݺߣShare a Scribd company logo
Пластилиновый код
Как перестать кодить и начать жить
Елена Шишкина,
ведущий программист
Деньги.Мэйл.Ру
Москва, 2015
1
ПОДУМАЕМ, КАК ЕГО НЕ ПИСАТЬ!
Надоело писать код?
2
С чего все начиналось
• Веб-сервис (JSON API)
– nginx
– Mojolicious
– PostgreSQL
– Вся логика в процедурах СУБД
• Архитектура веб-приложения
– Вертикальная нарезка на сервисы: auth, profile, contactlist, chat, …
– Горизонтальная нарезка
• www-layer
• Service layer
• Data layer
– Сервисы могут обращаться к друг другу через service layer
3
Типичная функция веб-слоя
• Проверка CSRF-токена
• Аутентификация
• Авторизация
• Чтение и валидация входных данных
• Обращение к сервисному слою
• Перехват и маппинг ошибок
• Генерация вывода
4
Функция веб-слоя
sub message {
my $self = shift;
my $result = eval {
my $form = $self->helper->read_form('chat/message');
die $form->export_errors if $form->has_errors;
die 'ERROR_CSRF_TOKEN'
unless $self->helper->token_ok($form);
die 'ERROR_NOT_AUTHORIZED'
unless $self->helper->check_auth($form);
$self->service->message($form->export);
};
unless ($result) {
my $err = $@;
$self->helper->logerr($err);
$result = $self->helper->map_error($err);
}
$self->render(json => $result);
}
5
Типичная функция сервисного слоя
• Обработка входящих данных
• Обращение к слою данных
• Сохранение в ленту активности пользователя
• Рассылка уведомлений
• Подготовка возвращаемых данных
–result: OK или код ошибки
–собственно данные:
• нет данных
• одно значение
• хэш
• массив хэшей
6
Функция сервисного слоя
sub message {
my ($self, $opts) = @_;
my $result = $self->data->message($opts);
$self->send_notify(chat_message => {
sender => $opts->{profile_id},
addressee => $result,
message => $opts->{message},
});
return $self->ok;
}
7
Типичная функция слоя данных
• Запрос данных из кэша (для статических запросов)
• Вызов процедуры СУБД
• Сохранение в кэш
• Инвалидация кэша
• Нормализация выходных данных
8
Функция слоя данных
sub contactlist {
my ($self, $opts) = @_;
my $cache_key = $self->to_cache_key(
'proifle.contactlist',
$opts
);
my $result = $self->cache->get($cache_key);
unless ($result) {
$result = $self->db->table(
'profiles.contactlist',
[ $opts->{profile_id} ]
);
$self->cache->set($cache_key, $result);
}
return $result;
}
9
Новая фича
• 1 процедура СУБД
• 3 копипасты с небольшими изменениями
В половине случаев меняются только названия,
ключи конфигов и имена процедур!
10
Можно как-нибудь так:
sub god_method {
my $self = shift;
my $cfg = $self->resolve;
my $result = eval {
my $form = $self->helper->read_form($cfg->{form});
die $form->export_errors if $form->has_errors;
if ($cfg->{check_token}) {
die 'ERROR_CSRF_TOKEN'
unless $self->helper->token_ok($form);
}
if ($cfg->{check_auth}) {
die 'ERROR_NOT_AUTHORIZED'
unless $self->helper->check_auth($form);
}
$self->service->god_method($cfg, $form->export);
};
unless ($result) {
my $err = $@;
$self->helper->logerr($err);
$result = $self->helper->map_error($err);
}
$self->render(json => $result);
}
Но это скучно!11
Будем генерировать методы на лету
• Не делаем лишних телодвижений: генерируем из
AUTOLOAD
• Чтобы не попросили странного, нам нужен список
разрешенных методов
• Генератор в базовом классе, списки методов – в
наследниках
• В наследниках можно описать вариации поведения
12
Проверка по списку разрешенных методов
sub _has_method {
my ($module, $method) = @_;
my $methods = ${ "$module::valid_methods" };
if (ref $methods && ref $methods eq 'HASH') {
return $methods->{$method};
} else {
return;
}
}
13
Метод-генератор
use Sub::Name;
sub _generate_sub {
my ($module, $method) = @_;
my $sub = subname "$module::$method", sub {
...
};
no strict 'refs';
*{"$module::$method"} = $sub;
return $sub;
}
14
AUTOLOAD
our $AUTOLOAD;
sub AUTOLOAD {
my $self = $_[0];
my ($method) = $AUTOLOAD =~ /^.+::(.*)$/;
my $package = blessed $self ? ref $self : $self;
return if !$method || $method eq 'DESTROY' || !
_has_method($package, $method);
my $sub = _generate_sub($package, $method);
goto ⊂
}
15
Oops! Mojolicious зовет can…
sub can {
my ($self, $method) = @_;
my $module = blessed $self ? ref $self : $self;
if (_has_method($module, $method)) {
return _generate_sub($module, $method);
} else {
return __PACKAGE__->SUPER::can($method);
}
}
16
Модули с фичами
use strict;
use warnings;
package MyProject::Controller::Chat;
#package MyProject::ServiceLayer::Chat;
#package MyProject::DataLayer::Chat;
use Mojo::Base 'MyProject::Controller::Base';
#use parent 'MyProject::ServiceLayer::Base';
#use parent 'MyProject::DataLayer::Base';
our $valid_methods = {
message => 1
};
1;
17
Схема модулей
MyProject::Base
_has_method
AUTOLOAD
MyProject::Controller::Base
_generate_sub
MyProject::ServiceLayer::Base
_generate_sub
MyProject::DataLayer::Base
_generate_sub
MyProject::Controller::Chat
$valid_methods
MyProject::ServiceLayer::Chat
$valid_methods
MyProject::DataLayer::Chat
$valid_methods
can
18
БОРЕМСЯ С ОДНОТИПНЫМ КОДОМ
19
Функция веб-слоя
Всегда:
• читает и валидирует входные
параметры
• зовет сервисный слой
• перехватывает и маппит ошибки
• генерирует вывод
Может:
• читать определение веб-формы
из разных конфигов
• проверять CSRF-токен
• проверять аутентификацию
• проверять авторизацию
20
Определение метода для веб-слоя
use strict;
use warnings;
package
MyProject::Controller::Chat;
use Mojo::Base
'MyProject::Controller::Base';
our $valid_methods = {
message => {
check_token => 1,
check_auth => 1,
form => 'chat/message'
}
};
1;
Немного упростим
• Токен будем проверять по
умолчанию
• Аутентификацию тоже будем
проверять по умолчанию
• Имя формы = имя модуля + имя
метода
21
Определение метода для веб-слоя
use strict;
use warnings;
package MyProject::Controller::Chat;
use Mojo::Base 'MyProject::Controller::Base';
our $valid_methods = {
message => { }
};
1;
22
Генератор для метода в веб-слое
23
sub _generate_sub {
my ($module, $method) = @_;
my $def = dclone(_get_definition($module, $method) || {});
my $form_name = _form_name($module, $method, $def);
$def->{check_token} = 1 unless exists $def->{check_token};
$def->{check_auth} = 1 unless exists $def->{check_auth};
my $service_method = $def->{service_method} || $method;
my $sub = subname "$module::$method", sub {
my $self = shift;
my $result = eval {
my $form = $self->helper->read_form($form_name);
die $form->export_errors if $form->has_errors;
if ($def->{check_token} && !$self->helper->token_ok($form)) {
die 'ERROR_CSRF_TOKEN';
}
if ($def->{check_auth} && !$self->helper->check_auth($form)) {
die 'ERROR_NOT_AUTHORIZED';
}
$self->service->$service_method($form->export);
};
unless ($result) {
my $err = $@;
$self->helper->logerr($err); $result = $self->helper->map_error($err);
}
$self->render(json => $result);
};
no strict 'refs'; *{"$module::$method"} = $sub;
return $sub;
} 24
Функция сервисного слоя
Всегда:
• обращается к слою данных
• подготавливает возвращаемые
данные
Может:
• обрабатывать входящие данные
• сохранять данные в ленту
активности пользователя
• рассылать уведомления
• возвращать данные в разных
структурах:
– нет данных (только код
результата)
– одно значение
– хэш
– массив хэшей
25
Определение метода для сервисного слоя
use strict;
use warnings;
package MyProject::ServiceLayer::Chat;
use parent 'MyProject::ServiceLayer::Base';
our $valid_methods = {
message => {
returns => 'none',
notify => 1,
save_history => 0
}
};
1;
26
Генератор метода для сервисного слоя
27
use Sub::Name;
use Storable qw(dclone);
sub _generate_sub {
my ($module, $method) = @_;
my $def = dclone(_get_definition($module, $method) || {});
my ($service) = $module =~ /^.+::(.*)$/;
my $data_method = $def->{data_method} || $method;
my $sub = subname "$module::$method", sub {
my ($self, $opts) = @_;
my $result = $self->service->$method($opts);
if ($def->{notify}) {
$self->send_notify((lc $service) . "_$method", $opts,
$result);
}
if ($def->{save_history}) {
$self->save_history((lc $service) . "_$method", $opts,
$result);
}
return $self->parse_answer($result, $def->{returns} || 'none');
};
no strict 'refs';
*{"$module::$method"} = $sub;
return $sub;
}
28
Функция слоя данных
Всегда:
• вызывает процедуру СУБД
Может:
• запрашивать данные из кэша
• передавать в процедуру разные
наборы параметров
• читать результат работы процедуры
в разном формате:
– нет возвращаемого значения
– одно значение
– строка
– таблица
• сохранять данные в кэш
• инвалидировать кэш
• нормализовывать выходные
данные
29
Определение метода для слоя данных
use strict;
use warnings;
package
MyProject::DataLayer::Chat;
use parent
'MyProject::DataLayer::Base';
our $valid_methods = {
message => {
args => [qw(profile_id
room_id reftime message)],
returns => 'table',
func => 'chat.message',
}
};
1;
Немного сократим
• Кэш по умолчанию не зовем и
не валидируем
• Имя процедуры базы строим по
шаблону:
– tablespace = имя модуля
– имя процедуры = имя метода
• Возвращаем по умолчанию
таблицу
30
Генератор метода для слоя данных
31
use Sub::Name;
use Storable qw(dclone);
sub _generate_sub {
my ($module, $method) = @_;
my $def = dclone(_get_definition($module, $method) || {});
my ($service) = $module =~ /^.+::(.*)$/;
$service = lc $service;
my $db_func = $def->{func} || $service . '.' . $method;
my $layer_func = $def->{returns} || 'table';
$layer_func = 'exec' if $layer_func eq 'none';
my $sub = subname "$module::$method", sub {
my ($self, $opts) = @_;
my $cache_key = ($def->{use_cache} || $def->{invalidate_cache})
? $self->to_cache_key($service . '.' . $method, $opts)
: undef;
my $result = $def->{use_cache}
? $self->cache->get($cache_key)
: undef;
unless ($result) {
$result = $self->db->$layer_func($db_func, @$opts{@{ $def->{args} }});
$self->cache->set($cache_key, $result) if $def->{use_cache};
}
$self->cache->invalidate($cache_key, $opts) if $def->{invalidate_cache};
return $result;
};
no strict 'refs';
*{"$module::$method"} = $sub;
return $sub;
}
32
Чего мы добились
• Поигрались с кодогенерацией
• Убрали дублирование кода
• Формализовали декларацию данных для генерации
методов
Получилась отличная модель, но…
33
БОРЕМСЯ С НЕОДНОТИПНЫМ КОДОМ
34
Гладко было на бумаге…
• После логина надо поставить куки
• После регистрации надо отправить email
• При добавлении фотографии нужно сохранить файл и
собрать метаинформацию
• При добавлении поста в ленту нужно распарсить и
обработать ссылки
• …
Нужен механизм для вызова произвольного кода!
35
Добавляем в определение метода коллбэки
prepare
• Вызывается до обращения к
нижележащему слою
• В качестве аргумента получает
входящие параметры метода
(для веб-слоя – объект формы)
• Может модифицировать
параметры (форму)
finish
• Вызывается после обращения к
нижележащему слою
• В качестве аргумента получает
данные, которые вернул
нижележащий слой
• Может модифицировать эти
данные
36
Для веб-слоя
our $valid_methods = {
method_name => {
prepare => sub {
my ($self, $form) = @_;
...
return $form;
},
finish => sub {
my ($self, $data) = @_;
...
return $data;
}
}
};
37
Чего мы добились
• Поигрались с кодогенерацией
• Убрали дублирование кода
• Формализовали декларацию данных для генерации
методов
• Научились добавлять вариативное поведение
Но нам все еще нужно добавлять по три файла,
в которых почти нет кода!
38
КОД, КОТОРОГО НЕ СУЩЕСТВУЕТ
39
Избавляемся от файлов-модулей
• Собираем воедино разрозненные определения методов
• Выделяем инструмент – генератор модулей
• Выносим отдельно заполнение определения методов
значениями по умолчанию
• Добавляем в определение HTTP-метод запроса (GET, POST,
PUT, DELETE)
• Строим роутинг веб-фреймворка
40
Новое определение метода в сервисе
• URL запроса (по умолчанию – имя_сервиса/имя_метода
• Метод запроса (по умолчанию – GET)
• Параметры веб-слоя
• Параметры сервисного слоя
• Параметры слоя данных
41
Параметры веб-слоя
• Проверка токена (по умолчанию включена)
• Проверка аутентификации (по умолчанию включена)
• Имя формы (по умолчанию имя_сервиса/имя_метода)
• Коллбэки prepare и finish (по умолчанию отсутствуют)
• Имя метода сервисного слоя (по умолчанию то же самое)
42
Параметры сервисного слоя
• Имя метода слоя данных (по умолчанию то же самое)
• Отправка уведомлений (по умолчанию выключена)
• Сохранение в историю (по умолчанию выключена)
• Формат возвращаемых данных (по умолчанию
определяется слоем данных)
• Коллбэки prepare и finish (по умолчанию отсутствуют)
43
Параметры слоя данных
• Взятие данных из кэша (по умолчанию выключено)
• Инвалидация кэша (по умолчанию выключена)
• Имя процедуры СУБД (по умолчанию
имя_сервиса.имя_метода)
• Набор входящих аргументов процедуры СУБД
• Формат возвращаемых данных (по умолчанию – таблица)
44
Простейшее определение метода
my $services => {
chat => {
message => {
data_layer => {
args => [qw(profile_id room_id reftime
message)],
},
},
},
};
45
Генератор сервиса
• package MyProject::Core::ServiceGenerator;
sub init_service {
my ($self, $service_name, $definition) = @_;
$service_name = ucfirst $service_name;
no strict 'refs';
$definition = $self->normalize_definition($definition);
for my $layer (qw(Controller ServiceLayer DataLayer)) {
unshift @{*{ "MyProject::$layer::$service_name::ISA" }},
'MyProject::$layer::Base';
*{ "MyProject::$layer::$service_name::_get_definition" } =
sub {
return $definition;
};
}
my $method = lc($definition->{method});
$self->routes->$method($definition->{url})->to(
controller => $service_name,
action => $name
);
}
46
КОД, КОТОРЫЙ СУЩЕСТВУЕТ
47
Код, который существует
• Множество уже написанных модулей
• Нестардартные методы
48
Добавляем в генератор сервиса проверку ISA
my $module = "MyProject::$layer::$service_name";
unless ($module->isa('MyProject::$layer::Base')) {
unshift @{*{ "$module::ISA" }}, 'MyProject::$layer::Base';
}
*{ "MyProject::$layer::$service_name::_get_definition" } = sub {
return $definition;
};
49
С AUTOLOAD все ОК, но can надо поправить
sub can {
my ($self, $method) = @_;
my $module = blessed $self ? ref $self : $self;
no strict 'refs';
if (my $sub = *{"$module::$method"}{CODE}) {
return $sub;
} elsif (_has_method($module, $method)) {
return _generate_sub($module, $method);
} else {
return __PACKAGE__->SUPER::can($method);
}
}
50
Чего мы добились
• Поигрались с кодогенерацией
• Убрали дублирование кода
• Формализовали декларацию данных для генерации методов
• Научились добавлять вариативное поведение
• Собрали определение сервисов и методов воедино
• Сделали определение типичных методов максимально
лаконичным
• Для новых и отлично ложащихся в шаблон сервисов мы даже
избавились от модулей
• Но при этом сохранили обратную совместимость
• А также возможность добавлять нестандартные методы
• Ленивая инициализация – при запуске сервера не генерируется
ничего лишнего
Но определение сервиса и метода –
это все еще код! 51
СЛЕДИТЕ ЗА РУКАМИ:
ПРОГРАММИРУЕМ НА КОНФИГАХ!
52
Декларациям место в текстовом формате
• Окончательно разделяем код и декларации
• Коллбэки prepare и finish выносим в модули, а в
декларациях оставляем имя модуля и функции
• Более компактный формат
• Легкое отключение сервиса на отдельных нодах: просто
удаляем конфиг!
53
Пример конфига
service: Chat
create_room:
web_layer:
form: chat/create_room
check_token: 1
check_auth: 1
method: put
url: common_chat/room/create
service_layer:
returns: room_id
finish: MyProject::Callback::Chat::gather_userinfo
notify: 1
data_layer:
func: chat.create_room
args: [profile_id, room_name, participant_id]
returns: single
54
ЧТО ДАЛЬШЕ?
55
ЕСТЬ ВОПРОСЫ?
56

More Related Content

What's hot (20)

TypeScript: особенности разработки / Александр Майоров (Tutu.ru)
TypeScript: особенности разработки / Александр Майоров (Tutu.ru)TypeScript: особенности разработки / Александр Майоров (Tutu.ru)
TypeScript: особенности разработки / Александр Майоров (Tutu.ru)
Ontico
Суперсилы Chrome developer tools
Суперсилы Chrome developer toolsСуперсилы Chrome developer tools
Суперсилы Chrome developer tools
2ГИС Технологии
UWDC 2013, Yii2
UWDC 2013, Yii2UWDC 2013, Yii2
UWDC 2013, Yii2
Alexander Makarov
Decorators' recipes
Decorators' recipesDecorators' recipes
Decorators' recipes
Yury Yurevich
Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1
Vasya Petrov
UWDC 2013, Как мы используем Yii
UWDC 2013, Как мы используем YiiUWDC 2013, Как мы используем Yii
UWDC 2013, Как мы используем Yii
Alexander Makarov
TARS: Сделай уровень frontend-рутины 0% — Артём Малко, 2ГИС
TARS: Сделай уровень frontend-рутины 0% — Артём Малко, 2ГИСTARS: Сделай уровень frontend-рутины 0% — Артём Малко, 2ГИС
TARS: Сделай уровень frontend-рутины 0% — Артём Малко, 2ГИС
2ГИС Технологии
Easy authcache 2 кеширование для pro родионов игорь
Easy authcache 2   кеширование для pro родионов игорьEasy authcache 2   кеширование для pro родионов игорь
Easy authcache 2 кеширование для pro родионов игорь
drupalconf
Component Inspector
Component InspectorComponent Inspector
Component Inspector
Roman Dvornov
Perl: Symbol table
Perl: Symbol tablePerl: Symbol table
Perl: Symbol table
Elena Shishkina
Как перестать отлаживать асинхронный код и начать жить / Андрей Саломатин (Pr...
Как перестать отлаживать асинхронный код и начать жить / Андрей Саломатин (Pr...Как перестать отлаживать асинхронный код и начать жить / Андрей Саломатин (Pr...
Как перестать отлаживать асинхронный код и начать жить / Андрей Саломатин (Pr...
Ontico
Что нового в Perl? 5.10 — 5.16
Что нового в Perl? 5.10 — 5.16Что нового в Perl? 5.10 — 5.16
Что нового в Perl? 5.10 — 5.16
Anatoly Sharifulin
DevConf 2012 - Yii, его разработка и Yii2
DevConf 2012 - Yii, его разработка и Yii2DevConf 2012 - Yii, его разработка и Yii2
DevConf 2012 - Yii, его разработка и Yii2
Alexander Makarov
Юрий Гольцев - Сервис PLWWW
Юрий Гольцев - Сервис PLWWWЮрий Гольцев - Сервис PLWWW
Юрий Гольцев - Сервис PLWWW
Positive Hack Days
Программирование Linux
Программирование LinuxПрограммирование Linux
Программирование Linux
Anthony Shoumikhin
plwww (24.03) MEPHI (PHDays)
plwww (24.03) MEPHI (PHDays)plwww (24.03) MEPHI (PHDays)
plwww (24.03) MEPHI (PHDays)
ygoltsev
Блоки, лямбды, замыкания
Блоки, лямбды, замыканияБлоки, лямбды, замыкания
Блоки, лямбды, замыкания
Dmitriy Kiriyenko
Контроль качества верстки или как начать делать Makeup
Контроль качества верстки или как начать делать MakeupКонтроль качества верстки или как начать делать Makeup
Контроль качества верстки или как начать делать Makeup
Timophy Chaptykov
Javascript
JavascriptJavascript
Javascript
Vasya Petrov
TypeScript: особенности разработки / Александр Майоров (Tutu.ru)
TypeScript: особенности разработки / Александр Майоров (Tutu.ru)TypeScript: особенности разработки / Александр Майоров (Tutu.ru)
TypeScript: особенности разработки / Александр Майоров (Tutu.ru)
Ontico
Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1Подробная презентация JavaScript 6 в 1
Подробная презентация JavaScript 6 в 1
Vasya Petrov
UWDC 2013, Как мы используем Yii
UWDC 2013, Как мы используем YiiUWDC 2013, Как мы используем Yii
UWDC 2013, Как мы используем Yii
Alexander Makarov
TARS: Сделай уровень frontend-рутины 0% — Артём Малко, 2ГИС
TARS: Сделай уровень frontend-рутины 0% — Артём Малко, 2ГИСTARS: Сделай уровень frontend-рутины 0% — Артём Малко, 2ГИС
TARS: Сделай уровень frontend-рутины 0% — Артём Малко, 2ГИС
2ГИС Технологии
Easy authcache 2 кеширование для pro родионов игорь
Easy authcache 2   кеширование для pro родионов игорьEasy authcache 2   кеширование для pro родионов игорь
Easy authcache 2 кеширование для pro родионов игорь
drupalconf
Как перестать отлаживать асинхронный код и начать жить / Андрей Саломатин (Pr...
Как перестать отлаживать асинхронный код и начать жить / Андрей Саломатин (Pr...Как перестать отлаживать асинхронный код и начать жить / Андрей Саломатин (Pr...
Как перестать отлаживать асинхронный код и начать жить / Андрей Саломатин (Pr...
Ontico
Что нового в Perl? 5.10 — 5.16
Что нового в Perl? 5.10 — 5.16Что нового в Perl? 5.10 — 5.16
Что нового в Perl? 5.10 — 5.16
Anatoly Sharifulin
DevConf 2012 - Yii, его разработка и Yii2
DevConf 2012 - Yii, его разработка и Yii2DevConf 2012 - Yii, его разработка и Yii2
DevConf 2012 - Yii, его разработка и Yii2
Alexander Makarov
Юрий Гольцев - Сервис PLWWW
Юрий Гольцев - Сервис PLWWWЮрий Гольцев - Сервис PLWWW
Юрий Гольцев - Сервис PLWWW
Positive Hack Days
Программирование Linux
Программирование LinuxПрограммирование Linux
Программирование Linux
Anthony Shoumikhin
plwww (24.03) MEPHI (PHDays)
plwww (24.03) MEPHI (PHDays)plwww (24.03) MEPHI (PHDays)
plwww (24.03) MEPHI (PHDays)
ygoltsev
Блоки, лямбды, замыкания
Блоки, лямбды, замыканияБлоки, лямбды, замыкания
Блоки, лямбды, замыкания
Dmitriy Kiriyenko
Контроль качества верстки или как начать делать Makeup
Контроль качества верстки или как начать делать MakeupКонтроль качества верстки или как начать делать Makeup
Контроль качества верстки или как начать делать Makeup
Timophy Chaptykov

Similar to Пластилиновый код: как перестать кодить и начать жить (20)

Psgi app
Psgi appPsgi app
Psgi app
und3f
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
Архитектура кода нового 2ГИС Web API или куда мы дели MVCАрхитектура кода нового 2ГИС Web API или куда мы дели MVC
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
DevDay
Easy authcache 2 кэширование для pro. Родионов Игорь
Easy authcache 2   кэширование для pro. Родионов ИгорьEasy authcache 2   кэширование для pro. Родионов Игорь
Easy authcache 2 кэширование для pro. Родионов Игорь
PVasili
Web весна 2013 лекция 4
Web весна 2013 лекция 4Web весна 2013 лекция 4
Web весна 2013 лекция 4
Technopark
Web осень 2012 лекция 4
Web осень 2012 лекция 4Web осень 2012 лекция 4
Web осень 2012 лекция 4
Technopark
М. Боднарчук Современное функциональное тестирование с Codeception
М. Боднарчук Современное функциональное тестирование с CodeceptionМ. Боднарчук Современное функциональное тестирование с Codeception
М. Боднарчук Современное функциональное тестирование с Codeception
Albina Tiupa
Михаил Боднарчук Современное функциональное тестирование с Codeception
Михаил Боднарчук Современное функциональное тестирование с CodeceptionМихаил Боднарчук Современное функциональное тестирование с Codeception
Михаил Боднарчук Современное функциональное тестирование с Codeception
Albina Tiupa
Behat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и MinkBehat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и Mink
tyomo4ka
Perl – жив?!
Perl – жив?!Perl – жив?!
Perl – жив?!
Anatoly Sharifulin
Yii2
Yii2Yii2
Yii2
Noveo
Опыт разработки и тестирования RESTful JSON сервиса
Опыт разработки и тестирования RESTful JSON сервисаОпыт разработки и тестирования RESTful JSON сервиса
Опыт разработки и тестирования RESTful JSON сервиса
Ilya Chesnokov
HSE{Consult}: DevOps – новая методология разработки
HSE{Consult}: DevOps – новая методология разработкиHSE{Consult}: DevOps – новая методология разработки
HSE{Consult}: DevOps – новая методология разработки
Business incubator HSE
DevOps или исскуство ухода за Интернет-проектом
DevOps или исскуство ухода за Интернет-проектомDevOps или исскуство ухода за Интернет-проектом
DevOps или исскуство ухода за Интернет-проектом
Alexander Titov
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
Архитектура кода нового 2ГИС Web API или куда мы дели MVCАрхитектура кода нового 2ГИС Web API или куда мы дели MVC
Архитектура кода нового 2ГИС Web API или куда мы дели MVC
DevDay
Easy authcache 2 кэширование для pro. Родионов Игорь
Easy authcache 2   кэширование для pro. Родионов ИгорьEasy authcache 2   кэширование для pro. Родионов Игорь
Easy authcache 2 кэширование для pro. Родионов Игорь
PVasili
Web весна 2013 лекция 4
Web весна 2013 лекция 4Web весна 2013 лекция 4
Web весна 2013 лекция 4
Technopark
Web осень 2012 лекция 4
Web осень 2012 лекция 4Web осень 2012 лекция 4
Web осень 2012 лекция 4
Technopark
М. Боднарчук Современное функциональное тестирование с Codeception
М. Боднарчук Современное функциональное тестирование с CodeceptionМ. Боднарчук Современное функциональное тестирование с Codeception
М. Боднарчук Современное функциональное тестирование с Codeception
Albina Tiupa
Михаил Боднарчук Современное функциональное тестирование с Codeception
Михаил Боднарчук Современное функциональное тестирование с CodeceptionМихаил Боднарчук Современное функциональное тестирование с Codeception
Михаил Боднарчук Современное функциональное тестирование с Codeception
Albina Tiupa
Behat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и MinkBehat в PHP с использованием Behat и Mink
Behat в PHP с использованием Behat и Mink
tyomo4ka
Опыт разработки и тестирования RESTful JSON сервиса
Опыт разработки и тестирования RESTful JSON сервисаОпыт разработки и тестирования RESTful JSON сервиса
Опыт разработки и тестирования RESTful JSON сервиса
Ilya Chesnokov
HSE{Consult}: DevOps – новая методология разработки
HSE{Consult}: DevOps – новая методология разработкиHSE{Consult}: DevOps – новая методология разработки
HSE{Consult}: DevOps – новая методология разработки
Business incubator HSE
DevOps или исскуство ухода за Интернет-проектом
DevOps или исскуство ухода за Интернет-проектомDevOps или исскуство ухода за Интернет-проектом
DevOps или исскуство ухода за Интернет-проектом
Alexander Titov

More from Moscow.pm (9)

О работе с документами .xls, .xlsx, .rtf
О работе с документами .xls, .xlsx, .rtfО работе с документами .xls, .xlsx, .rtf
О работе с документами .xls, .xlsx, .rtf
Moscow.pm
Fast queue – как мы сделали свою очередь на perl и redis
Fast queue – как мы сделали свою очередь на perl и redisFast queue – как мы сделали свою очередь на perl и redis
Fast queue – как мы сделали свою очередь на perl и redis
Moscow.pm
Perl для не программистов. Николай Мишин. Moscow.pm 4 июля 2013
Perl для не программистов. Николай Мишин. Moscow.pm 4 июля 2013Perl для не программистов. Николай Мишин. Moscow.pm 4 июля 2013
Perl для не программистов. Николай Мишин. Moscow.pm 4 июля 2013
Moscow.pm
Язык Go для Perl-программистов v1.1. Александр Орловский. Moscow.pm 4 июля 2013
Язык Go для Perl-программистов v1.1. Александр Орловский. Moscow.pm 4 июля 2013Язык Go для Perl-программистов v1.1. Александр Орловский. Moscow.pm 4 июля 2013
Язык Go для Perl-программистов v1.1. Александр Орловский. Moscow.pm 4 июля 2013
Moscow.pm
Разработка документации для RESTful API: как убить трёх зайцев одним. Moscow....
Разработка документации для RESTful API: как убить трёх зайцев одним. Moscow....Разработка документации для RESTful API: как убить трёх зайцев одним. Moscow....
Разработка документации для RESTful API: как убить трёх зайцев одним. Moscow....
Moscow.pm
Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...
Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...
Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...
Moscow.pm
Ленивые итераторы для разбора разнородных данных. Михаил Озеров. Moscow.pm 6 ...
Ленивые итераторы для разбора разнородных данных. Михаил Озеров. Moscow.pm 6 ...Ленивые итераторы для разбора разнородных данных. Михаил Озеров. Moscow.pm 6 ...
Ленивые итераторы для разбора разнородных данных. Михаил Озеров. Moscow.pm 6 ...
Moscow.pm
Преобразование Perl-структур в XML. Трефилова Екатерина. Moscow.pm 6 июля 2013
Преобразование Perl-структур в XML. Трефилова Екатерина. Moscow.pm 6 июля 2013Преобразование Perl-структур в XML. Трефилова Екатерина. Moscow.pm 6 июля 2013
Преобразование Perl-структур в XML. Трефилова Екатерина. Moscow.pm 6 июля 2013
Moscow.pm
Play Perl — распределенная социальная игра для Perl-разработчиков. Вячеслав М...
Play Perl — распределенная социальная игра для Perl-разработчиков. Вячеслав М...Play Perl — распределенная социальная игра для Perl-разработчиков. Вячеслав М...
Play Perl — распределенная социальная игра для Perl-разработчиков. Вячеслав М...
Moscow.pm
О работе с документами .xls, .xlsx, .rtf
О работе с документами .xls, .xlsx, .rtfО работе с документами .xls, .xlsx, .rtf
О работе с документами .xls, .xlsx, .rtf
Moscow.pm
Fast queue – как мы сделали свою очередь на perl и redis
Fast queue – как мы сделали свою очередь на perl и redisFast queue – как мы сделали свою очередь на perl и redis
Fast queue – как мы сделали свою очередь на perl и redis
Moscow.pm
Perl для не программистов. Николай Мишин. Moscow.pm 4 июля 2013
Perl для не программистов. Николай Мишин. Moscow.pm 4 июля 2013Perl для не программистов. Николай Мишин. Moscow.pm 4 июля 2013
Perl для не программистов. Николай Мишин. Moscow.pm 4 июля 2013
Moscow.pm
Язык Go для Perl-программистов v1.1. Александр Орловский. Moscow.pm 4 июля 2013
Язык Go для Perl-программистов v1.1. Александр Орловский. Moscow.pm 4 июля 2013Язык Go для Perl-программистов v1.1. Александр Орловский. Moscow.pm 4 июля 2013
Язык Go для Perl-программистов v1.1. Александр Орловский. Moscow.pm 4 июля 2013
Moscow.pm
Разработка документации для RESTful API: как убить трёх зайцев одним. Moscow....
Разработка документации для RESTful API: как убить трёх зайцев одним. Moscow....Разработка документации для RESTful API: как убить трёх зайцев одним. Moscow....
Разработка документации для RESTful API: как убить трёх зайцев одним. Moscow....
Moscow.pm
Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...
Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...
Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...
Moscow.pm
Ленивые итераторы для разбора разнородных данных. Михаил Озеров. Moscow.pm 6 ...
Ленивые итераторы для разбора разнородных данных. Михаил Озеров. Moscow.pm 6 ...Ленивые итераторы для разбора разнородных данных. Михаил Озеров. Moscow.pm 6 ...
Ленивые итераторы для разбора разнородных данных. Михаил Озеров. Moscow.pm 6 ...
Moscow.pm
Преобразование Perl-структур в XML. Трефилова Екатерина. Moscow.pm 6 июля 2013
Преобразование Perl-структур в XML. Трефилова Екатерина. Moscow.pm 6 июля 2013Преобразование Perl-структур в XML. Трефилова Екатерина. Moscow.pm 6 июля 2013
Преобразование Perl-структур в XML. Трефилова Екатерина. Moscow.pm 6 июля 2013
Moscow.pm
Play Perl — распределенная социальная игра для Perl-разработчиков. Вячеслав М...
Play Perl — распределенная социальная игра для Perl-разработчиков. Вячеслав М...Play Perl — распределенная социальная игра для Perl-разработчиков. Вячеслав М...
Play Perl — распределенная социальная игра для Perl-разработчиков. Вячеслав М...
Moscow.pm

Пластилиновый код: как перестать кодить и начать жить

  • 1. Пластилиновый код Как перестать кодить и начать жить Елена Шишкина, ведущий программист Деньги.Мэйл.Ру Москва, 2015 1
  • 2. ПОДУМАЕМ, КАК ЕГО НЕ ПИСАТЬ! Надоело писать код? 2
  • 3. С чего все начиналось • Веб-сервис (JSON API) – nginx – Mojolicious – PostgreSQL – Вся логика в процедурах СУБД • Архитектура веб-приложения – Вертикальная нарезка на сервисы: auth, profile, contactlist, chat, … – Горизонтальная нарезка • www-layer • Service layer • Data layer – Сервисы могут обращаться к друг другу через service layer 3
  • 4. Типичная функция веб-слоя • Проверка CSRF-токена • Аутентификация • Авторизация • Чтение и валидация входных данных • Обращение к сервисному слою • Перехват и маппинг ошибок • Генерация вывода 4
  • 5. Функция веб-слоя sub message { my $self = shift; my $result = eval { my $form = $self->helper->read_form('chat/message'); die $form->export_errors if $form->has_errors; die 'ERROR_CSRF_TOKEN' unless $self->helper->token_ok($form); die 'ERROR_NOT_AUTHORIZED' unless $self->helper->check_auth($form); $self->service->message($form->export); }; unless ($result) { my $err = $@; $self->helper->logerr($err); $result = $self->helper->map_error($err); } $self->render(json => $result); } 5
  • 6. Типичная функция сервисного слоя • Обработка входящих данных • Обращение к слою данных • Сохранение в ленту активности пользователя • Рассылка уведомлений • Подготовка возвращаемых данных –result: OK или код ошибки –собственно данные: • нет данных • одно значение • хэш • массив хэшей 6
  • 7. Функция сервисного слоя sub message { my ($self, $opts) = @_; my $result = $self->data->message($opts); $self->send_notify(chat_message => { sender => $opts->{profile_id}, addressee => $result, message => $opts->{message}, }); return $self->ok; } 7
  • 8. Типичная функция слоя данных • Запрос данных из кэша (для статических запросов) • Вызов процедуры СУБД • Сохранение в кэш • Инвалидация кэша • Нормализация выходных данных 8
  • 9. Функция слоя данных sub contactlist { my ($self, $opts) = @_; my $cache_key = $self->to_cache_key( 'proifle.contactlist', $opts ); my $result = $self->cache->get($cache_key); unless ($result) { $result = $self->db->table( 'profiles.contactlist', [ $opts->{profile_id} ] ); $self->cache->set($cache_key, $result); } return $result; } 9
  • 10. Новая фича • 1 процедура СУБД • 3 копипасты с небольшими изменениями В половине случаев меняются только названия, ключи конфигов и имена процедур! 10
  • 11. Можно как-нибудь так: sub god_method { my $self = shift; my $cfg = $self->resolve; my $result = eval { my $form = $self->helper->read_form($cfg->{form}); die $form->export_errors if $form->has_errors; if ($cfg->{check_token}) { die 'ERROR_CSRF_TOKEN' unless $self->helper->token_ok($form); } if ($cfg->{check_auth}) { die 'ERROR_NOT_AUTHORIZED' unless $self->helper->check_auth($form); } $self->service->god_method($cfg, $form->export); }; unless ($result) { my $err = $@; $self->helper->logerr($err); $result = $self->helper->map_error($err); } $self->render(json => $result); } Но это скучно!11
  • 12. Будем генерировать методы на лету • Не делаем лишних телодвижений: генерируем из AUTOLOAD • Чтобы не попросили странного, нам нужен список разрешенных методов • Генератор в базовом классе, списки методов – в наследниках • В наследниках можно описать вариации поведения 12
  • 13. Проверка по списку разрешенных методов sub _has_method { my ($module, $method) = @_; my $methods = ${ "$module::valid_methods" }; if (ref $methods && ref $methods eq 'HASH') { return $methods->{$method}; } else { return; } } 13
  • 14. Метод-генератор use Sub::Name; sub _generate_sub { my ($module, $method) = @_; my $sub = subname "$module::$method", sub { ... }; no strict 'refs'; *{"$module::$method"} = $sub; return $sub; } 14
  • 15. AUTOLOAD our $AUTOLOAD; sub AUTOLOAD { my $self = $_[0]; my ($method) = $AUTOLOAD =~ /^.+::(.*)$/; my $package = blessed $self ? ref $self : $self; return if !$method || $method eq 'DESTROY' || ! _has_method($package, $method); my $sub = _generate_sub($package, $method); goto ⊂ } 15
  • 16. Oops! Mojolicious зовет can… sub can { my ($self, $method) = @_; my $module = blessed $self ? ref $self : $self; if (_has_method($module, $method)) { return _generate_sub($module, $method); } else { return __PACKAGE__->SUPER::can($method); } } 16
  • 17. Модули с фичами use strict; use warnings; package MyProject::Controller::Chat; #package MyProject::ServiceLayer::Chat; #package MyProject::DataLayer::Chat; use Mojo::Base 'MyProject::Controller::Base'; #use parent 'MyProject::ServiceLayer::Base'; #use parent 'MyProject::DataLayer::Base'; our $valid_methods = { message => 1 }; 1; 17
  • 20. Функция веб-слоя Всегда: • читает и валидирует входные параметры • зовет сервисный слой • перехватывает и маппит ошибки • генерирует вывод Может: • читать определение веб-формы из разных конфигов • проверять CSRF-токен • проверять аутентификацию • проверять авторизацию 20
  • 21. Определение метода для веб-слоя use strict; use warnings; package MyProject::Controller::Chat; use Mojo::Base 'MyProject::Controller::Base'; our $valid_methods = { message => { check_token => 1, check_auth => 1, form => 'chat/message' } }; 1; Немного упростим • Токен будем проверять по умолчанию • Аутентификацию тоже будем проверять по умолчанию • Имя формы = имя модуля + имя метода 21
  • 22. Определение метода для веб-слоя use strict; use warnings; package MyProject::Controller::Chat; use Mojo::Base 'MyProject::Controller::Base'; our $valid_methods = { message => { } }; 1; 22
  • 23. Генератор для метода в веб-слое 23
  • 24. sub _generate_sub { my ($module, $method) = @_; my $def = dclone(_get_definition($module, $method) || {}); my $form_name = _form_name($module, $method, $def); $def->{check_token} = 1 unless exists $def->{check_token}; $def->{check_auth} = 1 unless exists $def->{check_auth}; my $service_method = $def->{service_method} || $method; my $sub = subname "$module::$method", sub { my $self = shift; my $result = eval { my $form = $self->helper->read_form($form_name); die $form->export_errors if $form->has_errors; if ($def->{check_token} && !$self->helper->token_ok($form)) { die 'ERROR_CSRF_TOKEN'; } if ($def->{check_auth} && !$self->helper->check_auth($form)) { die 'ERROR_NOT_AUTHORIZED'; } $self->service->$service_method($form->export); }; unless ($result) { my $err = $@; $self->helper->logerr($err); $result = $self->helper->map_error($err); } $self->render(json => $result); }; no strict 'refs'; *{"$module::$method"} = $sub; return $sub; } 24
  • 25. Функция сервисного слоя Всегда: • обращается к слою данных • подготавливает возвращаемые данные Может: • обрабатывать входящие данные • сохранять данные в ленту активности пользователя • рассылать уведомления • возвращать данные в разных структурах: – нет данных (только код результата) – одно значение – хэш – массив хэшей 25
  • 26. Определение метода для сервисного слоя use strict; use warnings; package MyProject::ServiceLayer::Chat; use parent 'MyProject::ServiceLayer::Base'; our $valid_methods = { message => { returns => 'none', notify => 1, save_history => 0 } }; 1; 26
  • 27. Генератор метода для сервисного слоя 27
  • 28. use Sub::Name; use Storable qw(dclone); sub _generate_sub { my ($module, $method) = @_; my $def = dclone(_get_definition($module, $method) || {}); my ($service) = $module =~ /^.+::(.*)$/; my $data_method = $def->{data_method} || $method; my $sub = subname "$module::$method", sub { my ($self, $opts) = @_; my $result = $self->service->$method($opts); if ($def->{notify}) { $self->send_notify((lc $service) . "_$method", $opts, $result); } if ($def->{save_history}) { $self->save_history((lc $service) . "_$method", $opts, $result); } return $self->parse_answer($result, $def->{returns} || 'none'); }; no strict 'refs'; *{"$module::$method"} = $sub; return $sub; } 28
  • 29. Функция слоя данных Всегда: • вызывает процедуру СУБД Может: • запрашивать данные из кэша • передавать в процедуру разные наборы параметров • читать результат работы процедуры в разном формате: – нет возвращаемого значения – одно значение – строка – таблица • сохранять данные в кэш • инвалидировать кэш • нормализовывать выходные данные 29
  • 30. Определение метода для слоя данных use strict; use warnings; package MyProject::DataLayer::Chat; use parent 'MyProject::DataLayer::Base'; our $valid_methods = { message => { args => [qw(profile_id room_id reftime message)], returns => 'table', func => 'chat.message', } }; 1; Немного сократим • Кэш по умолчанию не зовем и не валидируем • Имя процедуры базы строим по шаблону: – tablespace = имя модуля – имя процедуры = имя метода • Возвращаем по умолчанию таблицу 30
  • 31. Генератор метода для слоя данных 31
  • 32. use Sub::Name; use Storable qw(dclone); sub _generate_sub { my ($module, $method) = @_; my $def = dclone(_get_definition($module, $method) || {}); my ($service) = $module =~ /^.+::(.*)$/; $service = lc $service; my $db_func = $def->{func} || $service . '.' . $method; my $layer_func = $def->{returns} || 'table'; $layer_func = 'exec' if $layer_func eq 'none'; my $sub = subname "$module::$method", sub { my ($self, $opts) = @_; my $cache_key = ($def->{use_cache} || $def->{invalidate_cache}) ? $self->to_cache_key($service . '.' . $method, $opts) : undef; my $result = $def->{use_cache} ? $self->cache->get($cache_key) : undef; unless ($result) { $result = $self->db->$layer_func($db_func, @$opts{@{ $def->{args} }}); $self->cache->set($cache_key, $result) if $def->{use_cache}; } $self->cache->invalidate($cache_key, $opts) if $def->{invalidate_cache}; return $result; }; no strict 'refs'; *{"$module::$method"} = $sub; return $sub; } 32
  • 33. Чего мы добились • Поигрались с кодогенерацией • Убрали дублирование кода • Формализовали декларацию данных для генерации методов Получилась отличная модель, но… 33
  • 35. Гладко было на бумаге… • После логина надо поставить куки • После регистрации надо отправить email • При добавлении фотографии нужно сохранить файл и собрать метаинформацию • При добавлении поста в ленту нужно распарсить и обработать ссылки • … Нужен механизм для вызова произвольного кода! 35
  • 36. Добавляем в определение метода коллбэки prepare • Вызывается до обращения к нижележащему слою • В качестве аргумента получает входящие параметры метода (для веб-слоя – объект формы) • Может модифицировать параметры (форму) finish • Вызывается после обращения к нижележащему слою • В качестве аргумента получает данные, которые вернул нижележащий слой • Может модифицировать эти данные 36
  • 37. Для веб-слоя our $valid_methods = { method_name => { prepare => sub { my ($self, $form) = @_; ... return $form; }, finish => sub { my ($self, $data) = @_; ... return $data; } } }; 37
  • 38. Чего мы добились • Поигрались с кодогенерацией • Убрали дублирование кода • Формализовали декларацию данных для генерации методов • Научились добавлять вариативное поведение Но нам все еще нужно добавлять по три файла, в которых почти нет кода! 38
  • 39. КОД, КОТОРОГО НЕ СУЩЕСТВУЕТ 39
  • 40. Избавляемся от файлов-модулей • Собираем воедино разрозненные определения методов • Выделяем инструмент – генератор модулей • Выносим отдельно заполнение определения методов значениями по умолчанию • Добавляем в определение HTTP-метод запроса (GET, POST, PUT, DELETE) • Строим роутинг веб-фреймворка 40
  • 41. Новое определение метода в сервисе • URL запроса (по умолчанию – имя_сервиса/имя_метода • Метод запроса (по умолчанию – GET) • Параметры веб-слоя • Параметры сервисного слоя • Параметры слоя данных 41
  • 42. Параметры веб-слоя • Проверка токена (по умолчанию включена) • Проверка аутентификации (по умолчанию включена) • Имя формы (по умолчанию имя_сервиса/имя_метода) • Коллбэки prepare и finish (по умолчанию отсутствуют) • Имя метода сервисного слоя (по умолчанию то же самое) 42
  • 43. Параметры сервисного слоя • Имя метода слоя данных (по умолчанию то же самое) • Отправка уведомлений (по умолчанию выключена) • Сохранение в историю (по умолчанию выключена) • Формат возвращаемых данных (по умолчанию определяется слоем данных) • Коллбэки prepare и finish (по умолчанию отсутствуют) 43
  • 44. Параметры слоя данных • Взятие данных из кэша (по умолчанию выключено) • Инвалидация кэша (по умолчанию выключена) • Имя процедуры СУБД (по умолчанию имя_сервиса.имя_метода) • Набор входящих аргументов процедуры СУБД • Формат возвращаемых данных (по умолчанию – таблица) 44
  • 45. Простейшее определение метода my $services => { chat => { message => { data_layer => { args => [qw(profile_id room_id reftime message)], }, }, }, }; 45
  • 46. Генератор сервиса • package MyProject::Core::ServiceGenerator; sub init_service { my ($self, $service_name, $definition) = @_; $service_name = ucfirst $service_name; no strict 'refs'; $definition = $self->normalize_definition($definition); for my $layer (qw(Controller ServiceLayer DataLayer)) { unshift @{*{ "MyProject::$layer::$service_name::ISA" }}, 'MyProject::$layer::Base'; *{ "MyProject::$layer::$service_name::_get_definition" } = sub { return $definition; }; } my $method = lc($definition->{method}); $self->routes->$method($definition->{url})->to( controller => $service_name, action => $name ); } 46
  • 48. Код, который существует • Множество уже написанных модулей • Нестардартные методы 48
  • 49. Добавляем в генератор сервиса проверку ISA my $module = "MyProject::$layer::$service_name"; unless ($module->isa('MyProject::$layer::Base')) { unshift @{*{ "$module::ISA" }}, 'MyProject::$layer::Base'; } *{ "MyProject::$layer::$service_name::_get_definition" } = sub { return $definition; }; 49
  • 50. С AUTOLOAD все ОК, но can надо поправить sub can { my ($self, $method) = @_; my $module = blessed $self ? ref $self : $self; no strict 'refs'; if (my $sub = *{"$module::$method"}{CODE}) { return $sub; } elsif (_has_method($module, $method)) { return _generate_sub($module, $method); } else { return __PACKAGE__->SUPER::can($method); } } 50
  • 51. Чего мы добились • Поигрались с кодогенерацией • Убрали дублирование кода • Формализовали декларацию данных для генерации методов • Научились добавлять вариативное поведение • Собрали определение сервисов и методов воедино • Сделали определение типичных методов максимально лаконичным • Для новых и отлично ложащихся в шаблон сервисов мы даже избавились от модулей • Но при этом сохранили обратную совместимость • А также возможность добавлять нестандартные методы • Ленивая инициализация – при запуске сервера не генерируется ничего лишнего Но определение сервиса и метода – это все еще код! 51
  • 53. Декларациям место в текстовом формате • Окончательно разделяем код и декларации • Коллбэки prepare и finish выносим в модули, а в декларациях оставляем имя модуля и функции • Более компактный формат • Легкое отключение сервиса на отдельных нодах: просто удаляем конфиг! 53
  • 54. Пример конфига service: Chat create_room: web_layer: form: chat/create_room check_token: 1 check_auth: 1 method: put url: common_chat/room/create service_layer: returns: room_id finish: MyProject::Callback::Chat::gather_userinfo notify: 1 data_layer: func: chat.create_room args: [profile_id, room_name, participant_id] returns: single 54