Как составить структуру приложения

Взявшись за написание небольшого, но реального и растущего проекта, мы «на собственной шкуре» убедились, насколько важно то, чтобы программа не только хорошо работала, но и была хорошо организована. Не верьте, что продуманная архитектура нужна только большим проектам (просто для больших проектов «смертельность» отсутствия архитектуры очевидна). Сложность, как правило, растет гораздо быстрее размеров программы. И если не позаботиться об этом заранее, то довольно быстро наступает момент, когда ты перестаешь ее контролировать. Правильная архитектура экономит очень много сил, времени и денег. А нередко вообще определяет то, выживет ваш проект или нет. И даже если речь идет всего лишь о «построении табуретки» все равно вначале очень полезно ее спроектировать.

К моему удивлению оказалось, что на вроде бы актуальный вопрос: «Как построить хорошую/красивую архитектуру ПО?» — не так легко найти ответ. Не смотря на то, что есть много книг и статей, посвященных и шаблонам проектирования и принципам проектирования, например, принципам SOLID (кратко описаны тут, подробно и с примерами можно посмотреть тут, тут и тут) и тому, как правильно оформлять код, все равно оставалось чувство, что чего-то важного не хватает. Это было похоже на то, как если бы вам дали множество замечательных и полезных инструментов, но забыли главное — объяснить, а как же «проектировать табуретку».

Хотелось разобраться, что вообще в себя включает процесс создания архитектуры программы, какие задачи при этом решаются, какие критерии используются (чтобы правила и принципы перестали быть всего лишь догмами, а стали бы понятны их логика и назначение). Тогда будет понятнее и какие инструменты лучше использовать в том или ином случае.

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

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

Критерии хорошей архитектуры

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

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

Гибкость системы. Любое приложение приходится менять со временем — изменяются требования, добавляются новые. Чем быстрее и удобнее можно внести изменения в существующий функционал, чем меньше проблем и ошибок это вызовет — тем гибче и конкурентоспособнее система. Поэтому в процессе разработки старайтесь оценивать то, что получается, на предмет того, как вам это потом, возможно, придется менять. Спросите у себя: «А что будет, если текущее архитектурное решение окажется неверным?», «Какое количество кода подвергнется при этом изменениям?». Изменение одного фрагмента системы не должно влиять на ее другие фрагменты. По возможности, архитектурные решения не должны «вырубаться в камне», и последствия архитектурных ошибок должны быть в разумной степени ограничены. “Хорошая архитектура позволяет ОТКЛАДЫВАТЬ принятие ключевых решений” (Боб Мартин) и минимизирует «цену» ошибок.

Расширяемость системы. Возможность добавлять в систему новые сущности и функции, не нарушая ее основной структуры. На начальном этапе в систему имеет смысл закладывать лишь основной и самый необходимый функционал (принцип YAGNI — you ain’t gonna need it, «Вам это не понадобится») Но при этом архитектура должна позволять легко наращивать дополнительный функционал по мере необходимости. Причем так, чтобы внесение наиболее вероятных изменений требовало наименьших усилии.

Требование, чтобы архитектура системы обладала гибкостью и расширяемостью (то есть была способна к изменениям и эволюции) является настолько важным, что оно даже сформулировано в виде отдельного принципа — «Принципа открытости/закрытости» (Open-Closed Principle — второй из пяти принципов SOLID): Программные сущности (классы, модули, функции и т.п.) должны быть открытыми для расширения, но закрытыми для модификации.

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

Это означает, что приложение следует проектировать так, чтобы изменение его поведения и добавление новой функциональности достигалось бы за счет написания нового кода (расширения), и при этом не приходилось бы менять уже существующий код. В таком случае появление новых требований не повлечет за собой модификацию существующей логики, а сможет быть реализовано прежде всего за счет ее расширения. Именно этот принцип является основой «плагинной архитектуры» (Plugin Architecture). О том, за счет каких техник это может быть достигнуто, будет рассказано дальше.

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

Тестируемость. Код, который легче тестировать, будет содержать меньше ошибок и надежнее работать. Но тесты не только улучшают качество кода. Многие разработчики приходят к выводу, что требование «хорошей тестируемости» является также направляющей силой, автоматически ведущей к хорошему дизайну, и одновременно одним из важнейших критериев, позволяющих оценить его качество: “Используйте принцип «тестируемости» класса в качестве «лакмусовой бумажки» хорошего дизайна класса. Даже если вы не напишите ни строчки тестового кода, ответ на этот вопрос в 90% случаев поможет понять, насколько все «хорошо» или «плохо» с его дизайном” (Идеальная архитектура).

Существует целая методология разработки программ на основе тестов, которая так и называется — Разработка через тестирование (Test-Driven Development, TDD).

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

Хорошо структурированный, читаемый и понятный код. Сопровождаемость. Над программой, как правило, работает множество людей — одни уходят, приходят новые. После написания сопровождать программу тоже, как правило, приходится людям, не участвовавшем в ее разработке. Поэтому хорошая архитектура должна давать возможность относительно легко и быстро разобраться в системе новым людям. Проект должен быть хорошо структурирован, не содержать дублирования, иметь хорошо оформленный код и желательно документацию. И по возможности в системе лучше применять стандартные, общепринятые решения привычные для программистов. Чем экзотичнее система, тем сложнее ее понять другим (Принцип наименьшего удивления — Principle of least astonishment. Обычно, он используется в отношении пользовательского интерфейса, но применим и к написанию кода).

Ну и для полноты критерии плохого дизайна:

  1. Его тяжело изменить, поскольку любое изменение влияет на слишком большое количество других частей системы. (Жесткость, Rigidity).
  2. При внесении изменений неожиданно ломаются другие части системы. (Хрупкость, Fragility).
  3. Код тяжело использовать повторно в другом приложении, поскольку его слишком тяжело «выпутать» из текущего приложения. (Неподвижность, Immobility).

Модульная архитектура. Декомпозиция как основа

software architecture

Не смотря на разнообразие критериев, все же главной при разработке больших систем считается задача снижения сложности. А для снижения сложности ничего, кроме деления на части, пока не придумано. Иногда это называют принципом «разделяй и властвуй» (divide et impera), но по сути речь идет об иерархической декомпозиции. Сложная система должна строится из небольшого количества более простых подсистем, каждая из которых, в свою очередь, строится из частей меньшего размера, и т.д., до тех пор, пока самые небольшие части не будут достаточно просты для непосредственного понимания и создания.

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

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

В этом случае программа из «спагетти-кода» превращается в конструктор, состоящий из набора модулей/подпрограмм, взаимодействующих друг с другом по хорошо определенным и простым правилам, что собственно и позволяет контролировать ее сложность, а также дает возможность получить все те преимущества, которые обычно соотносятся с понятием хорошая архитектура:

  • Масштабируемость (Scalability)
    возможность расширять систему и увеличивать ее производительность, за счет добавления новых модулей.
  • Ремонтопригодность (Maintainability)
    изменение одного модуля не требует изменения других модулей
  • Заменимость модулей (Swappability)
    модуль легко заменить на другой
  • Возможность тестирования (Unit Testing)
    модуль можно отсоединить от всех остальных и протестировать / починить
  • Переиспользование (Reusability)
    модуль может быть переиспользован в других программах и другом окружении
  • Сопровождаемость (Maintenance)
    разбитую на модули программу легче понимать и сопровождать

Можно сказать, что в разбиении сложной проблемы на простые фрагменты и заключается цель всех методик проектирования. А термином «архитектура», в большинстве случаев, просто обозначают результат такого деления, плюс “некие конструктивные решения, которые после их принятия с трудом поддаются изменению” (Мартин Фаулер «Архитектура корпоративных программных приложений»). Поэтому большинство определений в той или иной форме сводятся к следующему:

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

Архитектура — это организация системы, воплощенная в ее компонентах, их отношениях между собой и с окружением.
Система — это набор компонентов, объединенных для выполнения определенной функции.

Таким образом, хорошая архитектура это, прежде всего, модульная/блочная архитектура. Чтобы получить хорошую архитектуру надо знать, как правильно делать декомпозицию системы. А значит, необходимо понимать — какая декомпозиция считается «правильной» и каким образом ее лучше проводить?

«Правильная» декомпозиция

1. Иерархическая

Не стоит сходу рубить приложение на сотни классов. Как уже говорилось, декомпозицию надо проводить иерархически — сначала систему разбивают на крупные функциональные модули/подсистемы, описывающие ее работу в самом общем виде. Затем, полученные модули, анализируются более детально и, в свою очередь, делятся на под-модули либо на объекты.

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

software decomposition

Эта мысль, при всей своей очевидности, не так банальна как кажется. Например, в чем заключается суть такого распространенного «архитектурного шаблона» как Модель-Вид-Контроллер (MVC)? Всего навсего в отделении представления от бизнес-логики, то есть в том, что любое пользовательское приложение вначале делится на два модуля — один из которых отвечает за реализацию собственно самой бизнес логики (Модель), а второй — за взаимодействие с пользователем (Пользовательский Интерфейс или Представление). Затем, для того чтобы эти модули могли разрабатываться независимо, связь между ними ослабляется с помощью паттерна «Наблюдатель» (подробно о способах ослабления связей будет рассказано дальше) и мы фактически получаем один из самых мощных и востребованных «шаблонов», которые используются в настоящее время.

Типичными модулями первого уровня (полученными в результате первого деления системы на наиболее крупные составные части) как раз и являются — «бизнес-логика», «пользовательский интерфейс», «доступ к БД», «связь с конкретным оборудованием или ОС».

Для обозримости на каждом иерархическом уровне рекомендуют выделять от 2 до 7 модулей.

2. Функциональная

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

Модуль = Функция + Данные, необходимые для ее выполнения.

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

Модуль — это не произвольный кусок кода, а отдельная функционально осмысленная и законченная программная единица (подпрограмма), которая обеспечивает решение некоторой задачи и в идеале может работать самостоятельно или в другом окружении и быть переиспользуемой. Модуль должен быть некой “целостностью, способной к относительной самостоятельности в поведении и развитии” (Кристофер Александер).

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

3. High Cohesion + Low Coupling
Самым же главным критерием качества декомпозиции является то, насколько модули сфокусированы на решение своих задач и независимы. Обычно это формулируют следующим образом: “Модули, полученные в результате декомпозиции, должны быть максимально сопряженны внутри (high internal cohesion) и минимально связанны друг с другом (low external coupling).

  • High Cohesion, высокая сопряженность или «сплоченность» внутри модуля, говорит о том, модуль сфокусирован на решении одной узкой проблемы, а не занимается выполнением разнородных функций или несвязанных между собой обязанностей. (Сопряженностьcohesion, характеризует степень, в которой задачи, выполняемые модулем, связаны друг с другом )

    Следствием High Cohesion является принцип единственной ответственности (Single Responsibility Principle — первый из пяти принципов SOLID), согласно которому любой объект/модуль должен иметь лишь одну обязанность и соответственно не должно быть больше одной причины для его изменения.

  • Low Coupling, слабая связанность, означает что модули, на которые разбивается система, должны быть, по возможности, независимы или слабо связанны друг с другом. Они должны иметь возможность взаимодействовать, но при этом как можно меньше знать друг о друге (принцип минимального знания).

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

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

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

Грамотная декомпозиция — это своего рода искусство и гигантская проблема для многих программистов. Простота тут очень обманчива, а ошибки обходятся очень дорого. Если выделенные модули оказываются сильно сцеплены друг с другом, если их не удается разрабатывать независимо или не ясно за какую конкретно функцию каждый из них отвечает, то стоит задуматься а правильно ли вообще производится деление. Должно быть понятно, какую роль выполняет каждый модуль. Самый же надежный критерий того, что декомпозиция делается правильно, это если модули получаются самостоятельными и ценными сами по себе подпрограммами, которые могут быть использованы в отрыве от всего остального приложения (а значит, могут быть переиспользуемы).

Делая декомпозицию системы желательно проверять ее качество задавая себе вопросы: “Какую функцию выполняет каждый модуль?“, “Насколько модули легко тестировать?”, “Возможно ли использовать модули самостоятельно или в другом окружении?”, “Как сильно изменения в одном модуле отразятся на остальных?

В первую очередь следует, конечно же, стремиться к тому, чтобы модули были предельно автономны. Как и было сказано, это является ключевым параметром правильной декомпозиции. Поэтому проводить ее нужно таким образом, чтобы модули изначально слабо зависели друг от друга. Но кроме того, имеется ряд специальных техник и шаблонов, позволяющих затем дополнительно минимизировать и ослабить связи между подсистемами. Например, в случае MVC для этой цели использовался шаблон «Наблюдатель», но возможны и другие решения. Можно сказать, что техники для уменьшения связанности, как раз и составляют основной «инструментарий архитектора». Только необходимо понимать, что речь идет о всех подсистемах и ослаблять связанность нужно на всех уровнях иерархии, то есть не только между классам, но также и между модулями на каждом иерархическом уровне.

Как ослаблять связанность между модулями

Для наглядности, картинка из неплохой статьи “Decoupling of Object-Oriented Systems”, иллюстрирующая основные моменты, о которых будет идти речь.

Decoupling architecture

1. Интерфейсы. Фасад

Главным, что позволяет уменьшать связанность системы, являются конечно же Интерфейсы (и стоящий за ними принцип Инкапсуляция + Абстракция + Полиморфизм):

  • Модули должны быть друг для друга “черными ящиками” (инкапсуляция). Это означает, что один модуль не должен «лезть» внутрь другого модуля и что либо знать о его внутренней структуре. Объекты одной подсистемы не должны обращаться напрямую к объектам другой подсистемы
  • Модули/подсистемы должны взаимодействовать друг с другом лишь посредством интерфейсов (то есть, абстракций, не зависящих от деталей реализации) Соответственно каждый модуль должен иметь четко определенный интерфейс или интерфейсы для взаимодействия с другими модулями.

Принцип «черного ящика» (инкапсуляция) позволяет рассматривать структуру каждой подсистемы независимо от других подсистем. Модуль, представляющий собой черный ящик, можно относительно свободно менять. Проблемы могут возникнуть лишь на стыке разных модулей (или модуля и окружения). И вот это взаимодействие нужно описывать в максимально общей (абстрактной) форме — в форме интерфейса. В этом случае код будет работать одинаково с любой реализацией, соответствующей контракту интерфейса. Собственно именно эта возможность работать с различными реализациями (модулями или объектами) через унифицированный интерфейс и называется полиморфизмом. Полиморфизм это вовсе не переопределение методов, как иногда ошибочно полагают, а прежде всего — взаимозаменяемость модулей/объектов с одинаковым интерфейсом, или «один интерфейс, множество реализаций» (подробнее тут). Для реализации полиморфизма механизм наследования совсем не нужен. Это важно понимать, поскольку наследования вообще, по возможности, следует избегать.

Благодаря интерфейсам и полиморфизму, как раз и достигается возможность модифицировать и расширять код, без изменения того, что уже написано (Open-Closed Principle). До тех пор, пока взаимодействие модулей описано исключительно в виде интерфейсов, и не завязано на конкретные реализации, мы имеем возможность абсолютно «безболезненно» для системы заменить один модуль на любой другой, реализующий тот же самый интерфейс, а также добавить новый и тем самым расширить функциональность. Это как в конструкторе или «плагинной архитектуре» (plugin architecture) — интерфейс служит своего рода коннектором, куда может быть подключен любой модуль с подходящим разъемом. Гибкость конструктора обеспечивается тем, что мы можем просто заменить одни модули/«детали» на другие, с такими же разъемами (с тем же интерфейсом), а также добавить сколько угодно новых деталей (при этом уже существующие детали никак не изменяются и не переделываются). Подробнее про Open-Closed Principle и про то, как он может быть реализован можно почитать тут + хорошая статья на английском.

Интерфейсы позволяют строить систему более высокого уровня, рассматривая каждую подсистему как единое целое и игнорируя ее внутреннее устройство. Они дают возможность модулям взаимодействовать и при этом ничего не знать о внутренней структуре друг друга, тем самым в полной мере реализуя принцип минимального знания, являющейся основой слабой связанности. Причем, чем в более общей/абстрактной форме определены интерфейсы и чем меньше ограничений они накладывают на взаимодействие, тем гибче система. Отсюда фактически следует еще один из принципов SOLID — Принцип разделения интерфейса (Interface Segregation Principle), который выступает против «толстых интерфейсов» и говорит, что большие, объемные интерфейсы надо разбивать на более маленькие и специфические, чтобы клиенты маленьких интерфейсов (зависящие модули) знали только о методах, которые необходимы им в работе. Формулируется он следующим образом: “Клиенты не должны зависеть от методов (знать о методах), которые они не используют” или “Много специализированных интерфейсов лучше, чем один универсальный”.

Итак, когда взаимодействие и зависимости модулей описываются лишь с помощью интерфейсов, те есть абстракций, без использования знаний об их внутреннем устройстве и структуре, то фактически тем самым реализуется инкапсуляция, плюс мы имеем возможность расширять/изменять поведения системы за счет добавления и использования различных реализаций, то есть за счет полиморфизма. Из этого следует, что концепция интерфейсов включает в себя и в некотором смысле обобщает почти все основные принципы ООП — Инкапсуляцию, Абстракцию, Полиморфизм. Но тут возникает один вопрос. Когда проектирование идет не на уровне объектов, которые сами же и реализуют соответствующие интерфейсы, а на уровне модулей, то что является реализацией интерфейса модуля? Ответ: если говорить языком шаблонов, то как вариант, за реализацию интерфейса модуля может отвечать специальный объект — Фасад.

Фасад — это объект-интерфейс, аккумулирующий в себе высокоуровневый набор операций для работы с некоторой подсистемой, скрывающий за собой ее внутреннюю структуру и истинную сложность. Обеспечивает защиту от изменений в реализации подсистемы. Служит единой точкой входа — “вы пинаете фасад, а он знает, кого там надо пнуть в этой подсистеме, чтобы получить нужное”.

Таким образом, мы получаем первый, самый важный паттерн, позволяющий использовать концепцию интерфейсов при проектировании модулей и тем самым ослаблять их связанность — «Фасад». Помимо этого «Фасад» вообще дает возможность работать с модулями точно также как с обычными объектами и применять при проектировании модулей все те полезные принципы и техники, которые используются при проектирования классов.

Facade Decoupling architecture

Замечание: Хотя большинство программистов понимают важность интерфейсов при проектировании классов (объектов), складывается впечатление, что идея необходимости использовать интерфейсы также и на уровне модулей только зарождается. Мне встретилось очень мало статей и проектов, где интерфейсы бы применялись для ослабления связанности между модулями/слоями и соответственно использовался бы паттерн «Фасад». Кто, например, видел «Фасад» на схемах уже упоминавшегося «архитектурного шаблона» Модель-Вид-Контроллер, или хотя бы слышал его упоминание среди паттернов, входящих в состав MVC (наряду с Observer и Composite)? А ведь он там должен быть, поскольку Модель это не класс, это модуль, причем центральный. И у создателя MVC Трюгве Реенскауга он, конечно же, был (смотрим «The Model-View-Controller (MVC ). Its Past and Present», только учитываем, что это писалось в 1973 году и то, что мы сейчас называем Представлением — Presentaition/UI тогда называлось Editior). Странным образом «Фасад» потерялся на многие годы и вновь обнаружить его мне удалось лишь недавно, в основном, в обобщенном варианте MVC от Microsoft («Microsoft Application Architecture Guide»). Вот соответствующие слайды:

Facade MVC Model-View-Controller Decoupling

А разработчикам, к сожалению, приходится заново «переоткрывать» идею, что к объектам Модели, отвечающей за бизнес-логику приложения, нужно обращаться не напрямую а через интерфейс, то есть «Фасад», как например, в этой статье, откуда для полноты картины взят еще один слайд:

Facade MVC Model-View-Controller Decoupling

2. Dependency Inversion. Корректное создание и получение зависимостей

Формально, требование, чтобы модули не содержали ссылок на конкретные реализации, а все зависимости и взаимодействие между ними строились исключительно на основе абстракций, то есть интерфейсов, выражается принципом Инвертирования зависимостей (Dependency Inversion — последний из пяти принципов SOLID):

  • Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те, и другие должны зависеть от абстракций.
  • Абстракции не должны зависеть от деталей. Реализация должна зависеть от абстракции.

У этого принципа не самая очевидная формулировка, но суть его, как и было сказано, выражается правилом: «Все зависимости должны быть в виде интерфейсов». Подробно и очень хорошо принцип инвертирования зависимостей разбирается в статье Модульный дизайн или «что такое DIP, SRP, IoC, DI и т.п.». Статья из разряда must-read, лучшее, что доводилось читать по архитектуре ПО.

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

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

В каком-то смысле такое решение следует Принципу единственного выбора (Single Choice Principle), который говорит: “всякий раз, когда система программного обеспечения должна поддерживать множество альтернатив, их полный список должен быть известен только одному модулю системы“. В этом случае, если в будущем придется добавить новые варианты (или новые реализации, как в рассматриваемом нами случае создания новых объектов), то достаточно будет произвести обновление только того модуля, в котором содержится эта информация, а все остальные модули останутся незатронутыми и смогут продолжать свою работу как обычно.

Ну а теперь разберем подробнее, как это делается на практике и каким образом модули могут корректно создавать и получать свои «зависимости», не нарушая принципа Dependency Inversion.

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

  • что модуль делает, какую функцию выполняет
  • что модулю нужно от его окружения, то есть с какими объектами/модулями ему придется иметь дело и
  • как он это будет получать

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

  1. Модуль сам создает объекты необходимые ему для работы.

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

    “Суть заключается в том, что вместо непосредственного инстанцирования объекта через new, мы предоставляем классу-клиенту некоторый интерфейс для создания объектов. Поскольку такой интерфейс при правильном дизайне всегда может быть переопределён, мы получаем определённую гибкость при использовании низкоуровневых модулей в модулях высокого уровня”.

    В случаях, когда нужно создавать группы или семейства взаимосвязанных объектов, вместо Фабричного Метода используется Абстрактная Фабрика (Abstract factory).

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

    Этот подход реализуется шаблоном Локатор Сервисов (Service Locator), основная идея которого заключается в том, что в программе имеется объект, знающий, как получить все зависимости (сервисы), которые могут потребоваться.

    Главное отличие от фабрик в том, что Service Locator не создаёт объекты, а фактически уже содержит в себе инстанцированные объекты (или знает где/как их получить, а если и создает, то только один раз при первом обращении). Фабрика при каждом обращении создает новый объект, который вы получаете в полную собственность и можете делать с ним что хотите. Локатор же сервисов выдает ссылки на одни и те же, уже существующие объекты. Поэтому с объектами, выданными Service Locator, нужно быть очень осторожным, так как одновременно с вами ими может пользоваться кто-то еще.

    Объекты в Service Locator могут быть добавлены напрямую, через конфигурационный файл, да и вообще любым удобным программисту способом. Сам Service Locator может быть статическим классом с набором статических методов, синглетоном или интерфейсом и передаваться требуемым классам через конструктор или метод.

    Вообще говоря, Service Locator иногда называют антипаттерном и не рекомендуют использовать (главным образом потому, что он создает неявные связности и дает лишь видимость хорошего дизайна). Подробно можно почитать у Марка Симана:
    Service Locator is an Anti-Pattern
    Abstract Factory or Service Locator?

  3. Модуль вообще не заботиться о «добывании» зависимостей. Он лишь определяет, что ему нужно для работы, а все необходимые зависимости ему поставляются («впрыскиваются») из вне кем-то другим.

    Это так и называется — Внедрение Зависимостей (Dependency Injection). Обычно требуемые зависимости передаются либо в качестве параметров конструктора (Constructor Injection), либо через методы класса (Setter injection).

    Такой подход инвертирует процесс создания зависимости — вместо самого модуля создание зависимостей контролирует кто-то извне. Модуль из активного элемента, становится пассивным — не он делает, а для него делают. Такое изменение направления действия называется Инверсия Контроля (Inversion of Control), или Принцип Голливуда — «Не звоните нам, мы сами вам позвоним».

    Это самое гибкое решение, дающее модулям наибольшую автономность. Можно сказать, что только оно в полной мере реализует «Принцип единственной ответственности» — модуль должен быть полностью сфокусирован на том, чтобы хорошо выполнять свою функцию и не заботиться ни о чем другом. Обеспечение его всем необходимым для работы это отдельная задача, которой должен заниматься соответствующий «специалист» (обычно управлением зависимостями и их внедрениями занимается некий контейнер — IoC-контейнер).

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

Более подробно и с примерами о способах создания и получения зависимостей можно почитать, например, в этой статье (только надо иметь ввиду, что хотя автор пишет о Dependency Inversion, он использует термин Inversion of Control; возможно потому, что в русской википедии содержится ошибка и этим терминам даны одинаковые определения). А принцип Inversion of Control (вместе с Dependency Injection и Service Locator) детально разбирается Мартином Фаулером и есть переводы обеих его статей: “Inversion of Control Containers and the Dependency Injection pattern” и “Inversion of Control”.

Не будет преувеличением сказать, что использование интерфейсов для описания зависимостей между модулями (Dependency Inversion) + корректное создание и внедрение этих зависимостей (прежде всего Dependency Injection) являются центральными/базовыми техниками для снижения связанности. Они служат тем фундаментом, на котором вообще держится слабая связанность кода, его гибкость, устойчивость к изменениям, переиспользование, и без которого все остальные техники имеют мало смысла. Но, если с фундаментом все в порядке, то знание дополнительных приемов может быть очень даже полезным. Поэтому продолжим.

3. Замена прямых зависимостей на обмен сообщениями

Иногда модулю нужно всего лишь известить других о том, что в нем произошли какие-то события/изменения и ему не важно, что с этой информацией будет происходить потом. В этом случае модулям вовсе нет необходимости «знать друг о друге», то есть содержать прямые ссылки и взаимодействовать непосредственно, а достаточно всего лишь обмениваться сообщениями (messages) или событиями (events).

Связь модулей через обмен сообщениями является гораздо более слабой, чем прямая зависимость и реализуется она чаще всего с помощью следующих шаблонов:

  • Наблюдатель (Observer). Применяется в случае зависимости «один-ко-многим», когда множество модулей зависят от состояния одного — основного. Использует механизм рассылки, который заключается в том, что основной модуль просто осуществляет рассылку одинаковых сообщений всем своим подписчикам, а модули, заинтересованные в этой информации, реализуют интерфейс «подписчика» и подписываются на рассылку. Находит широкое применение в системах с пользовательским интерфейсом, позволяя ядру приложения (модели) оставаться независимым и при этом информировать связанные с ним интерфейсы о том что произошли какие-то изменения и нужно обновиться.

    Организация взаимодействия посредством рассылки сообщений имеет дополнительный «бонус» — необязательность существования «подписчиков» на «опубликованные» (т.е. рассылаемые) сообщения. Качественно спроектированная подобная система допускает добавление/удаление модулей в любое время.

  • Посредник (Mediator). Применяется, когда между модулями имеется зависимость «многие ко многим. Медиатор выступает в качестве посредника в общении между модулями, действуя как центр связи и избавляет модули от необходимости явно ссылаться друг на друга. В результате взаимодействие модулей друг с другом («все со всеми») заменяется взаимодействием модулей лишь с посредником («один со всеми»). Говорят, что посредник инкапсулирует взаимодействие между множеством модулей.

    Mediator Посредник

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

Дополнение: Модули могут пересылать друг другу не только «простые сообщения, но и объекты-команды. Такое взаимодействие описывается шаблоном Команда (Command). Суть заключается в инкапсулировании запроса на выполнение определенного действия в виде отдельного объекта (фактически этот объект содержит один единственный метод execute()), что позволяет затем передавать это действие другим модулям на выполнение в качестве параметра, и вообще производить с объектом-командой любые операции, какие могут быть произведены над обычными объектами. Кратко рассмотрен тут, соответствующая глава из книги банды четырех тут, есть также статья на хабре.

4. Замена прямых зависимостей на синхронизацию через общее ядро

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

Ядро-посредник может как знать о модулях-клиентах и управлять ими (пример — архитектура apache ), так и может быть полностью, или почти полностью, независимым и ничего о клиентах не знать. В сущности именно этот подход реализован в «шаблоне» Модель-Вид-Контроллер (MVC), где с одной Моделью (являющейся ядром приложение и общим хранилищем данных) могут взаимодействовать множество Пользовательских Интерфейсов, которые работают синхронно и при этом не знают друг о друге, а Модель не знает о них. Ничто не мешает подключить к общей модели и синхронизировать таким образом не только интерфейсы, но и другие вспомогательные модули.

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

5. Закон Деметры (law of Demeter)

Закон Деметры запрещает использование неявных зависимостей: “Объект A не должен иметь возможность получить непосредственный доступ к объекту C, если у объекта A есть доступ к объекту B и у объекта B есть доступ к объекту C“. Java-пример.

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

Закон Деметры реализует уже упоминавшийся «принцип минимального знания», являющейся основой слабой связанности и заключающийся в том, что объект/модуль должен знать как можно меньше деталей о структуре и свойствах других объектов/модулей и вообще чего угодно, включая собственные подкомпоненты. Аналогия из жизни: Если Вы хотите, чтобы собака побежала, глупо командовать ее лапами, лучше отдать команду собаке, а она уже разберётся со своими лапами сама.

6. Композиция вместо наследования

Одну из самых сильных связей между объектами дает наследование, поэтому, по возможности, его следует избегать и заменять композицией. Эта тема хорошо раскрыта в статье Герба Саттера — «Предпочитайте композицию наследованию».

Могу только посоветовать в данном контексте обратить внимание на шаблон Делегат (Delegation/Delegate) и пришедший из игр шаблон Компонет (Component), который подробно описан в книге «Game Programming Patterns» (соответствующая глава из этой книги на английском и ее перевод).

Что почитать

Статьи в интернете:

  • Design patterns for decoupling (небольшая полезная глава из UML Tutorial);
  • Немного про архитектуру;
  • Patterns For Large-Scale JavaScript Application Architecture;
  • Слабое связывание компонентов в JavaScript;
  • Как писать тестируемый код — статья из которой хорошо видно что критерии тестируемости кода и хорошего дизайна совпадают;
  • сайт Мартина Фаулера.

Замечательный ресурс — Архитектура приложений с открытым исходным кодом, где “авторы четырех дюжин приложений с открытым исходным кодом рассказывают о структуре созданных ими программ и о том, как эти программы создавались. Каковы их основные компоненты? Как они взаимодействуют? И что открыли для себя их создатели в процессе разработки? В ответах на эти вопросы авторы статей, собранных в данных книгах, дают вам уникальную возможность проникнуть в то, как они творят“. Одна из статей полностью была опубликована на хабре — «Масштабируемая веб-архитектура и распределенные системы».

Интересные решения и идеи можно найти в материалах, посвященных разработке игр. Game Programming Patterns — большой сайт с подробным описанием многих шаблонов и примерами их применения к задаче создания игр (оказывается, есть уже его перевод — «Шаблоны игрового программирования», спасибо strannik_k за ссылку). Возможно будет полезна также статья «Гибкая и масштабируемая архитектура для компьютерных игр» (и ее оригинал. Нужно только иметь ввиду что автор почему-то композицию называет шаблоном “Наблюдатель”).

По поводу паттернов проектирования:

  • Интересная «Мысль про паттерны проектирования»;
  • Удобный сайт с краткими описаниями и схемами всех шаблонов проектирования — «Обзор паттернов проектирования»;
  • сайт на базе книги банды четырех, где все шаблоны описаны очень подробно — «Шаблоны [Patterns] проектирования».

Есть еще принципы/паттерны GRASP, описанные Крэгом Лэрманом в книге «Применение UML 2.0 и шаблонов проектирования», но они больше запутывают чем проясняют. Краткий обзор и обсуждение на хабре (самое ценное в комментариях).

Ну и конечно же книги:

  • Мартин Фаулер «Архитектура корпоративных программных приложений»;
  • Стив Макконнелл «Совершенный код»;
  • Шаблоны проектирования от банды четырех (Gang of Four, GoF) — «Приемы объектно-ориентированного проектирования. Паттерны проектирования».

#Мнения

  • 15 окт 2021

  • 0

Как проектируют приложения: разбираемся в архитектуре

Старший iOS-разработчик из «ВКонтакте» рассказывает, почему архитектура не главное в проекте и как сделать продукт поддерживаемым и масштабируемым.

Евгений Ёлчев

Старший iOS-разработчик во «ВКонтакте». Раньше был фулстеком, бэкендером и DevOps, руководил отделом мобильной разработки, три года преподавал iOS-разработку в GeekBrains, был деканом факультета. Состоит в программном комитете конференции Podlodka iOS Crew, ведёт YouTube-канал с видеоуроками по Flutter. В Twitter пишет под ником @tygeddar.

об авторе

Старший iOS-разработчик во «ВКонтакте». Раньше был фулстеком, бэкендером и DevOps, руководил отделом мобильной разработки, три года преподавал iOS-разработку в GeekBrains, был деканом факультета. Состоит в программном комитете конференции Podlodka iOS Crew, ведёт YouTube-канал с видеоуроками по Flutter. В Twitter пишет под ником @tygeddar.



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

Спойлер: больше всего я люблю архитектуру MVC. Дальше расскажу, как она работает и почему мне не нравятся всякие MVVM, MVP и VIPER. Кстати, недавно я разобрался во Flux и её имплементации Redux и понял, что их я тоже недолюбливаю.

В основе статьи — тред автора в Twitter.

Впервые с архитектурой Model-View-Controller (MVC) я столкнулся в 2009 году, когда изучал веб-фреймворк Zend. В его документации было написано, что Model — это база данных, View — HTML-шаблон, а Controller — логика, которая берёт данные из БД, обрабатывает их, кладёт в шаблон и отдаёт страницу.

Я тогда был студентом, делал курсовые и пет-проекты. У них была сложная вёрстка и непростая структура БД, но максимально простая логика. Код получался простым, но в целом меня такой подход устраивал.

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

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

Я пробовал поменять логику в своём проекте — перенёс её из одного файла в другой, но разницы не заметил. По факту ничего и не изменилось, только код теперь лежал в другом файле.

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

Со временем мои проекты становились всё сложнее, а контроллеры пухлее (правда, не как UIViewController в iOS). Я пробовал с этим бороться, выносил логику в сторонние файлы, которые включал в контроллеры, но это мало что меняло: архитектура сохранялась, просто код переносился из одного файла в другой.

Кадр: сериал «Счастливы вместе»

В 2013 году я пересел на Laravel, разобрался с автозагрузкой классов в PHP, начал разбираться с ООП и прочитал «Совершенный код» Стива Макконнелла.

Стало ясно, что не стоит складывать всё в один файл — код и классы должны организовывать структуру, а некоторые фрагменты кода лучше убрать из MVC и выделить в самостоятельные части, которые можно переиспользовать.

С этого момента я начал писать проекты по-другому. В них появились иерархии классов, которые хранили логику, а контроллер сильно похудел — он получал данные от базы, передавал их в разные пакеты, получал от них результат и отправлял на HTML-страницу.

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

Следующий качественный скачок случился, когда я отошёл от веб-разработки и углубился в бэкенд. Мне пришлось проектировать и разрабатывать сложную систему управления VDS-сервером. Там были API, плагины, менеджер зависимостей для плагинов, асинхронный код, много режимов работы, связь с операционной системой и разным софтом. Основная задача проекта — чтобы у системы было ядро и самостоятельные плагины, которые бы умели работать вместе.

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

Получилось так: HTML ⟷ JavaScript (модели, общение с API) ⟷ API ⟷ переиспользуемые пакеты ⟷ бизнес-логика и доступ к данным. Всё это не было похоже на MVC.

Потом я ушёл в iOS-разработку и временно перестал думать про архитектуру. Изучал UIKit, а компоненты располагал по наитию. HTML и CSS превратились в разные UIView, тонкий контроллер — в UIViewController, бизнес-логика — в сервисы.

C MVC всё работало хорошо, но я читал и про другие архитектуры. Люди рассказывали, как MVVM, MVP или VIPER упростили им жизнь, поэтому я тоже решил их попробовать.

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

Архитектура не даёт преимуществ. Ни одна продвинутая архитектура не была лучше того, что я делал в самом начале. За всё время я перепробовал разные подходы и поэтому мог оценить их пользу для проекта. Но между MVC и MVP не было разницы — кроме названий классов и правил вроде тех, когда элементы вызывают друг друга.

Компании понимают архитектуру по-разному. Одни говорят, что используют MVVM, у других то же самое называется MVC. Я видел пять MVVM-систем, и все были разными. Исключение — VIPER, у которой благодаря Егору Толстому есть подробная документация и много примеров. Но даже там были отличия.

Популярная архитектура не значит лучшая. Выбирать архитектуру из-за мейнстримности бесполезно. Кто-то решает использовать MVVM, но одни и те же компоненты кладёт в разные части проекта.

Архитектура не спасёт проект. Сама по себе она не решает проблемы и не гарантирует успеха.

Я постоянно изучал архитектуры, читал книги и спорил с коллегами, несколько раз пересматривал идею MVC в языке Smalltalk и несколько раз менял к ней отношение.

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

Приложения с бизнес-логикой и доступом к данным были и до MVC, им не хватало только пользовательского интерфейса. Главная задача MVC — связать UI со всем остальным. Единственная рекомендация от создателя — при надобности создавать для каждой View свой фасад для Model и слушать его через паттерн-наблюдатель.

View — это и есть пользовательский интерфейс, Model — остальное приложение. Задача Controller — не быть прослойкой между V и M, а всего лишь принимать информацию от пользователя.

Принцип MVC — не мешать UI с бизнес-логикой, базой данных и другими частями приложения. А как это реализовать, уже пускай думает архитектор. Это не космическая инженерия.

Важно понимать, что MVP, MVVM или VIPER не заменяют MVC, а только дополняют её. Контроллер уже не нужен, потому что за ввод данных отвечает View, это стало его неотъемлемой частью.

Получается, что MVC в Apple, MVVM и другие варианты — это MV, где контроллер убрали за ненадобностью. Из всех современных MV(x) именно MVVM больше всего похожа на каноническую MVC.

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

Кадр: из фильма «Хищник»

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

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

Model — ваша ответственность. Архитектура MVC не даёт инструкций, как правильно написать основную часть приложения. Ваша ответственность в том, чтобы не устраивать в Model кашу, где половина классов — Service, а вторая половина — Helper.

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

Когда вы разобрались с основами, можно подходить к архитектуре Flux и библиотеке Redux. Я выделил их, потому что Facebook* сформулировал подробный гайд по Flux, а также выпустил под неё библиотеку. Неожиданно, но это тоже MVC — M и V разделены, и V слушает изменения в M. Правда, тут появились дополнительные ограничения, которые все тоже трактуют по-своему.

Redux — хорошая штука, но и у неё есть проблемы. Я использовал эту библиотеку в проекте, который писал и поддерживал сам. Всем компонентам старался давать правильные названия, завязывал на Store не все View, а только начальную сцену, группировал middleware и редюсеры, даже связывал их со стейтом.

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

Код был расширяемый и поддерживаемый, но, если я хотел что-то изменить, приходилось править гору файлов. Это очень больно. А если учесть, что проект работал на бойлерплейтном Flutter, то боль усиливалась на порядок.

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

Настоящая архитектура — та, которая описывает ваши подходы, она должна быть понятна всей команде. Если я буду делать приложение на SwiftUI, то выберу классическую MV — ту, где View следит за Model (многие называют это MVVM). И вам рекомендую поступать так же.

* Решением суда запрещена «деятельность компании Meta Platforms Inc. по реализации продуктов — социальных сетей Facebook* и Instagram* на территории Российской Федерации по основаниям осуществления экстремистской деятельности».

Научитесь: Архитектор ПО
Узнать больше

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

В 2020 году в мире насчитывалось более 3,5 миллиарда пользователей мобильных телефонов, а в 2023 году ожидается рост этого числа до 3,8 миллиарда пользователей. Все эти факторы оказывают положительное влияние на мировой рынок приложений, который, по прогнозам, достиг 693 млрд долларов в 2021 году и вырастет до 935,2 млрд долларов в 2023 году. Поэтому, если вы планируете создание мобильного приложения, это станет верным способом привлечь и удержать больше клиентов и увеличить доходы вашей компании.

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

Что такое архитектура мобильного приложения?

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

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

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

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

Что определяет хорошую архитектуру мобильного приложения?

При создании архитектуры мобильного приложения разработчики программного обеспечения должны следовать ряду принципов, таких как:

  • SOLID — 5 принципов объектно-ориентированного программирования для построения простых в обслуживании и масштабируемых приложений.
  • KISS — принцип сохранения простоты системы и кода для минимизации количества ошибок.
  • DRY — принцип сокращения повторений в паттернах программного обеспечения для избежания избыточности.

Кроме того, при создании мобильного приложения разработчики должны ориентироваться на архитектуру CLEAN  (Чистая архитектура). Этот тип архитектуры означает, что каждый слой приложения не зависит от каких-либо внешних программ или других слоев. Для соединения независимых слоев разработчики используют правило зависимости, когда переходы между слоями осуществляются с помощью границ. Границы — это порты ввода и вывода, которые позволяют передавать данные между слоями.

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

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

Для успешной разработки iOS разработчики обычно используют такие языки программирования как Swift, Objective-C и такие инструменты разработки как iOS SDK, XCode, AppCode, а для Android — языки программирования Kotlin, Java, C/C++ и инструменты Android SDK, Eclipse, Android Studio и другие. Эти языки программирования и инструменты являются наиболее эффективными, когда речь идет о разработке сложных функций приложений, необходимых любому бизнесу.

Многоуровневая архитектура мобильных приложений

Каждое мобильное приложение имеет свои слои. Наиболее популярной моделью слоев является трехслойная архитектура. Она состоит из: презентационного слоя, бизнес-слоя и слоя данных.

  • Презентационный слой
  • Слой бизнес-логики
  • Слой доступа к данным

Презентационный слой

Презентационный слой представляет данные пользователю. По сути, это то, что видит и что воспринимается пользователем при работе с приложением. При создании презентационного слоя разработчики программного обеспечения уделяют особое внимание дизайну пользовательского интерфейса (UI) и пользовательского опыта (UX). Дизайн UI/UX включает в себя визуальные компоненты, такие как шрифты, темы, цвета, интуитивность навигации приложения, какие периферийные устройства может подключать ваше приложение и другие.

Слой бизнес-логики

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

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

Слой доступа к данным

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

Как правильно выбрать архитектурное решение для  мобильного приложения? 

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

Существует три основных типа приложений:

  • нативные приложения;
  • гибридные приложения;
  • мобильные веб-приложения.

Нативные приложения

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

API мобильного устройства. В магазинах приложений представлено большое количество приложений такого типа.

Нативные приложения создаются для конкретной мобильной платформы с использованием определенных языков программирования и фреймворков. Например, для создания приложения для Android вам понадобится Java и Android studio. Поэтому, если вы хотите запустить еще и приложение на платформе iOS, вам придется создавать новое приложение с нуля, используя инструменты, подходящие для iOS, такие как Swift и AppCode.

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

Архитектура мобильных приложений для Android

Для операционной системы Android не существует такого понятия, как единая мобильная архитектура, и Google не предоставляет никакой документации или руководства по конкретной архитектуре. Тем не менее путем проб и ошибок специалисты пришли к выводу, что архитектура Clean лучше всего подходит для приложений Android.

Архитектура «Clean» обычно представляется в виде круга из четырех слоев:

  • Entities— бизнес-логика общая для многих приложений.
  • Use Cases (Interactors) — логика приложения.
  • Interface Adapters — адаптеры между Use Cases и внешним миром. Сюда попадают Presenter’s из MVP, а также Gateways (более популярное название репозитории).
  • Frameworks — самый внешний слой, тут лежит все остальное: UI, база данных, http-клиент, и т.п.

Для соблюдения Dependency Rule, бизнес-логика и логика приложения должны не зависеть от Presenter’s, UI, и каждый слой должен иметь свои Границы. Эти Границы устанавливают связь между слоями, предоставляя им два интерфейса — выходной порт для ответа и входной порт для запроса.

Четкая структура и независимость слоев и создают Clean Architecture:

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

Архитектура мобильных приложений для iOS

В отличие от Android, операционная система Apple предлагает разработчикам гораздо больше рекомендаций по построению архитектуры мобильных приложений iOS на основе модели MVC (Model-View-Controller). Однако разработчики iOS не ограничены только одним Архитектурным паттерном, этот паттерн просто является наиболее часто используемым в iOS приложениях.

Компоненты  MVC: 

  • Модель / Model — этот компонент отвечает за данные, а также определяет структуру приложения. Например, если вы создаете To-Do приложение, код компонента model будет определять список задач и отдельные задачи.
  • Представление / View — этот компонент отвечает за взаимодействие с пользователем. То есть код компонента view определяет внешний вид приложения и способы его использования.
  • Контроллер / Controller  — этот компонент отвечает за связь между model и view. Код компонента controller определяет, как сайт реагирует на действия пользователя. По сути, это мозг MVC-приложения.

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

Используя модель MVC, разработчики iOS:

  • Значительно ускоряют процесс разработки мобильных приложений;
  • Обеспечивают прозрачное взаимодействие между слоями приложения;
  • Получают хорошо структурированную, а так же простую в тестировании кодовую базу.

Гибридные приложения

Гибридные приложения — это решения, представляющее собой по сути веб-приложение в нативной оболочке соответствующей мобильной платформы. Для разработки этих решений используют веб-технологии, запускаются внутри нативных приложений, представляя веб-контент в обертке собственного приложения. Их содержимое может быть размещено в приложении или доступно с веб-сервера. Таким образом, гибридные приложения имеют доступ к аппаратному обеспечению устройства и в то же время являются веб-приложениями, сочетая в себе веб- и нативные возможности. Эти приложения также можно найти в интернет магазинах приложений.

Обычно, гибридные приложения намного дешевле и быстрее в разработке, поскольку они могут использовать нативные API, такие как контакты, камеру и так далее. Они имеют одну кодовую базу для приложений Android и iOS, что означает, что вам не нужно разрабатывать два приложения с нуля для каждой платформы. Так же они являются более простыми в удобными в обслуживании.

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

Архитектура гибридных мобильных приложений

При создании гибридных мобильных приложений основная цель, которую преследуют разработчики, — создать приложение с единой кодовой базой, подходящей для платформ Android и iOS. Для этого разработчики смешивают нативный (Android/iOS) код с веб-кодом.

Гибридная архитектура обычно выглядит следующим образом:

Презентационный слой имеет веб-ориентированное представление. Он построен с помощью веб-технологий, таких как HTML, CSS и JavaScript, который работает на движке рендеринга. Разработчики программного обеспечения используют PhoneGap, Apache Cordova или Ionic Capacitor, которые взаимодействуют с нативной частью через вызовы API.

Мобильные веб-приложения

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

Эти приложения создаются с помощью технологий HTML, JavaScript и CSS и автоматически обновляются из Интернета без какого-либо процесса представления или утверждения пользователя.

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

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

Заключение

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

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

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

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

mobile app download growth statisticsС недавним ростом спроса на мобильные приложения, связанные с бизнесом, увеличился и ассортимент предлагаемых мобильных приложений. Сегодня предприятия могут использовать традиционный путь создания мобильного приложения с помощью команды разработчиков программного обеспечения. Однако на выбор также предлагается множество готовых для платформы мобильных приложений “с низким кодом и без кода”. Эти варианты разработки мобильных приложений еще больше упростили бизнес-процессы. Это помогает им быть более отзывчивыми к своим клиентам без необходимости быть экспертами в области кодирования. Предприятия должны иметь базовое понимание архитектуры мобильных приложений, чтобы привлекать и поддерживать интерес пользователей своих мобильных приложений.

Что такое архитектура мобильных приложений?

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

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

3 архитектуры дизайна

Успешная архитектура мобильного приложения использует принципы проектирования трех терминов, приведенных ниже:

  • SOLID архитектура мобильного приложения
  • KISS архитектура мобильных приложений
  • DRY архитектура мобильных приложений

SOLID-архитектура

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

KISS-архитектура

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

DRY-архитектура

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

ДОПОЛНИТЕЛЬНЫЙ ПРИНЦИП МОБИЛЬНОЙ АРХИТЕКТУРЫ

Существует также несколько дополнительных принципов разработки архитектуры мобильных приложений; наиболее важные из них перечислены ниже:

ЧИСТАЯ АРХИТЕКТУРА

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

Каковы основные элементы мобильной архитектуры?

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

Пользовательский опыт или UX-дизайн

Хорошо продуманный пользовательский интерфейс (UI) является ключевым элементом архитектуры хорошего мобильного приложения. Пользовательский опыт или UX-дизайн гарантирует, что архитектура вашего мобильного приложения будет интуитивно понятной. Это позволит создать привлекательный и бесшовный опыт работы с мобильными приложениями для пользователей. UI и UX дизайн отражают продуманность разработчиков на этапе разработки приложения. Обычно можно определить, учитывал ли разработчик программного обеспечения потребности конечных пользователей, поскольку это отражается в архитектуре мобильного приложения. Когда разработчики уделяют пристальное внимание UI и UX дизайну архитектуры мобильного приложения, результатом является интуитивно понятное, удобное для пользователя мобильное приложение.

UI and UX designИсточник: Dribbble

Пропускная способность сети или сетевая стратегия

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

Попробуйте no-code платформу AppMaster

AppMaster поможет создать любое веб, мобильное или серверное приложение в 10 раз быстрее и 3 раза дешевле

Начать бесплатно

Стратегия навигации

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

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

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

Используемое устройство

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

Как мобильные приложения выбирают архитектуру?

Что же делает хорошую и лучшую технологическую основу или архитектуру мобильного приложения? Для успешной разработки архитектуры мобильного приложения необходимо обратить внимание на следующие параметры:

Логичная и четко определенная

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

Универсальное использование на всех платформах

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

Масштабируемый технологический стек

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

Полностью функциональная

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

Низкая стоимость обслуживания

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

Каковы 3 уровня веб-приложений?

Архитектура приложения на основе веб-сайта или веб-приложения включает в себя 3 ключевых слоя, которые необходимо эффективно разработать. Ниже мы подробно рассмотрим каждый из основных слоев:

  • Слой 1 – Презентация
  • Слой 2 – бизнес
  • Уровень 3 – данные

Презентация

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

Бизнес

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

Данные

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

Какая архитектура лучше всего подходит для мобильных приложений?

Итак, какая архитектура считается лучшей для мобильных приложений? И почему? Чтобы выбрать лучший тип архитектуры мобильного приложения для ваших приложений, разработчикам необходимо учитывать платформу, предполагаемых конечных пользователей, процессы обработки данных, ключевые функции приложения и бюджет проекта. Обычно команды разработчиков и разработчики имеют возможность выбрать архитектуру мобильного приложения по своему вкусу. Для начала работы они могут выбрать один из трех вариантов, а именно: нативную, веб-ориентированную и гибридную мобильную архитектуру и технологические стеки. Выбор зависит от их личных предпочтений и стиля, который им удобен. Однако если вы ищете вариант разработки мобильных приложений без кода или с низким кодом, App Master – отличный и удобный способ начать работу без лишних хлопот.

Попробуйте no-code платформу AppMaster

AppMaster поможет создать любое веб, мобильное или серверное приложение в 10 раз быстрее и 3 раза дешевле

Начать бесплатно

no code mobile builder

Нативные мобильные приложения

Нативные мобильные приложения размещаются в самом используемом устройстве и предназначены для работы на мобильных устройствах в самых разных условиях. Поскольку нативные приложения “живут” на главном экране мобильных устройств, они наиболее полезны при неблагоприятных обстоятельствах. Например, нативные приложения могут эффективно работать через главный экран устройства даже в условиях низкой пропускной способности сети или ее отсутствия. Нативные приложения не рассчитаны на громоздкость и построены на облегченной архитектуре мобильных приложений.

В результате нативные приложения эффективно обрабатывают данные и имеют интуитивно понятный дизайн. Нативные приложения также являются универсальными, удобными (UI) приложениями для пользователей, работающих в автономном режиме или в условиях низкой пропускной способности. Универсальность этих нативных мобильных приложений позволяет пользователям эффективно работать с ними на широком спектре физических устройств с различными размерами. Нативные приложения также хорошо работают на различных типах платформ, таких как Android, IOS или веб-платформа.

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

Мобильные веб-приложения

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

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

Гибридные мобильные приложения

Новое соображение при создании архитектуры приложения – это оба варианта; гибридная архитектура мобильных приложений – идеальное решение для устранения недостатков нативных и веб-приложений. Гибридные приложения могут взаимодействовать между веб и работать на родных устройствах и платформах. Они также проще, дешевле и требуют меньшего обслуживания, чем их нативные аналоги. Однако один недостаток этого типа архитектуры может возникнуть из-за проблем с возможностями и функциями подключения на основе веб-технологий. Интерфейс с Интернетом может создавать проблемы с подключением для пользователей, которым требуется эффективная работа с приложением в автономном режиме.

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

ПРЕИМУЩЕСТВА

  • Более широкая целевая аудитория
  • Простота и быстрота разработки
  • Низкая стоимость создания
  • Низкая стоимость обслуживания
  • Широкая интеграция

Что такое диаграмма архитектуры мобильных приложений?

Диаграмма архитектуры мобильного приложения визуально представляет элементы и компоненты дизайна приложения. По сути, она отвечает на вопрос “как”, который относится к внутренним процессам, участвующим в создании функционального и эффективного мобильного приложения. Важно, чтобы этот тип диаграммы был разработан в рамках начального процесса разработки приложения. Она помогает разработчикам программного обеспечения и заинтересованным сторонам визуализировать цель и творческий процесс создания конечного продукта мобильного приложения. Она также помогает командам определить соответствующий стек технологий, возможности базы данных, дизайн пользовательского интерфейса и UX, платформу приложения и ключевую функциональность мобильного приложения.

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

  • Идентифицировать системный процесс
  • Позволяет обратную связь
  • Дает визуальный контекст

Идентифицировать системные процессы

Хорошая диаграмма архитектуры мобильного приложения отображает взаимосвязь между компонентами приложения и процессами. Сюда входит пользовательский опыт или UX-дизайн, управление базой данных и функции программного обеспечения. Логическая диаграмма потока и функций процессов приложения может помочь разработчикам визуализировать концепцию мобильного приложения.

Позволяет обратную связь

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

Попробуйте no-code платформу AppMaster

AppMaster поможет создать любое веб, мобильное или серверное приложение в 10 раз быстрее и 3 раза дешевле

Начать бесплатно

Дает визуальный контекст

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

Каковы этапы создания базовых мобильных приложений?

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

  • Определение реалистичного бюджета
  • Фаза обнаружения архитектуры приложения
  • Самые необходимые функции приложения
  • Выбор подходящей платформы
  • Создание MVP приложения
  • Тестирование приложения перед запуском
  • Запуск готового приложения
  • Регулярное обслуживание приложения
  • Отслеживание показателей приложения

Составьте реалистичный бюджет

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

Фаза обнаружения

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

Выберите функции приложения

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

Выберите платформу

Пользовательский интерфейс (UI) и пользовательский опыт или UX дизайн архитектуры мобильного приложения будет зависеть от выбранной платформы. UI и UX должны быть способны взаимодействовать с платформой мобильного приложения. Архитектура мобильного приложения должна способствовать бесшовному взаимодействию, независимо от того, является ли мобильная платформа Android, веб-платформой или iOS,

Создайте MVP

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

Тестирование мобильного приложения

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

User experienceИсточник: Dribbble

Запуск мобильного приложения

Развертывание мобильного приложения – это полуфинальная стадия процесса разработки приложения. Т включает в себя продвижение и маркетинг приложения среди конечных пользователей. Стимулировать пользователей могут внутриприкладные акции или маркетинговые кампании.

Обслуживание приложения

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

Отслеживание показателей приложения

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

Итог

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

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

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

3 месяца назад·6 мин. на чтение

Эта статья поможет вам избежать некоторых распространенных ошибок, которые большинство разработчиков допускают при создании архитектуры приложений на реакте, и подскажет вам правильный способ структурирования директорий.
Прежде чем начать, необходимо подчеркнуть один момент: не существует идеального решения, которое подходит для любого возможного случая. Это особенно важно понимать, потому что многие разработчики всегда ищут единственное и неповторимое решение всех своих проблем.
Если вы попали сюда, это значит, что вы заинтересовались этой темой, так что пора начинать! Все содержимое, которое будет упоминаться, будет помещено в каталог src, и каждая упомянутая новая папка будет располагаться относительно этой директории.

Компоненты

Что в первую очередь создает React-разработчик в проекте? React-приложения создаются с помощью компонентов.
Существует много различных архитектур (некоторые очень хорошие, а другие ужасные) и есть весьма универсальный путь, который можно использовать в большинстве случаев, даже для небольших проектов.
Вот как это выглядит:

├── components
│   ├── common
│   │   └── button
│   │       ├── button.tsx
│   │       ├── button.stories.tsx
│   │       ├── button.spec.tsx
│   │       └── index.ts
│   └── signup-form
│       ├── signup-form.tsx
│       ├── signup-form.spec.tsx
│       └── index.ts

Ключевым моментом здесь является следующее: у нас есть папка components, содержащая все компоненты, которые используются в приложении более одного раза, поэтому мы собираемся исключить все специфические компоненты из этой папки.
Почему? Просто потому, что смысл этой папки заключается в том, чтобы содержать логику многократного использования. Кнопка должна использоваться почти на каждой странице нашего приложения, поэтому и существует общая папка common. Для компонента signup-form происходит нечто иное. Почему?
Предположим, что у нас есть две разные страницы (подробнее об этом позже) для входа и регистрации, компонент signup-form должен повторяться два раза, вот почему он помещен в папку components.

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

button
├── button.tsx
├── button.stories.tsx
├── button.spec.tsx
└── index.ts

Это потому, что ваше приложение в конечном итоге может содержать более 1000 компонентов, и, если все они имеют тесты или файл storybook, это может легко превратиться в беспорядок. Давайте рассмотрим некоторые ключевые моменты этой папки:

  • Все файлы, связанные с этим компонентом, находятся в этой папке.
  • Все экспортируемые модули помещаются в index.ts, чтобы избежать двойного имени в импорте.
  • Все файлы названы в kebab-case.

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

  • Где находится компонент кнопки? -> В папке button.
  • Где находятся сторибуки для этой кнопки? -> В папке button.
  • Мне нужно найти тесты для этой кнопки, где я могу его найти? -> Очевидно, в папке button.

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

Страницы

Отдельной сущности для страниц в React не существует. Они тоже являются компонентами, состоящими из других компонентов. Но в отличие от других компонентов, обычно они очень строго привязаны (например, в определенный путь URL). Куда же их вставлять?
Мы можем использовать каталог views (или pages, если хотите), в который помещаются все эти вещи, например:

views
├── home.tsx
├── guestbook.tsx
└── newsletter
   ├── index.ts
   ├── newsletter.tsx
   └── components
       └── newsletter-form
           ├── newsletter-form.tsx
           ├── newsletter-form.spec.tsx
           └── index.ts

Для home и guestbook все довольно просто, страница должна быть результатом композиции других компонентов, которые имеют соответствующие тесты, поэтому для них нет специального каталога.

Иначе обстоит дело со страницей newsletter , на которой есть нечто специфическое, компонент newsletter-form. В этом случае используется подход создания вложенной папки компонентов внутри папки страницы и действуем так же, как в обычной папке компонентов, используя те же правила.
Этот подход очень эффективен, поскольку позволяет разделить код на небольшие фрагменты, но при этом сохраняет хорошо организованную архитектуру. Компонент newsletter-form не следует помещать в папку с общими components, просто потому, что это единственное место, в котором он используется. Если приложение будет расти, и компонент будет использоваться в нескольких частях, ничто не помешает вам переместить его.
Еще один совет – сохранять согласованное имя между страницей и маршрутом, примерно так:

<Route path="/bookings">
 <Route index element={<Bookings />} />
 <Route path="create" element={<CreateBooking />} />
 <Route path=":id" element={<ViewBooking />} />
 <Route path=":id/edit" element={<EditBooking />} />
 <Route path=":id/delete" element={<DeleteBooking />} />
</Route>

Лэйауты (Layouts, Макеты)

Лэйауты вообще не являются страницами, они больше похожи на компоненты, поэтому с ними можно обращаться так же, но лучше помещать их в папку layouts, так понятнее, что в этом приложении есть n лэйаутов.

layouts
├── main.tsx
└── auth.tsx

Вы можете заметить, что мы не называем их main-layout.tsx, а просто main.tsx, потому что, следуя этому шаблону, нам пришлось бы переименовать все компоненты, например, table-component.tsx, что странно. Поэтому называем все компоненты без очевидного суффикса, задаваемого родительским каталогом, а если нужно подчеркнуть, что используется макет, всегда можно использовать псевдоним импорта.

import { Main as MainLayout } from "@/layouts/main.tsx";

Контексты, хуки и хранилища

Это довольно просто, и обычно, почти все разработчики придерживаются чего-то подобного:

hooks
├── use-users.ts
└── use-click-outside.ts
contexts
├── workbench.tsx
└── authentication.tsx

Опять же, для единообразия используется kebab-case для всех имен файлов, так что нам нужно беспокоиться о том, какие из них написаны заглавными буквами, а какие нет. Для тестовых файлов, из-за того, что пользовательских хуков немного, не обязательно создавать отдельную папку, но, если вы хотите быть очень строгими, вы можете сделать и это:

hooks
├── use-users
│   ├── use-users.ts
│   ├── use-users.spec.ts
│   └── index.ts
└── use-click-outside.ts

Функции-помощники (хэлперы, helpers)

Сколько раз вы создавали красивую функцию formatCurrency, не зная, куда ее положить? Папка helpers придет вам на помощь.
Обычно сюда помещаются все файлы, которые используются для того, чтобы код выглядел лучше. Не важно, будет ли функция использоваться несколько раз или нет.

helpers
├── format-currency.ts
├── uc-first.ts
└── pluck.ts

Константы

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

constants
└── index.ts

Кроме того, можно хранить все константы в одном файле. Нет смысла разбивать каждую константу на отдельные файлы.

// @/constants/index.ts
export const COMPLANY_EMAIL = "example@example.com";

И использоваться они будут так:

import { COMPLANY_EMAIL } from "@/constants";

Стили

Просто поместите глобальные стили в папку styles, и все готово.

styles
├── index.css
├── colors.css
└── typography.css

А как насчет CSS для компонентов?
Хороший вопрос. Помните папку компонентов, о которой мы говорили некоторое время назад? Так вот, вы можете добавить больше файлов в зависимости от ваших потребностей.

button
├── button.tsx
├── button.stories.tsx
├── button.styled.tsx
├── button.module.scss
├── button.spec.tsx
└── index.ts

Если вы используете emotion, styled-components или просто CSS Modules, поместите их в папку конкретного компонента, чтобы все было оптимально упаковано.

Конфигурационные файлы

Есть ли у вашего приложения файлы конфигурации, такие как Dockerfiles, Fargate Task Definitions, webpack и т.д.? Папка config должна быть идеальным местом для них. Помещение их в соответствующую директорию позволяет избежать загрязнения корневого каталога не относящимися к делу файлами.

API

99% приложений react имеют хотя бы один вызов API к внешнему источнику данных (вашему бэкенду или какому-то публичному сервису), обычно эти операции выполняются в нескольких строках кода без особых сложностей, и именно поэтому, оптимальная их организация игнорируется.
Рассмотрим этот код:

axios
 .get("https://api.service.com/bookings")
 .then((res) => setBookings(res.data))
 .catch((err) => setError(err.message));

Довольно просто, верно? Теперь представьте, что у вас есть эти 3 строки, распределенные по 10 компонентам, потому что вы часто используете именно этот адрес сервера.
Надеюсь, вы не хотите выполнять поиск и замену всех URL в приложении, кроме того, если вы используете TypeScript, импортировать каждый раз тип ответа – это довольно повторяющееся действие.

Вместо этого рассмотрите возможность использования каталога api, который, прежде всего, содержит экземпляр клиента, используемого для вызовов, например, fetch или axios, а также файлы, содержащие декларации вызовов fetch.

api
├── client.ts
├── users.ts
└── bookings.ts

И пример файла users.ts:

export type User = {
 id: string;
 firstName: string;
 lastName: string;
 email: string;
};

export const fetchUsers = () => {
 return client.get<User[]>("/users", {
   baseURL: "https://api.service.com/v3/",
 });
};

Итоги

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

Добавить комментарий