Лебедь, рак и щука: огранизация работы нескольких программистов на малых и средних проектах
Для начала определимся с тем, что мы делаем. Обычно наш продукт состоит из:
- сервера или нескольких серверов
- настроек сторонних приложений (http-сервера, СУБД, прочие хранилища данных и утилиты)
- нашей схемы размещения файлов (фото, видео, и прочий хлам контент)
- наших кэшей
- нашей структуры БД
- нашего кода
- бессоных ночей
Начнем с конца, пропустив бессоные ночи.
Обычно все начинают с одного и того же…
Коммунизм
Один сервер, одна база, один код. Правим по очереди или по алфавиту. Правки то и дело сопровождаются криками «Не трогайте пока User.php. Мне надо выборки поправить». Версионность кода поддерживается ежедневным архивированием папочки с кодом и дампом БД.
И надоело им материться, и явился им SVN (CVS, Git, Bazaar, нужное подчеркнуть). И поняли они, что это хорошо…
База на то и база, чтобы одна была
Стало лучше, но все равно то и дело слышаться возгласы «Ёп! Кто поле is_hidden у статьи убрал? Оно же во всех выборках! Давай коммить (новые выборки) быстрей!».
Призадумались ребята. Подумали, поспали, еще подумали. И поняли, что схему БД надо «привязывать» к коду, который с ней работает. Погуглили, и нашли:
- кучу утилит, которые умеют делать «diff» между двумя базами
- для ROR на Ruby – миграции на SQL DDL
- для Django на Python – DbMigration
- а для РНР — всю туже кучу утилит типа mysql diff, ну еще и Doctrine Migration для ORM Doctrine.
А отдельные храбрецы, разрабатывающие на LIMB‘е используют вот эту радость. Радость представляет из себя набор консольных утилит для автоматического создания, тестирования и применения миграций, а так же для создания дампов БД. Подробности можно прочитать на нашем форуме.
Все вроде бы хорошо, но вот беда — на продакшене кэши решили хранить в memcached, а на машинах разработчиков толи памяти мало, толи им на содержимое кэша часто смотреть приходится.
Делим cash, то есть cache
Что у нас есть такое куда смотреть легко, и где место не жалко. Эврика! Файлы на диске. Теперь дело за малым — нужна библиотечка, которая позволит простым изменением настройки переключаться между разными хранилищами.
ИМХО, каждый, уважающий себя, фреймворк должен иметь такую штуку. Хотя бы для того, чтобы мерятся с другими. У Zend Framework есть Zend_Cache. В LIMB таких штук целых две. Пакет сache — нормально работает и вполне стабилен (по API), и второй писал я, со всеми вытекающими. Пакет cache2:
- 6 хранилищ: память скрипта, APC, memcached, БД, файлы, сессия, фейк для тестов, (и еще, как минимум, добавится кэш в РНР-файл)
- хранилища будут иметь абсолютно одинаковый интерфейс и потокобезопасность:
- add
- get
- set
- delete
- flush
- increment/decrement
- safeIncrement/safeDecrement
- lock/unlock
- логгер, позволяющий отслеживать операции с кэшом
- утилитка для проведения микротестов
- полустабильное состояние
Вся информация о хранилище задается с помощью DSN. Например:
file:///tmp/cache //кэш на файлах
apc:///?prefix=foo //кэш в APC, ключи префиксуются строкой foo.
Ну совсем все радостно, но файлы на продакшене на отдельном сервере лежат, а у разработчиков локально надо. Или настройкой sphinx занимается только один человек, и поднимать его у остальных — бессмысленно.
Абстракция. Как много в этом слове
Как известно любая проблема, кроме одной, решается введением абстракции. Решим и нашу.
Для начала разделим каждый наш файл конфигурации на две части: общую и специфичную для конкретной машины. У нас это, например, avatar.conf.php и avatar.conf.override.php. Специфичные штуки игнорируем в нашей системе контроля версий. Общие кладем рядом с кодом. Желательно, чтобы в файле с общими настройками задавались настройки для продакшена. Тогда вы во-первых их(настройки продакшена) не потеряете, а во-вторых проще будет продакшен обновлять.
Разница при работе с медиа-файлами обычно сводится к двум задачам:
- куда-то положить
- откуда-то запросить
Для указания размещения файлов мы, используем URL и обычную функцию copy(). Например на продакшене
'scp://storage_user@storage_host1/'
.'?rsa_pub='.urlencode('/home/php-fpm/.ssh/id_rsa.pub')
.'&rsa;=' .urlencode('/home/php-fpm/.ssh/id_rsa').'/media'
, а на машине разработчика просто
'/www/somesite/www/media/'
Ну а с тем откуда запрашивать еще проще. Для продакшена 'img-1.somesite.ru/media/', а у разработчика somesite.pupkin.local/media/'. Это базовые пути и URL’ы для медиа-файлов. Остальное определяется, исходя из id файла.
Со Sphinx’ом поступим схожим образом. Добавим в конфигурационный файл директиву fake_search, которая будет определять тип поискового сервиса. Используя фабричный метод, наподобие такого:
static function createSearchService($config)
{
if(!isset($config['fake_search']) || !$config['fake_search'])
return SphinxClient();
else
return FakeSphinxClient();
}
, т.к. SphinxClient и FakeSphinxClient реализуют общий интерфейс, то для остальной программы такая замена пройдет незаметно. FakeSphinxClient будет просто отдавать первые подходящие объекты, а следовательно верстальщик сможет застилить страницу с результатами поиска, не занимаясь такими «магическими» штуками, как установка Sphinx.
Разработчики рады, и поют песни, восхваляющие абстракции. Осталась одна маленькая проблемка — разница в версиях ПО третих сторон, и их настройках.
Может хватит, а?
Вот на этом шаге мы остановились и находимся в данный момент. Следующий этап эволюции это работа в среде, максимально приближенной к «боевой». Т.е. работа на общем сервере, где с помощью виртуализации поддерживается среда, в которой нам придется работать на продакшене. У каждого свой код, своя база(и не одна), в общей СУБД, свои хосты. Все межсерверные взаимодействия отлаживаются еще на этапе разработки. И проблем с разными версиями ПО не возникает.
Коммит.
if(!isset($config['fake_search'] || !$config['fake_search'])
тут ошипко
Спасибо. Поправил