Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Aleksey Mashanov Rit

580 views

Published on

  • Be the first to comment

  • Be the first to like this

Aleksey Mashanov Rit

  1. 1. Поэтапный  рефакторинг:  success story Алексей Машанов
  2. 2. Цели рефакторинга ● Упрощение добавления новых возможностей за счет возможности реиспользования кода. ● Упрощение сопровождения кода за счет приведения его в человекочитаемый вид, нормализации кода и структуры базы. ● Избавление от велосипедов и перенос тем самым головной боли по их развитию и поддержке на сообщество.
  3. 3. Характеристики системы ● Perl + PostgreSQL ● ~ 1200 модулей и 400 скриптов ● ~ 300000 строк чистого кода ● ~ 450 таблиц в БД ● ~ 150 хранимых процедур и 140 триггеров
  4. 4. Разбиение на этапы Этап рефакторинга —  коммит должен укладываться  в рамки одного релиза t Релиз (3­4 недели) Шаг рефакторинга —  изменение не сказывающееся  на работоспособности системы Рефакторинг выполняется в основной ветке разработки
  5. 5. Test Driven Refactioring Для каждого вносимого в код изменения Написание автотеста Проверка автотеста путем поломки тестируемого кода Внесение модификации (рефакторинг) Проверка модифицированного кода автотестом
  6. 6. Структура автотестов Test::Class My::Test rollback после  lib/ t/lib/ каждого теста Class1 Class1::Test Class2 Class2::Sub1 Class2::Test Class2::Sub1::Test Class3 Class2::Sub2 Class3::Test Class2::Sub2::Test
  7. 7. I. Замена самописных ORM на DBIx::Class
  8. 8. Структура до рефакторинга Ent Ent::Smth11 Entity Entity::Smth11 new() new() Ent::Smth12 Entity::Smth12 list() list() get() … get() … set() set() save() Ent::Smth1N save() Entity::Smth1N ● Два примерно одинаковых ORM ● Методы модификации и поиска объединены в одном классе ● Доступ к полям объекта как к элементам хэша
  9. 9. Что хотим получить DBIx::Class::Row Schema::Result::Smth11 DBIx::Class::ResultSet Schema::ResultSet::Smth11 new() Schema::Result::Smth12 search() Schema::ResultSet::Smth12 get_column() … … set_column() Schema::Result::Smth1N Schema::ResultSet::Smth1N insert() update() Schema::Result::Smth21 Schema::ResultSet::Smth21 Schema::Result::Smth22 Schema::ResultSet::Smth22 … … Schema::Result::Smth2N Schema::ResultSet::Smth2N ● Один ORM ● Методы модификации и поиска в разных классах ● Доступ к полям объекта через акцессоры
  10. 10. Зачем? ● До рефакторинга ● Два самописных ORM в одной системе это слишком много ● Оба из них не поддерживают отношений между таблицами, тем не менее они нам необходимы, что приводит к обилию в коде plain SQL запросов ● Вновьприбывшие разработчики вынуждены с ними разбираться и вникать в их отличия ● После рефакторинга ● Много новых хороших возможностей, которым мы все очень рады ● Мы не одни во вселенной: почти все что нам может понадобиться уже изобрели, реализовали, отладили и устранили почти все баги, а какие не устранили, устраняют довольно-таки быстро ● Опыт работы с DBIx::Class разработчику пригодится не только для работы над нашей системой, поэтому он с большей вероятностью потрудится разобраться в нем поглубже
  11. 11. Создание схемы table table table схема DBIx::Class::Schema::Loader code style conventions simple perl script выстраиваем нужную  иерархию Schema::Result::* Используем статическую схему DBIx::Class::Schema
  12. 12. Схема обертки tiehash Ent EntHash Schema::Result::SmthX new() FETCH() get_coumn() set() STORE() set_column() get() EXISTS() has_column_loaded() list() NEXTKEY() columns() save() _DBIC_ insert_or_update() _DBICRS_ Ent::SmthN Ent::Smth2 Schema::ResultSet::SmthX dbic_class() Ent::Smth1 dbic_class() search() dbic_class()
  13. 13. Callback методы DBIx::Class::Core Ent::* save() DBIC::EntCallback delete() insert() update() delete() да нет Schema::Result::* caller() eq 'Ent'
  14. 14. Неспешная миграция 1. Ent::XXX­>... Schema­>resultset('XXX')­>... 2. SELECT * FROM xxx Schema­>resultset('XXX')­>select() 3. Ent::XXX­>save() Schema::Result::XXX­>insert() Schema::Result::XXX­>update() Ent::XXX­>delete() Schema::Result::XXX­>delete()
  15. 15. Завершение рефакторинга ● Удаление иерархии старых ORM
  16. 16. Timeline рефакторинга Схема таблиц Schema::* Обертка Ent вокруг DBIx::Class 1 релиз Callback методов Перенос хуков в Избавление Замена Ent::* → Schema::Result::* от plain SQL N релизов Schema­>resultset('*') Удаление старого ORM 1 релиз t
  17. 17. II. Единый механизм хранения сущностей
  18. 18. Исходная структура Service связан с объектом одного из трех Lbill Client ● классов, а не с одним. ● User, Server, VDS имеют примерно одинаковый набор финансовых полей, но не используют наследование. ● Лишняя связь от User, Server, VDS к Client. User Server VDS ● Сложные запросы к базе со множественными LEFT JOIN. ● Добавление новой сущности приводит к созданию 1 класса, 3 связей и модификации Service. Service
  19. 19. Желаемая структура ● Добавление новой сущности Client приводит к добавлению 1 класса и 1 связи. Lbill User ● Финансовые операции ограничены работой с Entity, а не с тремя User, Server, VDS. Entity Server ● При добавлении новой сущности большинство возможностей (кроме Service VDS технических) - «из коробки». ● Нет лишних связей (нормализация).
  20. 20. Структура базы vz_vds vz_vds servers clients servers id id users id users id identity_id client_id id id entity_id client_id lbills технические  lbill_id entity_id client_id технические  lbill_id id поля lbill_id финансовые  технические  поля финансовые  client_id поля поля финансовые  поля технические  поля services services entities технические  поля технические  поля user_id entity_id id поля server_id lbill_id vz_vds_id финансовые  поля Было Стало
  21. 21. Миграционные триггеры entities AFTER UPDATE Обновление соответствующих финансовых полей в  таблицах users, servers, vz_vds при их изменении BEFORE INSERT vz_vds 1.Проверка, что все синхронизируемые из entities поля IS  servers NULL — это означает, что не выполняется попытка  users установить их значение при INSERT 2.Автоматическое заполнение синхронизируемых полей  данными из соответствующей записи в entities AFTER INSERT OR UPDATE Проверка, что все значения полей соответствуют  значениям всех соответствующих полей в таблице entities
  22. 22. Заполнение данными vz_vds id servers entity_id entities 24786 id 1 users entity_id 38798 2 1. id 24786 id 78969 38798 1 entity_id 3 1 24786 12 2 78969 38798 23 3 INSERT 78969 3 UPDATE SET entity_id 2. services id user_id server_id vds_id entity_id 724786 78969 3 338798 2786 26 978969 6783 365 UPDATE SET entity_id
  23. 23. Обертка в ORM EntHash Schema::Result::User is_proxied() EntHash::Proxy Schema::Result::Entity client_id EntHash::ProxyAux Schema::Result::Lbill tiehash Ent EntHash::User is_proxied() Ent::User hash_class() Schema::ResultSet::User { prefetch => { entity =>'lbill' } } list() search() client_id lbill.client_id is_proxied($_) entity.$_
  24. 24. Неспешная миграция 1. users.$fields servers.$fields entity.$field vz_vds.$field services.user_id services.server_id services.entity_id services.vds_id 2. Ent::XXX­>new() Schema­>resultset('XXX')­>new() 3. SELECT * FROM xxx Schema­>resultset('XXX')­>search()
  25. 25. Завершение рефакторинга ● Удаление переехавших в entities полей из таблиц users, servers, vz_vds; полей user_id, server_id, vz_vds_id из таблицы services ● Удаление миграционных триггеров ● Удаление оберточных классов и прочих миграционных подпорок
  26. 26. Timeline рефакторинга Создание таблиц Написание триггеров Заполнение данными Обертка ORM 1 релиз Замена plain SQL модификаций users.$field → Замена plain SQL entities.$field запросов N релизов Удаление ненужных полей и подпорок 1 релиз t
  27. 27. Вопросы?

×