- Цель
- Как обычно выглядит собеседование по проектированию мобильных систем
- Общие рекомендации
-
Предлагаемый план ответа на интервью
-
- Бизнес-идея
-
- Уточнение требований
-
- Проектирование математической модели (при необходимости)
-
- Проектирование системы высокого уровня
-
- Проектирование API
-
- Высокоуровневый дизайн клиентской части
-
- Детальное проектирование некоторых модулей
-
- Один сложный случай и подробное обсуждение
-
Резюме
-
Ссылки
- Цель
- Как обычно выглядит собеседование по дизайну мобильных систем
- Процесс
- Входные данные
- Ожидания
- Распространенные рекомендации
- Предлагаемый план ответа на собеседовании
- Бизнес-идея
- Уточнение требований
- Проектирование математической модели
- Высокоуровневое проектирование системы
- Дизайн API
- Высокоуровневый дизайн клиентской части
- Детальное проектирование некоторых модулей
- Один сложный случай и подробное обсуждение
- Резюме
- Ссылки
Цель
Будучи мобильным разработчиком, вы можете знать, что найти какие-либо материалы для подготовки к собеседованию по проектированию мобильной системы — это проблема. Существует множество видеоматериалов о собеседованиях по проектированию бэкенд-систем и лишь немногие из них посвящены мобильным системам. Та же ситуация и со статьями, можно найти множество статей и даже курсов, которые помогут подготовиться к собеседованию по разработке бэкенд-систем, и совсем нет статей о собеседованиях по разработке мобильных систем. Именно поэтому я решил поделиться своим опытом подготовки и прохождения собеседований по дизайну мобильных систем.
Конечно, эта статья — всего лишь один из способов понять суть собеседования, и результат будет зависеть не только от информации, которую вы получили из этой статьи, но и от ваших знаний, навыков, умений и знаний на собеседовании, поэтому не стоит рассматривать эту информацию как серебряную пулю.
Как обычно выглядит собеседование по дизайну мобильных систем
Сначала давайте разберемся, как выглядит собеседование по дизайну мобильной системы, каковы исходные данные, процесс и ожидания.
Процесс
Обычно собеседование занимает около 45 минут:
- 5 минут — знакомство
- 5-10 минут — определение задачи
- 25-30 минут — решение
- 5 минут — ваши вопросы интервьюеру.
Обычно ваша задача — разработать мобильное приложение или его часть, а иногда — разработать часть бэкенда, который также имеет отношение к мобильному приложению. Во вторые 5-10 минут интервьюер сообщает вам всю необходимую информацию, которая может варьироваться от компании к компании.
Входные данные
Давайте посмотрим, что вам могут предоставить:
- Идея новой функции или всего приложения. Обычно это звучит так: «Давайте разработаем приложение, похожее на Instagram» или «Учитывая, что вы работаете в Twitter, давайте добавим функцию объединения некоторых ваших постов в истории». Ничего больше вы обычно не услышите от интервьюера, всю дополнительную информацию вам нужно получить от него с помощью правильно сформулированных вопросов — техника будет описана далее в статье.
- Эскизы экранов, необходимых для реализации идеи из первого пункта. Это очень важная часть информации, и вы должны быть очень внимательны, чтобы усвоить все нюансы.
- Некоторые требования и ограничения, например, в каком объеме вам нужно спроектировать систему — только мобильная, или только мобильная и API, или мобильная и бэкенд.
Вот и все, больше никакой информации от интервьюера вы, скорее всего, не получите.
Ожидания
Ожидания высокого уровня:
- Способность понимать и решать сложные проблемы
- Четко и эффективно общаться
- Наличие соответствующего опыта и знанийПуть углубления от этих абстрактных определений в основном зависит от интервьюера, его навыков, опыта и положения в компании. Они могут определять сложные проблемы совершенно по-разному, но мы попытаемся сформулировать наиболее распространенные конкретные сигналы, которые вы должны продемонстрировать.
По первому пункту, чтобы показать свою способность понимать и решать сложные проблемы, вы должны продемонстрировать следующее:
- Способность думать о деловой части задачи, поскольку это первый шаг в понимании проблемы и ответе на вопрос «Почему?». Вы должны продемонстрировать способность понимать бизнес-цель функции или всего приложения и применять эти знания в процессе уточнения или генерации технических требований.
- Хорошо работать с нечеткими требованиями. Интервьюер обычно дает вам очень мало информации, и этой информации явно недостаточно, чтобы даже начать проектирование, поэтому вы должны уметь задавать правильные вопросы, чтобы уточнить требования. Процесс выяснения требований должен быть обусловлен определенными и четкими целями, вопросы не должны быть случайными, а должны следовать определенной системе.
- Способность синтезировать решение для поставленной задачи. В первых двух пунктах вы продемонстрировали способность понять проблему, следующим шагом будет применение всех имеющихся знаний и разработка разумного решения. Вы синтезируете проект, используя подход «сверху вниз», чтобы сначала обеспечить контекст и основные концепции, а затем, используя технику «разделяй и властвуй», постепенно разделить систему на все более мелкие части.
Второй пункт — эффективная и четкая коммуникация — может включать в себя:
- Голосовая коммуникация. Вы должны говорить около 30 минут без больших пауз, объясняя все свои решения и идеи с достаточной уверенностью, знаниями и умением отстаивать свои идеи
- Общение на белой доске. Белая доска очень полезна для документирования всех ваших мыслей, чтобы вы могли вернуться к любому шагу и посмотреть, что вы решили ранее и все ли ваши решения последовательны.
Что касается соответствующего опыта и знаний:
- Иметь опыт разработки огромных мобильных приложений или лучше целых клиент-серверных систем. Ожидается, что вы продемонстрируете знания в области проектирования коммуникаций между приложением и сервером (односторонние, двусторонние, проектирование API), пагинации, управления кэшем и состоянием, методов модулизации, различных паттернов проектирования мобильных приложений и их плюсов и минусов. Кроме того, здесь вы можете продемонстрировать знание наиболее популярных проблем в мобильных приложениях и методов их избежания (таких как мутация состояния, хранение больших изображений в состоянии приложения и чрезмерное использование синглтонов и god-объектов).
- Способность предвидеть проблемы или трудности в решении. Некоторые решения могут быть очень сложными для реализации, и от вас ожидается, что вы будете четко сообщать об этом, объяснять, где возможная проблема или компромисс.
- Иметь опыт работы с различными методами обеспечения качества и снижения рисков. Здесь вы должны упомянуть о том, как вы делаете свою разработку пригодной для тестирования, сине-зеленого развертывания, быстрого отказа и т.д.
- Умение проектировать модули на уровне класса, знание основных принципов и паттернов проектирования. Очень маловероятно, что у вас будет на это время, но все же есть шанс, что интервьюер задаст вам вопросы типа «Как бы вы реализовали обновление метки подсчета непрочитанных сообщений?». — Вам нужно будет решить, будет ли это паттерн наблюдателя, или делегата, или опроса, или чего-либо еще, и нарисовать простую диаграмму.
Распространенные рекомендации
Резюмируем наиболее популярные рекомендации, которые компании дают перед собеседованием:
- Перед началом проектирования вам необходимо собрать как можно больше требований от интервьюера. Не стесняйтесь задавать столько вопросов, сколько необходимо.
- Вы должны показать свои знания, чтобы можно было начать с той части дизайна, в которой вы разбираетесь лучше всего.
- Не молчите, говорите все время, общайтесь четко, не ждите подсказок от интервьюера.
- Всегда предоставляйте информацию об альтернативах и защищайте свой выбор.
Предлагаемый план ответа на собеседовании
Я считаю, что если в вашей компании существует хорошая традиция проводить обзор дизайна перед внедрением любой большой (или межкомандной) функции, то вы можете заметить, что на самом деле интервью по дизайну системы выглядит как комплексный обзор дизайна наряду с предварительной работой, когда вы собираете требования, продумываете дизайн и рисуете диаграммы.
Поэтому вы можете провести обзор дизайна 3-5 раз, и этого должно быть достаточно, чтобы понять идею. Но если у вас нет такой возможности, давайте рассмотрим хороший план собеседования по проектированию системы.
Это может быть план высокого уровня:
- Бизнес-идея (что мы продаем пользователю? обычно это услуги и данные) : 3-4 минуты
- Уточнение требований (5 измерений, чтобы помочь с ограничениями) : 1-2 минуты
- Разработка математической модели (если необходимо) : 0-5 минут
- Определите, как разделить состояние и функциональность между серверной и клиентской сторонами : 3-5 минут
- Проектирование API : 7-10 минут
- Высокоуровневый дизайн клиентской части (многоуровневая структура) : 4-5 минут
- Детальное проектирование одного выбранного модуля : 4-8 минут
- Один сложный/трудный случай и его детальное обсуждение : 5-10 минут
Давайте рассмотрим каждый пункт из приведенного ниже плана более подробно.
Бизнес-идея
В основном это делается для того, чтобы понять контекст, сформулировать, что именно мы собираемся продавать пользователям (какие услуги и данные) и начать думать о задаче с самого начала.
Результат этого шага следующий:
- Список услуг, которые мы продаем нашим пользователям
- Доступ к данным, которые мы продаем нашим пользователям
- Понимание основной идеи продукта или функции (если вы чего-то не понимаете, не стесняйтесь задавать все необходимые вопросы. Чем больше вы знаете о продукте, тем легче вам будет принимать решения во время остальной части процесса).
Например, если вас попросили разработать упрощенное приложение, подобное WhatsApp, без регистрации/логина, групп, списков рассылки, шифрования E2E и чтения статуса, то результат этого шага будет примерно таким:
Мы продаем следующие услуги:
- Safely storing and providing read/write access to your address book
- Providing the ability to make/receive Audio and Video calls to contacts from your address book
- Providing the ability to send/receive text messages and images to contacts from your address book and safely store the history of all the chats
Услуги должны быть как можно более независимыми друг от друга, это поможет вам быстрее разработать API. В нашем примере все сервисы зависят от сервиса адресной книги, но они независимы друг от друга.
Мы продаем доступ к следующим данным:
Address book item:
- First name
- Family name
- Phone number
Chat history message:
- User opponent
- Inbound or outbound
- Date and time of the message
- Message text or Image
Таким образом, приведенный выше список услуг и сущности данных (+ эскизы пользовательского интерфейса, обычно предоставляемые интервьюером) формируют основу для всей вашей будущей работы. Сущности данных не являются окончательными и не подготовлены для хранения или передачи по сети, это просто высокоуровневое описание данных, в основном с точки зрения конечного пользователя.
Вы можете начать уточнять требования прямо здесь, например, вы можете спросить, нужно ли нам добавить изображение аватара пользователя, но в этом случае вы должны быть готовы ответить на этот вопрос самостоятельно, поскольку интервьюер может задать вам тот же вопрос, и вы должны обосновать свой выбор.
Уточнение требований
Очень важно заранее собрать все возможные требования. Мы начали это делать с бизнес-требований на предыдущем этапе, теперь нам нужно уточнить технические требования, ограничения и контекст. Зачем нам это нужно? Потому что ваш дизайн должен зависеть от контекста, процессов разработки и, собственно, от запроса клиента/пользователя/интервьюера.
Поэтому первый вопрос должен быть о контексте, о том, есть ли он в нашей задаче, есть ли у нас хост-приложение, в которое мы добавляем новую функциональность, есть ли у нас уже готовый бэкенд для веб-версии, насколько велика наша команда и какова ее экспертиза. В 99% случаев, я полагаю, интервьюер ответит, что контекста нет вообще и нужно начинать с нуля, но эти вопросы покажут, что вы заботитесь о контексте и учитываете его в процессе разработки решения.
Что касается ограничений, я предпочитаю использовать контрольный список для оценки проектных решений. В следующем списке приведены пункты контрольного списка и вопросы, которые можно задать по каждому из пунктов, относящихся к дизайну мобильной системы. Вы можете придумать другие вопросы и использовать другой список, этот просто для напоминания о вопросах во время интервью.
Ремонтопригодность — разрабатываем ли мы MVP, PoC или полномасштабную систему? Насколько велика команда, которая будет реализовывать мой проект?
Тестируемость — мы можем оставить это измерение, нам определенно нужно подумать о каком-то виде тестирования. Здесь нет ограничений, просто не забудьте упомянуть о тестировании на последних этапах вашего проекта.
Масштабируемость/производительность — здесь мы можем рассматривать масштабируемость как масштабируемость нашей команды — возможность повторного использования их кода на разных платформах, и хотя мы можем спросить о кроссплатформенности, так как это может значительно повлиять на наш дизайн. Также вы можете задать вопросы о некоторых параметрах, которые могут повлиять на производительность приложений, где производительность имеет решающее значение и где лучше заранее определить ограничения. Например, если вы разрабатываете приложение с распознаванием множества объектов в живом видео с помощью ML, вы можете упомянуть, что мы ограничиваемся только последними моделями устройств Android или iOS. В противном случае нам придется использовать сторонние библиотеки и задействовать главный процессор для выполнения всех вычислений, что может повлиять на время автономной работы и качество распознавания. Это не так серьезно повлияет на дизайн, но упомянуть об этом стоит, поскольку это показывает ваш опыт и способность предвидеть проблемы.
Безопасность — очень важно обсудить ограничения, вызванные соображениями безопасности. Особенно если вас просят хранить какую-то информацию на устройстве, или если вы должны работать с персонально идентифицируемой информацией (PII) или чувствительной PII. Поэтому лучше уточнить у интервьюера, что сохранение и передача запрашиваемой информации должны быть безопасными, а если вы считаете, что это небезопасно, то предложите, что нужно сделать для ее защиты.
Доступность — здесь мы можем спросить о возможности работы в автономном режиме, возможно, ограничениях версий ОС, выборе телефона/планшета, использовании ограниченного набора языков и размеров экрана. Конечно, все эти вопросы следует задавать только в том случае, если они имеют отношение к вашей задаче.
Нет необходимости записывать все измерения на доске, так как они работают только как ключи, чтобы вспомнить, о чем вы должны думать и спрашивать. Позже вы можете использовать те же измерения, если вас попросят сравнить два решения.
Лучше записать все ответы на вышеупомянутые вопросы на доске, чтобы потом можно было на них ссылаться.
Проектирование математической модели
Этот шаг относится только к ограниченному подмножеству задач. Эти задачи будут выглядеть следующим образом:
- Вы — разработчик Facebook, и вам нужно добавить новую функцию, чтобы показывать два самых популярных поста в верхней части ленты. В этом случае вам нужно уточнить/придумать, что значит «самый популярный», а затем описать, как его вычислить: использовать ли для этого просмотры, комментарии или лайки, мгновенную популярность или скользящее среднее и так далее.
- Вы являетесь разработчиком в быстрорастущем агрегаторе супер-отелей, и ваша задача — разработать функцию обнаружения ботов. Здесь вы должны определить входные данные, необходимые для обнаружения ботов, и придумать алгоритм и математическую модель того, как использовать эти данные, чтобы отличить ботов от реальных пользователей. Например, вы можете рассчитать количество запросов с помощью скользящего окна и сравнить его с заданным пределом.
Результатом этого шага будет простая схема модуля с входами, формулой(ами) внутри и выходами.
Высокоуровневое проектирование системы
Следующий шаг — решить, какая часть сервисов и данных должна быть реализована/храниться на сервере, а какая — на мобильном устройстве. Здесь можно использовать результат первого шага — список сервисов и сущностей данных.
Рассмотрим предыдущий пример — приложение, подобное WhatsApp.
Можем ли мы реализовать хранение адресной книги только на устройстве? Нет, потому что нам нужно иметь одно общее хранилище, чтобы все клиенты могли использовать его для связи с нужным корреспондентом (Мы не рассматриваем схемы распределенного хранения, но вы можете подумать об этом и сравнить с приведенной выше схемой, используя измерения из второго шага).
Можно ли реализовать аудиовызовы, используя только клиентскую часть? Да, но вы должны рассказать интервьюеру обо всех компромиссах в этом случае по сравнению с сервисом на стороне сервера (например, шифрование E2E, лучшая масштабируемость, худшая доступность из-за обхода NAT и так далее).
Например, вы решили перенести все службы на сторону сервера, и поэтому все данные также будут находиться на сервере.
Тогда, поскольку все данные и сервисы находятся на бэкенде, нам нужно выбрать тип связи клиент-сервер для каждого из них по очереди.
Для примера приложения, подобного WhatsApp, сервис адресной книги может быть легко реализован как простой REST HTTP вызов с JSON полезной нагрузкой (учитывая, что каждый пользователь может использовать только одно устройство и поэтому нам не нужны уведомления от сервера к клиенту об изменениях в контактах адресной книги), сервис аудио/видео звонков будет использовать свои собственные интерфейсы связи — двунаправленное TCP соединение для протокола сигнала (WebSocket, вероятно) и UDP для потоковой передачи данных (RTP, вероятно). На этом шаге должно быть достаточно просто упомянуть тип связи (двунаправленная или однонаправленная, потоковая или на основе запроса-ответа, затем более подробно — HTTP REST (простой, с длинным опросом), web-socket, raw-socket и т.д…).
Для сервиса текстовых/изобразительных чатов немного сложнее решить, какой канал связи использовать. Это может быть REST HTTP + push-уведомление, или двунаправленные веб-сокеты, или пользовательский канал на основе TCP, или что-то еще. У всех вариантов есть свои плюсы и минусы, поэтому вы можете выбрать любой вариант, просто сообщите интервьюеру о других альтернативах и будьте готовы защищать выбранный вариант.
Результатом этого шага должно быть что-то вроде следующей схемы, где вы определяете место, где будут располагаться сервисы и данные + каналы связи между сервером и клиентами.
Это первая очень высокоуровневая структура нашей системы и, скорее всего, это будет единственная схема, на которой вы нарисуете какие-либо детали бэкенда.
Дизайн API
Эта тема довольно противоречива, существует множество способов проектирования API, поэтому вы можете использовать любой из них, единственное — в результате этого шага вы должны точно определить все сущности данных и вызовы API. Побочным эффектом проектирования API является уточнение некоторых решений на стороне сервера.
По моему опыту, хорошо начинать с сервиса и сущности данных, от которых зависят другие сервисы/сущности данных, и расширять их до одного или нескольких вызовов API. Например, наш первый сервис — адресная книга, поэтому будет 4 вызова API:
- GET /address-book?page,limit -> [AddressBookItem]. Чтение данных адресной книги с помощью пейджинга (очень важно не забывать о пейджинге — лучше считать пейджинг по умолчанию для любого вызова и затем убрать его, если он не нужен, чем вообще забыть о нем). Я намеренно не упоминаю идентификатор пользователя, так как в интервью мы должны упомянуть, что все запросы должны содержать заголовок с маркером авторизации, который был получен ранее во время процедуры входа в систему. Если интервьюер хочет узнать больше о логине, вы можете рассказать ему один или несколько способов реализации регистрации/логина.
- POST /address-book?user_id,first_name,family_name,phone -> AddressBookItem. Добавление новой записи в адресную книгу.
- PUT /address-book?usert_id,first_name,family_name,phone -> AddressBookItem. Обновить существующую запись в адресной книге.
- DELETE /address-book?user_id. Удалить существующую запись из адресной книги.
Как выглядит AddressBookItem:
AddressBookItem:
user_id: int64
first_name: string
family_name: string
phone: string
Вызов API для чтения сообщений чата будет выглядеть следующим образом:
GET /chat-history?user_id,page,limit -> [ChatMessage, ChatImageMessage].
Где ChatImageMessage имеет вид:
ChatImageMessage:
user_id: int64
msg_id: int64
dt: timestamp
is_inbound: bool
img_url_thumb: string
img_url_full_size: string
Я написал вызовы API в пользовательской форме, которую легко показать в одной строке, поэтому знак вопроса не означает, что мы будем использовать параметры пути запроса, это просто разделитель между именем вызова и списком параметров. Кроме того, слово «GET» не означает, что мы будем использовать HTTP-запросы, это может быть WebSocket в соответствии с нашей схемой высокого уровня, и этот вызов API может быть закодирован, например, с помощью Google Protobuf, но идея останется прежней — это должен быть идемпотентный вызов с тремя входными параметрами, который возвращает массив из двух типов сообщений чата (простое текстовое сообщение и сообщение с изображением).
Вы можете придумать свой собственный формат написания вызовов API, если считаете, что он будет лучше представлять название вызова, входные и выходные данные.
На этом этапе мы определяем данные более детально, принимая во внимание реализацию на стороне сервера и пытаясь предвидеть будущие проблемы. Например, мы добавили уникальные идентификаторы для пользователей и сообщений и представляем изображение в виде двух прямых ссылок — одна для миниатюры, другая для полноразмерного изображения.
Скорее всего, в вашей задаче вам придется работать с изображениями, поэтому лучше заранее продумать возможные проблемы, например, сэкономить много трафика для пользователей, загружая только миниатюры, а не полноразмерные изображения. Это звучит очевидно, но такие небольшие оптимизации делают ваше решение более интересным для разработки и обсуждения.
Проектирование API — это итеративный процесс, вы можете изменить API позже, когда будете разрабатывать клиентскую часть, и это нормально. Этот шаг довольно важен и труден, поскольку вам нужно одновременно думать о проектировании на стороне сервера и на стороне клиента, пытаясь предугадать будущие проблемы со скоростью соединения, пропускной способностью и удобством использования. Предыдущие шаги призваны подготовить вас, предоставив список сервисов, сущностей данных и тип канала связи между клиентом и сервером.
После завершения этого шага вы можете потратить некоторое время на анализ результата — возможно, есть какие-то очевидные оптимизации, которые вы можете применить к API, например, вы можете захотеть объединить некоторые API в один, или разделить некоторые из них, или даже решить изменить канал связи, потому что, например, оказалось, что двунаправленная связь лучше подходит для одного из случаев.
Высокоуровневый дизайн клиентской части
Наконец, можно приступить к рисованию схемы структуры клиентских модулей. Слоистый дизайн, как правило, самый простой способ организовать модули в некоторые значимые группы почти во всех Frontend-приложениях.
Лучше начать рисовать эту схему с центральной и самой важной ее части — состояния и сервисов, не связанных с состоянием. Это те вещи, которые вы предоставляете своим пользователям, так что давайте начнем проектирование с них. Вы можете назвать этот слой business entities или Business Layer и затем начать спускаться вниз, чтобы встретиться с серверной стороной.
Следующим слоем может быть слой сервисов, здесь я использую название «сервис» в другом значении, чем раньше — интервальный слой сервисов помогает изолировать конкретные реализации низкоуровневых коммуникационных и постоянных модулей (Data Layer) от Business Layer. Таким образом, Business Layer не должен беспокоиться о том, откуда мы получаем данные — из постоянного хранилища или каналов связи, и какой SDK мы используем для реализации аудио/видео вызовов. Еще одна цель сервисного уровня — преобразование данных из нескольких форматов в тот, который использует Business Layer. На этом этапе мы не делаем никаких предположений о реализации сервисов — будет ли это набор промежуточного ПО в Redux или сервисы, распределенные между модулями VIPER, или что-то еще, мы можем просто упомянуть, что наша цель — сделать сервисы stateless и попытаться сохранить явное состояние только в Business Layer.
Data Layer содержит SDK, фреймворки, библиотеки, которые предоставляют вашему приложению такие возможности, как сетевое взаимодействие, доступ к локальной БД, логирование, или же полноценные полнофункциональные возможности, такие как аудио/видео вызовы. Конечно, вы можете разделить Data Layer на несколько внутренних слоев, если это имеет смысл, например, если вы разрабатываете некоторые низкоуровневые кодеки, у вас будет два слоя в Data Layer — один для высокоуровневой оркестровки и один для низкоуровневого доступа к аппаратному обеспечению устройства, но это должно быть скрыто в Data Layer и открыто только на этапе Dependency Injection, где мы можем определить, какие модули и кодеки должны быть использованы в Data Layer.
Поднимаясь вверх от бизнес-слоя к пользователю, мы должны нарисовать Presentation Layer. Основная цель этого слоя — подготовить данные из бизнес-слоя для отображения пользователю в UI Layer. Опять же стоит упомянуть, что мы хотим сохранить Presentation Layer без статических данных.
Последний слой, который необходимо нарисовать на схеме структуры дизайна высокого уровня, это UI Layer. Это очень специфичный для платформы слой, где бизнес-данные, преобразованные на Презентационном слое, отображаются на экране устройства с помощью OS SDK. Вторая цель этого слоя — взаимодействие с пользователем и передача всех этих взаимодействий через Presentation Layer в Business Layer и далее, если необходимо.
Этот слоистый дизайн может быть сопоставлен с любыми конкретными архитектурными паттернами, такими как MVC, MVI, Redux, VIPER, RIBs и т.д., но очень важно оставаться на более высоком уровне абстракции на этом шаге и не углубляться в эти конкретные паттерны. Вы можете упомянуть здесь такую вещь, как «модуль», но отложите конкретное определение «модуля» до следующего шага, поскольку значение модуля зависит от архитектурного паттерна, который вы выберете позже. Например, модуль в VIPER располагается горизонтально и пересекает все слои, кроме слоя данных, поэтому каждый модуль содержит небольшой фрагмент каждого слоя. В Redux, напротив, обычно нет модулей, а есть скорее функции.
Детальное проектирование некоторых модулей
Здесь мы должны выбрать одну функцию/модуль и определить архитектурный паттерн, который, по вашему мнению, лучше всего подходит для нее. Рассмотрим функцию чата в приложении, подобном WhatsApp. Поскольку функция чата пересекает все слои, выбранный паттерн будет влиять на все приложение, поэтому нам нужно думать не только о выбранной функции, но и о приложении в целом.
На самом деле, наше WhatsApp-подобное приложение работает с довольно ограниченными типами данных, которые связаны друг с другом, и все изменения в данных должны немедленно отражаться в различных частях приложения в виде сообщений в списке чатов и на экране чата, в виде уведомления, когда пользователь просматривает другие части приложения, и в виде счетчика непрочитанных сообщений. Это означает, что мы можем иметь одно периодически обновляемое состояние с возможностью подписки на изменения этого состояния. Похоже, что Redux является хорошим вариантом для бизнес-слоя + промежуточное ПО в качестве сервисного слоя + MVP или MVVM или MVI паттерны для презентационного и пользовательского слоев с магазином Redux, играющим роль модели. VIPER кажется немного чрезмерным для нескольких экранов с минимальным количеством маршрутов между ними, а RIBs лучше подходит для очень сложных экранов с большим количеством команд, работающих над одним экраном. Конечно, вы можете выбрать любой паттерн или не упоминать ни один из них, но тогда вам придется объяснить, где должен быть размещен store/state, как он должен работать, как мы реализуем подписку на изменения состояния, как должны использоваться сервисы и как они будут обновлять состояние.
Я намеренно не упоминал конкретную платформу, так как на самом деле не имеет значения, Android или iOS, поскольку для обеих платформ существует множество реализаций магазина Redux и реактивных фреймворков для реализации MVVM/MVI, в то время как MVP не нуждается в этом.
Давайте нарисуем возможную структурную схему для функции чата:
Здесь важно описать, как будет работать весь цикл обновления данных, продемонстрировать преимущества выбранного вами паттерна и упомянуть некоторые компромиссы и сложные проблемы, с которыми вы столкнетесь при реализации этого решения.
Например, мы можем перечислить следующие преимущества Redux-дизайна функции чата, используя предыдущие измерения:
- Удобство обслуживания: Мы полностью разделяем принимающую часть данных (слой данных и сервисов) и потребительскую часть данных (слой представления и пользовательского интерфейса), используя магазин с подписками на обновления, поэтому не имеет значения, из какого источника поступают данные — из постоянного хранилища или сетевого канала связи. Это помогает изменять одни части приложения, не затрагивая другие + повторно использовать как можно больше кода.
- Масштабируемость: Магазин может иметь несколько подписчиков, поэтому Presenter нашей функции чата будет срабатывать и передавать последние изменения в View для их отображения + Presenter верхней панели, например, также будет срабатывать и обновлять счетчик непрочитанных сообщений. Добавить еще одного подписчика очень просто.
- Тестируемость: Состояние неизменяемо и находится в одном месте (не распределено по всему приложению), что помогает избежать многих ошибок, связанных с одновременным изменением состояния. Также это помогает тестировать приложение, так как вы уверены, что состояние может быть изменено только в редукторах.
Недостатки:
- Развертываемость (и частично Maintainability): сложная структура проекта и запутанная концепция модульности. Чтобы изменить что-то в функции чата, разработчикам придется вносить изменения во многих местах — state, reducers, actions, middleware, Presenter, View. Более того, все эти места разбросаны по проекту (мы можем придумать структуру проекта, которая минимизирует этот эффект, объединив большинство частей в одном месте, но это может привести к повторяющемуся коду и проблемам с изменением кода других команд), что делает модулизацию немного сложной. Таким образом, каждая команда, скорее всего, будет касаться хранилища/состояния, а это означает перестройку всех модулей, поскольку все модули зависят от состояния.
- Масштабируемость: чем больше становится приложение, тем больше становится состояние. Обычно мы разбиваем его на под-состояния, чтобы каждая функция имела свое под-состояние, но в любом случае огромное состояние может привести к проблемам с производительностью и многопоточным доступом.
Стоит упомянуть, что ваш паттерн/подход/дизайн хорош для тестирования, например, в нашем примере можно упомянуть, что мы должны тестировать промежуточное ПО, сервисы, презентеры и редукторы, и это очень просто, поскольку они не имеют статических свойств.
Кроме того, здесь можно продемонстрировать умение проектировать диаграммы классов. Конкретная реализация будет зависеть от платформы (потому что, например, концепции рендеринга таблиц отличаются в Android и iOS).
Один сложный случай и подробное обсуждение
На этом этапе вы можете продемонстрировать знание платформы и умение решать проблемы. Лучше выбрать один интересный кейс, который вы уже встречали и успешно решили, и мягко подвести разговор к этому случаю.
Например, вы можете сказать, что это интересная проблема кэширования изображений в чатах. Каждое изображение имеет прямую ссылку на его миниатюру и полноразмерное изображение, и как только изображение попадает в видимую область экрана, оно запрашивается, скажем, у службы загрузки изображений, которая загружает изображение по этим прямым ссылкам. Слабым местом такого решения является то, что если у пользователя медленное сетевое соединение, то изображения будут появляться слишком поздно, особенно если пользователь прокручивает чат довольно быстро. Очевидно, что нам нужен кэш. Самое простое решение — добавить к сервису загрузки изображений кэш памяти, который представляет собой словарь с ключами в виде прямых ссылок и значениями в виде изображений, но что произойдет, когда мы перезапустим приложение? Все текстовые сообщения будут восстановлены из локальной БД (предположим это), но должны ли мы сохранить миниатюры и полноразмерные изображения также в БД? Или мы должны сохранить только миниатюры в БД, а полноразмерные изображения в файловой системе? Если мы сохраним изображения в файловой системе, как мы сможем найти их позже? Как сохранить папку с изображениями, если, например, устройство, в случае iOS, попросит освободить место? Вопросов много, и ответы на них могут продемонстрировать ваш опыт, знание платформы и навыки решения проблем.
Поэтому хитрость здесь заключается в том, чтобы заранее выбрать одну общую достаточно сложную проблему и подвести разговор к ней. Интервьюер может отказаться и выбрать случайную, но все же вы покажете, что знаете некоторые проблемы, и у вас будет шанс много говорить о проблеме, которую вы знаете лучше всего.
Резюме
Резюмируя вышеизложенное, могу сказать, что всегда лучше заранее разработать план собеседования по проектированию системы, будь то тот, который я предложил в этой статье, или другой. Неважно, какой план вы выберете, но он должен наглядно продемонстрировать вашу способность решать очень большую неоднозначную проблему — разбить ее на несколько значимых фрагментов, усвоить их один за другим, обработать эту информацию и выдать в результате другой вид релевантной информации.
Удачи вам на собеседованиях и не стесняйтесь писать комментарии к этой статье или делиться своим опытом.
Ссылки
- Модель многослойной архитектуры: https://towardsdatascience.com/software-architecture-patterns-98043af8028
- Атрибуты/размеры качества архитектуры/систем: https://sites.google.com/site/misresearch000/home/software-architecture-quality-attributes.
- Существует множество книг о проектировании API, стоит прочитать хотя бы одну.
- Паттерны мобильной архитектуры: MVC, MVP, MVVM, MVI, VIPER, REDUX и др. Было бы неплохо знать о них и о концепциях, лежащих в основе каждого из них, даже если вы не хотите упоминать ни один из них на собеседовании. О каждом из них легко найти много информации.