Привет! Меня зовут Дмитрий. Я архитектор решений в крупной российской компании, более 15 лет проектирую, пишу код и руковожу командами. Сотрудничаю с Практикумом как ревьюер курса по Java и как автор курса «Архитектура программного обеспечения».

Предположим, вы решили развлечься дизайном систем (System Design), пусть даже и не добровольно, на собеседовании. Если компания поленилась поделиться рабочим контекстом, то задача может быть в формате «запроектируй Твиттер». Более кандидатоориентированная компания N может попросить «спроектируй поиск на сервисе N».

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


Сразу скажу, всё, что будет ниже, — не идеальная архитектура, но на интервью она никому особо и не нужна. Обычно важнее понять, как мыслит человек и сможет ли он в будущем за разумное время и с наличием информации создать конфетку из… доступных ресурсов компании.

Начало интервью и вводные данные

Василий пришёл на собеседование. Скорее всего, у него есть час на всё. На сам дизайн минут 40, а остальное время — на вопросы по нему. Бывает, вопросы идут в процессе, тогда время отъедается и может даже не хватить. В идеале ему бы дойти до законченного варианта с картинкой и ответить на большинство вопросов, которые хотели у него спросить. Итак, отсчёт пошёл.

Задача: спроектировать сервис покупки подарков. Этот сервис должен помочь со знакомой многим ситуацией: перед днём рождения коллеги выбирается ответственный, ему скидывают деньги, он что-то покупает, а потом надо решать, что делать со сдачей. 

Условия: сервис будет находиться на большом маркетплейсе, где можно найти всё: от цветов до Теслы. Любые зарегистрированные пользователи могут положить деньги в копилку, она будет доступна всем, кто скинулся. После сбора необходимой суммы её потратят на выбранные товары. После покупок и закрытия копилки сдача должна вернуться кэшбеком маркетплейса на счета пользователей.

Всё это — бизнес-требования. Требования могут быть функциональными и нефункциональными, их нужно уточнять. Обычно тот, кто дает задачу, надевает «шапочку продакта» и готов отвечать на вопросы. 

Функциональные требования

Василию нужно уточнить функциональные требования: как бизнес видит работу системы и как работает текущая архитектура. Он должен зафиксировать, что есть сейчас, и, в идеале, накидать high-level архитектуру, то есть общий набросок будущей системы.

Бросаться сразу рисовать «как будет» — не очень хороший вариант. Скорее всего получится не то, что хотел получить «продакт», и часть требований будет потеряна.

Ему следует задать вопросы, а если на что-то ответить не могут, пофантазировать вслух. Очень важно при проектировании вести встречу и задавать вопросы, а не ждать, что это сделает интервьюер. Вопросы, которые можно задать:

  • Какие ещё сервисы есть?

  • Сколько DAU предполагается?

  • Какие планы по развитию?

  • Какие сроки на реализацию?

И любые другие, какие ещё придут в голову по бизнес-контексту.

Уточненные данные: у проекта есть биллинг, сервис пользователей, каталог товаров, поиск, сервис логистики, интеграции с партнерами, корзина, сервис кэшбека и промоакций, аналитики, сам сайт. 

Сейчас дневная аудитория в среднем 200 тысяч уникальных пользователей, и это количество будет только расти. Всего пользователей — 1 млн. Прогнозов, сколько будут покупать подарков, нет. Опросы пользователей показали, что фича будет востребована. Хочется в ближайший квартал выпустить её в прод на часть пользователей.

Теперь нужно сделать схему и отобразить в ней данные. Так Василию будет проще ориентироваться в ландшафте и дальше задавать вопросы. Очень красиво делать не надо: достаточно наброска, чтобы синхронизироваться и подтвердить, что они с «продактом» друг друга поняли. Бывают случаи, когда кандидат долго вырисовывает стрелочки, но не успевает закончить все остальное, — так делать не надо.

При этом никто не ждёт, что он отобразит весь контекст верно и до последней детали. Например, «продакт» осознанно пропустил банк и склады как не особо важные для текущей задачи. А ещё добавил в контекст лишнюю информацию, вроде интеграций с партнерами  и поиска.

High-level архитектура сервиса
High-level архитектура сервиса

Нефункциональные требования

Теперь необходимо узнать подробности по нефункциональным требованиям к сервису. Интервьюер надевает «шапочку СТО/Архитектора/Начальника» и готов отвечать на вопросы. Василий может спросить: 

  • Есть ли данные по текущему RPS?

  • Какие требования ко времени ответа?

  • Какие требования к надежности?

Уточнённые данные: текущий RPS на сервис — 1000, время ответа нового сервиса не должно превышать 200 мс, а надежность должна быть 99.9. 

Всё это нужно зафиксировать. 

Время на знакомство, получение задачи и археологию по обоим видам требований — ≈15 минут.

API и интеграции

Кто-то начинает на этом этапе собирать уже схему архитектуры «как будет». Рано. Нарисовать недолго, но сначала лучше пройтись по интеграциям и API.

Исходя из задачи, у пользователя должна быть возможность производить с копилкой следующие действия: 

  • создать, 

  • закрыть, 

  • редактировать, 

  • получить ссылку, 

  • получить одну копилку или список.

Для создания копилки понадобятся: название, описание, крайняя дата, картинка. 

Для редактирования — те же данные, как при создании, плюс идентификатор. 

Для закрытия, получения ссылки и получения конкретной копилки или списка — идентификатор.

С данными для API Василий разобрался, вроде всего пока хватает и ничто не противоречит требованиям. 

Не менее важный момент — выбрать протокол взаимодействия (способ тут очевидно синхронный, который можно вызвать асинхронно из JS, например). Его выбирают из текстовых и бинарных. 

Тут Василий должен спросить раздвоенную личность «продакта-СТО», что сейчас в маркетплейсе применяется. Ну странно же тащить gRPC, если нет жёсткой потребности, и везде сейчас обычный стиль REST с JSON`ом. 

Когда он узнает все данные, он взвешивает плюсы и минусы, при этом вслух объясняет свой выбор. В 99% случаев вполне подойдет HTTP со стилем REST и  JSON`ом на сдачу, но лучше явно проговорить этот момент. Пример описания API:

/code
GET /giftmoneybox  — получить список согласно авторизации пользователя
GET /giftmoneybox/{giftmoneybox_id} — получить конкретную копилку
POST /giftmoneybox — создать копилку
application/json
{
      “title”,
      “description”,
      “edge_date”,
      “image_link”  
}
/code

Василий может пояснить, например, почему вместо base64 у него ссылка на картинку (скорее всего, его и так спросят, но лучше занять роль «ведущего»). Дело в том, что вряд ли сервис будет хранить много изображений в базе данных, да и вообще как-то не очень хранить их там. Скорее всего, всё равно будет внешнее хранилище, поэтому логичнее использовать ссылку. Ограничение на картинку можно поставить до 1 мб.

Будут ли ещё какие-то интеграции тут? Как будто пока не видно.

Время на API — 5—10 минут.

Жизненный цикл данных

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

Данные сервиса:

  •   id — идентификатор, например числовой;

  •   title — название, просто текст, 150 символов;

  •   description — описание, 1024 символа;

  •   edge_date — крайняя дата, метка времени;

  •   image_link — ссылка на картинку, 256 символов;

  •   status — закрыто или открыто, bool.

Связи: так как пользователь может входить в разные копилки других пользователей или создать несколько своих, здесь подойдёт стандартная «многие-ко-многим». Что выбрать для хранения? Кажется, обычная реляционка и пара таблиц решат проблему. Тут опять наступает момент — ну, вы поняли — выбора. 

Василий выбрал PostgreSQL в качестве системы управления БД. Для этого он вспомнил разные продукты и оценил их по критериям:

  • как ведет себя под нагрузкой,

  • лицензия,

  • ресурсы,

  • масштабируемость,

  • техническая политика компании.

Он объяснил, что PostgreSQL поддается репликации достаточно легко, шардированию — уже чуть с бубном. Это продукт с открытой лицензией, а к ресурсам он не особо требователен (ну когда как), если не забивать один сервер терабайтами. 

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

  • Что будет с индексами для данных?

  • Какой алгоритм индексации будет?

  • Когда и по какому ключу будешь шардировать?

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

Таблицы в базе данных
Таблицы в базе данных

Время на данные с вопросами — ≈10 минут.

Схема архитектуры

Настал звёздный час — теперь уже можно рисовать схему архитектуры «как будет». Теперь Василий отрисовывает сервис, который создаёт и всю окружающую обвязку: хранилище для изображений, БД и всё, что ещё посчитает нужным. В процессе он рассказывает, что именно делает и почему.

Например, он рисует S3-подобное хранилище и говорит: «Отлично подходит для хранения изображений, есть API, …». И говорит, что выбрал MinIO, потому что у него открытая лицензия, его можно легко развернуть внутри и масштабировать. Очень хорошо, если прямо в процессе или сразу после он будет рассказывать и про отказоустойчивость. 

Показатели надёжности Василий узнал ещё в самом начале, поэтому он может смело размещать сервисы по ЦОДам, от трёх и больше. Попутно рассказывать, почему именно от трёх ЦОДов, как будет происходить переключение в случае аварии. Если данных много, он может тут же поговорить про геошардирование, если клиенты размазаны по географии, и как лучше выбрать CDN.

Постепенно собирается полная схема, по которой у интервьюера будут ещё вопросы. Сейчас на ней сервис (несколько экземпляров), кластер кэша для «горячих» копилок, кластер БД и кластер MinIO. 

Аутентификацию тоже можно смело добавить и как раз рассказать, что по id пользователя можно получить его копилки, по id копилок — всю информацию о них. И всё это передать корзине при выборе способа оплаты. А сервис разместится, конечно, в Kubernetes, если он есть :)

Схема архитектуры «как будет»
Схема архитектуры «как будет»

Время на приложение — ≈15 минут.

Расчёт ресурсов и финальные штрихи

В самом конце Василию стоит посчитать, сколько места потребуется с запасом на 3—5 лет. Если явно не обозначен потенциал роста и количество пользователей, есть повод пофантазировать: пусть аудитория будет расти на 20% в год, и 30% от неё будет пользоваться копилкой. 

Грубо считаем количество копилок:

1 год: 1 000 000 (пользователи) * 30% (сколько будут пользоваться)  = 300 000

2 год: 1 200 000 (пользователи) * 30% (сколько будут пользоваться)  = 360 000

3 год: 1 440 000 (пользователи) * 30% (сколько будут пользоваться)  =  432 000

Всего копилок за 3 года: 1 092 000

Всего изображений: 1 092 000 Мб (1 Мб на картинку, 1 картинка на копилку)

Чтобы посчитать данные, Василий сделает предположение, что на одну копилку приходится в среднем 10 пользователей. Объём данных в gitbox:

  • id — 8 байт,

  • title — 150 байт,

  • description — 1024 байта,

  • edge_date — 8 байт,

  • image_link — 256 байт,

  • status —1 байт.

Итого: 8 байт + 150 байт + 1024 байта + 8 байт + 256 байт + 1 байт = 1447 байт

В gitbox_users, с учетом средних 10 пользователей: 8 байт + 8 байт = 16 байт *10 = 160 байт

Итого одна копилка: 1447 байт + 160 байт = 1,57 Кб

Финальный расчёт на три года: 1 092 000 * 1.57 = 1,6343258321285248 Гб — именно столько места понадобится будущим копилкам. Не очень много, можно пока обойтись без шардов.


В виде экзотики Василий может подумать, сколько процессоров понадобится. Например, сделать о-о-очень грубое предположение, что сейчас из 1000 RPS основного сервиса на копилки придётся 300 RPS (30%). Каждый запрос к основному сервису — 200 мс, а тут пусть будет не более 30 мс.

Формула для расчёта: RPS=X∗(1/(TD/1000)

Где X — количество ядер, TD — время отработки запроса (ms), 1000 — количество миллисекунд в секунде.

300 = ? ядра * (1/(30мс/1000))

Финальный расчёт: X = 300/(1/(30мс/1000)) = 9 ядер

Остались финальные штрихи: мониторинг, логи и трейсы, чтобы было совсем всё красиво. Василий добавляет их на схему, называет технологии: например, Prometheus, Grahana для мониторинга и графиков, ELK Stack для логов и Jaeger для трейсинга.

Время — всё, что осталось.

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

Финальный чек-лист

▢ Получить задачу: смесь неполных функциональных и нефункциональных требований.

▢ Задать вопросы «по бизнесу», которые помогут понять функциональные требования.

▢ Создать high-level архитектуру, набросок текущего состояния.

▢ Уточнить нефункциональные требования.

▢ Проработать контракты и интеграции.

▢ Проработать данные.

▢ Проработать архитектуру приложений и отказоустойчивость.

▢ Продумать ресурсы инфраструктуры.

Комментарии (1)


  1. CloseToAlgotrading
    28.08.2024 08:09

    Спасибо, интересно было почитать.
    Позволю высказать свое мнение по данной теме.

    Я бы начал с более детального рассмотрения предоставленного юзкейса. И в первую очередь следовало бы ограничить зону ответственности сервиса. Когда границы определены, можно найти уже и стейкхолдеров (в данном случае другие сервисы).

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

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

    Опять же, когда проработали системные функции сервиса, встает вопрос о данных, не о конкретной реализации API, а именно о данных, чем и как обмениваются между собой сервис со стейкхолдерами (другими сервисами, пользователем и тд.).

    Ну и далее по списку, на базе функциональной архитекруты, можно подумать о логической архитекруте, что можно в некоторых случаях один к одному перенести на физический уровень (уровень реализации непосредственно софта) и тд.

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