Skip to content
On this page

Принципы разработки

Основные принципы

Мы отвечаем за работоспособность своей системы

Мы катнули сервис в прод и ушли на обед. В этот момент с сервисом что-то случилось. Не нужно рассчитывать, что его кто-то за вас починит — разработчик сервиса отвечает за него. Нужно реагировать на алерты и предпринимать меры для устранения проблем. То же самое может произойти и ночью.

Чтобы меньше просыпаться ночью, мы заботимся о стабильности сервисов заранее. Мы следим за тем, чтобы наш сервис работал во всех окружениях, не только в продакшене.

Ваша система – это ответственность надолго

Некоторые наши системы живут невероятно долго, поэтому мы заранее рассчитываем на то, чтобы облегчить себе (и другим) жизнь через 3 года. Мы поддерживаем работоспособность нашей системы до последнего использования. И даже если сервис не менялся давно, но в используемой библиотеке нашли уязвимость, мы делаем исправления.

Мы начинаем реализацию с проектирования и учитываем текущую архитектуру

Допустим, мы решили добавить новую функцию. Мы могли бы просто взять и создать новый сервис для реализации такой функциональности. Далее сделать поход из всех затрагиваемых этой функциональностью сервисов. Но мы так не делаем. Правильно сначала посмотреть в какое место системы лучше добавить новую возможность. Вполне вероятно, что для этого найдется уже готовый сервис и не нужно делать новый. Следует помнить, что мы стараемся избегать распределенных транзакций и не создаем новые сервисы на любую новую фичу.

Мы избегаем ненужной сложности

Мы избегаем ненужной сложности, сохраняя решения простыми. Мы проектируем систему так, чтобы не нужно было делать одно и то же изменение в нескольких местах. Это относится к коду, схемам данных, тестам и даже документации. Если вдруг вы ловите себя на том, что этот код вы уже писали/встречали раньше, остановитесь, подумайте и не повторяйте себя.

Мы используем готовые инструменты и не изобретаем велосипеды

Решая задачу, мы всегда рассматриваем альтернативы или существующие решения. И только если они нам не подходят, разрабатываем своё.

Мы относимся к чужому коду с уважением

Мы обсуждаем большие изменения с владельцем кода/репозитория перед тем, как их вносить. Если мы видим, что можно улучшить текущий код, то мы стараемся это делать. Когда нас просят сделать код-ревью пулл-реквеста, мы делаем это как можно быстрее. Если мы видим проблемы в пулл-реквесте, мы оставляем понятные комментарии, что нужно исправить и почему. Если нам нужно сделать быстрое временное решение, то мы обязуемся привести его потом в порядок.

Если что-то становится ненужным, мы это удаляем

Мы не храним “на всякий случай” то, что уже устарело или просто стало ненужным. Это относится и к неиспользуемому коду, и к сервисам, и к базам данных.

Из основных принципов следуют дополнительные. Их больше и они более конкретные.

Проектирование

Мы не завязываемся на детали реализации

Чтобы разрабатывать независимо, мы не завязываемся на особенности реализации систем, которые используем. Например, мы можем знать, что определенное поле в сервисе монотонно возрастает из-за того, что используется mongodb. Мы можем завязаться на это поле, используя его как ключ идемпотентности. Но в будущем у нас могут возникнуть проблемы, когда этот сервис изменит эту реализацию. Поэтому мы так не делаем.

Интерфейсы

Внешний интерфейс важнее внутренней реализации

В первую очередь необходимо продумать как будет выглядеть внешний интерфейс. Реализацию в будущем можно менять, клиенты ее не видят.

Мы соблюдаем обратную совместимость

Разработчик сервиса обязан поддерживать свой интерфейс обратно совместимым и реализующим такую же функциональность, которую он предоставлял в момент создания. Важно, что эта поддержка не ограничивается несколькими месяцами, её нужно обеспечивать до того момента, пока интерфейс хоть кто-то использует. Если у нас есть необходимость сделать обратно несовместимое изменение, мы делаем новую версию, оставляя старую работоспособной.

Мы делаем интерфейсы простыми и удобными для использования

Для интеграции с вашей системой клиенты не должны тратить большое количество времени.

Документация

Мы документируем интерфейсы, особенности работы системы, нефункциональные требования — и держим их в актуальном состоянии

Мы делаем это для того, чтобы новый инженер быстро вошел в курс дела или чтобы при проблемах мы могли быстро разобраться в чем дело, не вызывая автора из отпуска.

Работа с данными

Мы не пишем бизнес-логику на стороне базы

Часть логики технически можно реализовать прямо в хранимых процедурах на стороне базы данных, например, в PostgreSQL. Но мы так не делаем, потому что тестирование, отладка и развязывание инцидентов в проде с такой схемой работы сервиса становятся очень сложными. Поэтому мы реализуем всю бизнес-логику на стороне кода самого сервиса.

Мы используем шину данных только для уведомления о событиях

При создании новой сущности кажется удобным отправлять сразу весь созданный state в шину данных. Но мы так не делаем, потому что шина данных создана для обмена уведомлениями, а не состояниями.

Мы кэшируем данные там, где это необходимо

Мы не ходим за данными каждый раз. Вполне нормально закэшировать необходимые тексты у себя в сервисе и затем использовать их. Однако важно помнить об инвалидации кэша в тех случаях, где это критично.

У данных есть только один источник правды

Мы не поддерживаем несколько мастер-копий данных в разных системах. Это усложняет поддерживаемость и стабильность разработанной системы. Задача синхронизации копий данных трудоемка.

Мы не храним данные в stateless-компонентах, а используем персистентные хранилища

Мы считаем, что у сервисов нет никакого состояния и не сохраняем ничего на файловую систему.

Также, мы не делаем механизмов обмена внутренними состояниями между экземплярами сервиса, так как эта схема является хрупкой. Мы не можем гарантировать консистентность между инстансами.

Взаимодействие сервисов

По возможности, мы используем асинхронное взаимодействие

Общение сервисов лучше делать асинхронным, так как это снимает необходимость ожидания со стороны клиента. Асинхронное взаимодействие также позволяет не делать сложной логики перепосылки запросов, реализации graceful degradation. Можно развязывать большое количество прямых связей. Поэтому, там где возможно, мы отдаем предпочтение асинхронному взаимодействию. Однако в некоторых случаях синхронное взаимодействие необходимо (например, когда пользователю нужно отдать результат сразу).

Мы пишем наш код так, чтобы система продолжала работать в случае сбоев (fault tolerance)

Мы не рассчитываем, что сервис, с которым мы интегрируемся, будет всегда надежно работать. Если он упадет, наш сервис продолжит работу. Мы продумываем полную вероятную деградацию своей системы и пути восстановления.

Тестирование

Любые изменения должны легко тестироваться автоматически

Чем больше проверок мы доверяем роботам, тем больше у нас времени на действительно полезные вещи. Если какое-то изменение нельзя протестировать автоматически, нужно переделать реализацию, это нормально. Например, мы можем реализовать какую-нибудь жесткую связку с внешним API, которую невозможно будет толком проверить. Это может обойтись нам слишком дорого в отладке и при последующих изменениях. Поэтому мы думаем о том, как это протестировать ещё до того, как начнем писать код.

Что не протестировано – то не работает. Что не работает – не катим

Мы проверяем все наши изменения перед выкаткой, и на большую часть изменений пишем тесты, чтобы не утонуть в бесконечных фиксах.

Мы работаем по Zero Bug Policy

Мы всегда знаем, какие проблемы у нас есть, и не копим их в бэклоге. По каждому багу мы сразу принимаем решение: чиним сейчас, в порядке очереди или никогда (закрываем задачу), если это вообще не баг — превращаем его в задачу.

Эксплуатация

Мы всегда в курсе, что происходит с нашими системами

Разработчик знает как работает сервис, настраивает на него dashboard’ы и ставит алерты на потенциальные деградации. В итоге разработчик всегда может сказать, все ли хорошо в данный момент с сервисом.