Руководство по архитектуре приложений

Привет, Хабр! Представляю вашему вниманию вольный перевод «Руководство по архитектуре приложения (Guide to app architecture)» из JetPack. Все замечания по переводу прошу оставлять в комментариях, и они будут исправлены. Так же для всех будут полезны комментарии от тех кто использовал представленную архитектуру с рекомендациями её использования.

Это руководство охватывает лучшие практики и рекомендуемую архитектуру для создания надежных приложений. Эта страница предполагает базовое знакомство с Android Framework. Если вы новичок в разработке приложений для Android, ознакомьтесь с нашими руководствами для разработчиков, чтобы начать работу и узнать больше о концепциях, упомянутых в этом руководстве. Если вы интересуетесь архитектурой приложений и хотели бы ознакомиться с материалами этого руководства с точки зрения программирования на Kotlin, ознакомьтесь с курсом Udacity «Разработка приложений для Android с помощью Kotlin».

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

В большинстве случаев настольные приложения имеют единую точку входа с рабочего стола или программы запуска, а затем запускаются как единый монолитный процесс. Приложения на Android имеют гораздо более сложную структуру. Типичное приложение для Android содержит несколько компонентов приложения, включая Activities, Fragments, Services, ContentProviders и BroadcastReceivers.

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

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

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

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

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

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

Общие архитектурные принципы

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

Разделение ответственности

Самый важный принцип, которому нужно следовать, — это разделение ответственности. Распространена ошибка, когда вы пишете весь свой код в Activity или Fragment. Это классы пользовательского интерфейса которые должны содержать только логику обрабатывающую взаимодействие пользовательского интерфейса и операционной системы. Как можно больше разделяя ответственность в этих классах (SRP), вы можете избежать многих проблем, связанных с жизненным циклом приложения.

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

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

Постоянная модель идеально подходит по следующим причинам:

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

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

Рекомендуемая архитектура приложения

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

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

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

Обзор

Для начала рассмотрим схему взаимодействия модулей архитектуры готового приложения:

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

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

Создаём пользовательский интерфейс

Пользовательский интерфейс состоит из фрагмента UserProfileFragment и соответствующего ему файла макета user_profile_layout.xml.

Для управления пользовательским интерфейсом наша модель данных должна содержать следующие элементы данных:

  • User ID: идентификатор пользователя. Лучшим решением является передача этой информации во фрагмент, используя аргументы фрагмента. Если ОС Android разрушает наш процесс, эта информация сохраняется, поэтому идентификатор будет доступен при следующем запуске нашего приложения.
  • User object: класс данных, который содержит сведения о пользователе.

Мы используем UserProfileViewModel, основанный на компоненте архитектуры ViewModel, чтобы сохранить эту информацию.

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

Теперь мы определили следующие файлы:

  • user_profile.xml: определили макет пользовательского интерфейса.
  • UserProfileFragment: описали контроллер пользовательского интерфейса, который отвечает за отображение информации пользователю.
  • UserProfileViewModel: класс отвечающий за приготовление данных для отображения их в UserProfileFragment и реагирует на взаимодействие с пользователем.

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

class UserProfileViewModel : ViewModel() {
   val userId : String = TODO()
   val user : User = TODO()
}

class UserProfileFragment : Fragment() {
   private val viewModel: UserProfileViewModel by viewModels()

   override fun onCreateView(
       inflater: LayoutInflater, container: ViewGroup?,
       savedInstanceState: Bundle?
   ): View {
       return inflater.inflate(R.layout.main_fragment, container, false)
   }
}

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

Примечание. SavedStateHandle позволяет ViewModel получить доступ к сохраненному состоянию и аргументам связанного фрагмента или действия.

// UserProfileViewModel
class UserProfileViewModel(
   savedStateHandle: SavedStateHandle
) : ViewModel() {
   val userId : String = savedStateHandle["uid"] ?:
          throw IllegalArgumentException("missing user id")
   val user : User = TODO()
}

// UserProfileFragment
private val viewModel: UserProfileViewModel by viewModels(
   factoryProducer = { SavedStateVMFactory(this) }
   ...
)

Теперь нам нужно сообщить нашему Фрагменту, когда получен пользовательский объект. Вот тут-то и появляется компонент архитектуры LiveData.

LiveData — это наблюдаемый держатель данных. Другие компоненты в вашем приложении могут отслеживать изменения объектов, используя этот держатель, не создавая явных и жестких путей зависимости между ними. Компонент LiveData также учитывает состояние жизненного цикла компонентов вашего приложения, таких как Activities, Fragments и Services, и включает логику очистки для предотвращения утечки объектов и чрезмерного потребления памяти.

Примечание. Если вы уже используете такие библиотеки, как RxJava или Agera, вы можете продолжать использовать их вместо LiveData. Однако при использовании библиотек и подобных подходов убедитесь, что вы правильно обрабатываете жизненный цикл своего приложения. В частности, убедитесь, что вы приостановили свои потоки данных, когда связанный LifecycleOwner остановлен, и уничтожили эти потоки, когда связанный LifecycleOwner был уничтожен. Вы также можете добавить артефакт android.arch.lifecycle: реактивные потоки, чтобы использовать LiveData с другой библиотекой реактивных потоков, такой как RxJava2.

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

class UserProfileViewModel(
   savedStateHandle: SavedStateHandle
) : ViewModel() {
   val userId : String = savedStateHandle["uid"] ?:
          throw IllegalArgumentException("missing user id")
   val user : LiveData<User> = TODO()
}

Теперь модифицируем UserProfileFragment для наблюдения за данными во ViewModel и для обновления пользовательского интерфейса в соответствии с изменениями:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)
   viewModel.user.observe(viewLifecycleOwner) {
       // обновляем UI
   }
}

Каждый раз, когда данные профиля пользователя обновляются, вызывается обратный вызов onChanged(), и пользовательский интерфейс обновляется.

Если вы знакомы с другими библиотеками, в которых используются наблюдаемые обратные вызовы, возможно, вы поняли, что мы не переопределили метод onStop() фрагмента, чтобы прекратить наблюдать за данными. Этот шаг не является обязательным для LiveData, поскольку он поддерживает жизненный цикл, это означает, что он не вызовет обратный вызов onChanged(), если фрагмент находится в неактивном состоянии; то есть он получил вызовonStart(), но еще не получил onStop()). LiveData также автоматически удаляет наблюдателя при вызове метода onDestroy() у фрагмента.

Мы не добавили никакой логики для обработки изменений конфигурации, таких как поворот экрана устройства пользователем. UserProfileViewModel автоматически восстанавливается при изменении конфигурации, поэтому, как только создается новый фрагмент, он получает тот же экземпляр ViewModel, и обратный вызов вызывается немедленно с использованием текущих данных. Учитывая, что объекты ViewModel предназначены для того, чтобы пережить соответствующие объекты View, которые они обновляют, вы не должны включать прямые ссылки на объекты View в вашу реализацию ViewModel. Для получения дополнительной информации о сроке службы ViewModel соответствует жизненному циклу компонентов пользовательского интерфейса, см. Жизненный цикл ViewModel.

Получение данных

Теперь, когда мы использовали LiveData для подключения UserProfileViewModel к UserProfileFragment, как мы можем получить данные профиля пользователя?

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

Вот наше определение Webservice , который связывается с нашим backend-ом:

interface Webservice {
   /**
    * @GET declares an HTTP GET request
    * @Path("user") annotation on the userId parameter marks it as a
    * replacement for the {user} placeholder in the @GET path
    */
   @GET("/users/{user}")
   fun getUser(@Path("user") userId: String): Call<User>
}

Первая идея для реализации ViewModel может включать прямой вызов Webservice для извлечения данных и назначения этих данных нашему объекту LiveData. Этот дизайн работает, но с его использованием наше приложение становится все сложнее поддерживать по мере роста. Это дает слишком большую ответственность классу UserProfileViewModel, что нарушает принцип разделения интересов. Кроме того, область действия ViewModel связана с жизненным циклом Activity или Fragment, что означает, что данные из Webservice теряются, когда заканчивается жизненный цикл связанного объекта пользовательского интерфейса. Такое поведение создает нежелательный пользовательский опыт.

Вместо этого наша ViewModel делегирует процесс извлечения данных новому модулю, хранилищу.

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

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

class UserRepository {
   private val webservice: Webservice = TODO()
   // ...
   fun getUser(userId: String): LiveData<User> {
       // Это не оптимальная реализация. Мы исправим это позже.
       val data = MutableLiveData<User>()
       webservice.getUser(userId).enqueue(object : Callback<User> {
           override fun onResponse(call: Call<User>, response: Response<User>) {
               data.value = response.body()
           }
           // Случай ошибки опущен для краткости.
           override fun onFailure(call: Call<User>, t: Throwable) {
               TODO()
           }
       })
       return data
   }
}

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

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

Управление зависимостями между компонентами

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

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

  • Внедрение зависимостей (DI). Внедрение зависимостей позволяет классам определять свои зависимости, не создавая их. Во время выполнения, другой класс отвечает за предоставление этих зависимостей. Мы рекомендуем библиотеку Dagger 2 для реализации внедрения зависимостей в приложениях Android. Dagger 2 автоматически создает объекты, обходя дерево зависимостей, и обеспечивает гарантии времени компиляции для зависимостей.
  • (Service location) Локатор службы: шаблон локатора службы предоставляет реестр, в котором классы могут получать свои зависимости вместо их построения.

Реализовать реестр служб проще, чем использовать DI, поэтому, если вы не знакомы с DI, вместо этого используйте шаблон: локация служб.

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

В нашем примере приложения используется Dagger 2 для управления зависимостями объекта Webservice.

Подключите ViewModel и хранилище

Теперь мы модифицируем наш UserProfileViewModel для использования объекта UserRepository:

class UserProfileViewModel @Inject constructor(
   savedStateHandle: SavedStateHandle,
   userRepository: UserRepository
) : ViewModel() {
   val userId : String = savedStateHandle["uid"] ?:
          throw IllegalArgumentException("missing user id")
   val user : LiveData<User> = userRepository.getUser(userId)
}

Кеширование

Реализация UserRepository абстрагирует вызов объекта Webservice, но поскольку он опирается только на один источник данных, он не очень гибок.

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

Эта конструкция является неоптимальной по следующим причинам:

  • Это тратит ценные ресурсы трафика.
  • Это заставляет пользователя ожидать завершения нового запроса.

Чтобы устранить эти недостатки, мы добавляем новый источник данных в наш UserRepository, который кэширует объекты User в памяти:

//Информируем Dagger, что этот класс должен быть создан только единожды.
@Singleton
class UserRepository @Inject constructor(
   private val webservice: Webservice,
   // Простой кэш в памяти. Детали опущены для краткости.
   private val userCache: UserCache
) {
   fun getUser(userId: String): LiveData<User> {
       val cached = userCache.get(userId)
       if (cached != null) {
           return cached
       }
       val data = MutableLiveData<User>()
       userCache.put(userId, data)
       // Эта реализация все еще неоптимальная, но лучше, чем раньше.
       // Полная реализация также обрабатывает случаи ошибок.
       webservice.getUser(userId).enqueue(object : Callback<User> {
           override fun onResponse(call: Call<User>, response: Response<User>) {
               data.value = response.body()
           }

           // Случай ошибки опущен для краткости.
           override fun onFailure(call: Call<User>, t: Throwable) {
               TODO()
           }
       })
       return data
   }
}

Постоянные данные

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

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

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

Правильный способ справиться с этой ситуацией — использовать постоянную модель. Нам на помощь приходит библиотека сохранения постоянных данных (БД) Room.

Room — это библиотека объектно-реляционного отображения (object-mapping), которая обеспечивает локальное сохранение данных с минимальным стандартным кодом. Во время компиляции он проверяет каждый запрос на соответствие вашей схеме данных, поэтому неработающие запросы SQL приводят к ошибкам во время компиляции, а не к сбоям во время выполнения. Room абстрагируется от некоторых базовых деталей реализации работы с необработанными таблицами SQL и запросами. Это также позволяет вам наблюдать за изменениями в данных БД, включая коллекции и запросы на соединение, выставляя такие изменения с помощью объектов LiveData. Он даже явно определяет ограничения выполнения, которые решают общие проблемы с потоками, такие как доступ к хранилищу в основном потоке.

Примечание. Если ваше приложение уже использует другое решение, такое как объектно-реляционное отображение SQLite (ORM), вам не нужно заменять существующее решение на Room. Однако, если вы пишете новое приложение или реорганизуете существующее приложение, мы рекомендуем использовать Room для сохранения данных вашего приложения. Таким образом, вы можете воспользоваться возможностями абстракции библиотеки и проверки запросов.

Чтобы использовать Room, нам нужно определить нашу локальную схему. Сначала мы добавляем аннотацию @Entity в наш класс модели данных User и аннотацию @PrimaryKey в поле id класса. Эти аннотации помечают User как таблицу в нашей базе данных, а id — как первичный ключ таблицы:

@Entity
data class User(
   @PrimaryKey private val id: String,
   private val name: String,
   private val lastName: String
)

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

@Database(entities = [User::class], version = 1)
abstract class UserDatabase : RoomDatabase()

Обратите внимание, что UserDatabase является абстрактной. Библиотека Room автоматически обеспечивает реализацию этого. Подробности смотрите в документации по Room.

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

@Dao
interface UserDao {
   @Insert(onConflict = REPLACE)
   fun save(user: User)

   @Query("SELECT * FROM user WHERE id = :userId")
   fun load(userId: String): LiveData<User>
}

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

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

Определив наш класс UserDao, мы затем ссылаемся на DAO из нашего класса базы данных:

@Database(entities = [User::class], version = 1)
abstract class UserDatabase : RoomDatabase() {
   abstract fun userDao(): UserDao
}

Теперь мы можем изменить наш UserRepository, чтобы включить источник данных Room:

// Информирует Dagger, что этот класс должен быть создан только один раз.
@Singleton
class UserRepository @Inject constructor(
   private val webservice: Webservice,
   // Простой кэш в памяти. Детали опущены для краткости.
   private val executor: Executor,
   private val userDao: UserDao
) {
   fun getUser(userId: String): LiveData<User> {
       refreshUser(userId)
       // Возвращает объект LiveData непосредственно из базы данных.
       return userDao.load(userId)
   }

   private fun refreshUser(userId: String) {
       // Работает в фоновом потоке.
       executor.execute {
           // Проверьте, если пользовательские данные были получены недавно.
           val userExists = userDao.hasUser(FRESH_TIMEOUT)
           if (!userExists) {
               // Обновляем данные.
               val response = webservice.getUser(userId).execute()

               // Проверьте на ошибки здесь.

               // Обновляем базу данных. Объект LiveData автоматически обновляется,
               // поэтому нам здесь больше ничего не нужно делать.
               userDao.save(response.body()!!)
           }
       }
   }

   companion object {
       val FRESH_TIMEOUT = TimeUnit.DAYS.toMillis(1)
   }
}

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

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

Единственный источник правды

Обычно разные конечные точки REST API возвращают одни и те же данные. Например, если у нашего бэкэнда есть другая конечная точка, которая возвращает список друзей, один и тот же пользовательский объект может исходить из двух разных конечных точек API, возможно, даже с использованием разных уровней детализации. Если бы UserRepository возвращал ответ от запроса Webservice как есть, без проверки согласованности, наши пользовательские интерфейсы могли бы показывать запутанную информацию, потому что версия и формат данных из хранилища зависели бы от последней вызванной конечной точки.

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

Показывать прогресс операции

В некоторых случаях использования, таких как pull-to-refresh, важно, чтобы пользовательский интерфейс показывал пользователю, что в данный момент выполняется сетевая операция. Рекомендуется отделять действие пользовательского интерфейса от фактических данных, поскольку данные могут обновляться по разным причинам. Например, если мы получили список друзей, тот же пользователь может быть снова выбран программным образом, что приведет к обновлению LiveData. С точки зрения пользовательского интерфейса, факт наличия запроса в полете — это просто еще одна точка данных, аналогичная любой другой части данных в самом объекте User.

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

  • Измените getUser (), чтобы он возвращал объект типа LiveData. Этот объект будет включать в себя статус работы сети. Для примера, смотрите реализацию NetworkBoundResource в проекте GitHub android-Architecture-components.
  • Предоставьте другую общедоступную функцию в классе UserRepository, которая может возвращать состояние обновления пользователя. Этот вариант лучше использовать, если вы хотите отображать состояние сети в вашем пользовательском интерфейсе только в том случае, если процесс извлечения данных возник из явного действия пользователя, такого как pull-to-refresh.

Протестируйте каждый компонент

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

В следующем списке показано, как протестировать каждый модуль кода из нашего расширенного примера:

  • Пользовательский интерфейс и взаимодействие: используйте инструментарий Android UI тест. Лучший способ создать этот тест — использовать библиотеку Espresso. Вы можете создать фрагмент и предоставить ему макет UserProfileViewModel. Поскольку фрагмент связывается только с UserProfileViewModel, мокирование (имитация) только этого класса достаточна для полного тестирования пользовательского интерфейса вашего приложения.
  • ViewModel: вы можете протестировать класс UserProfileViewModel с помощью теста JUnit. Вам нужно только смоделировать один класс, UserRepository.
  • UserRepository: вы также можете протестировать UserRepository с помощью теста JUnit. Вам нужно испытывать Webservice и UserDao. В этих тестах проверьте следующее поведение:
    • Хранилище делает правильные вызовы веб-службы.
    • Репозиторий сохраняет результаты в базе данных.
    • Хранилище не делает ненужных запросов, если данные кэшируются и обновляются.

  • Поскольку и Webservice, и UserDao являются интерфейсами, вы можете имитировать их или создавать поддельные реализации для более сложных тестовых случаев.
  • UserDao: тестируйте классы DAO с помощью инструментальных тестов. Поскольку эти инструментальные тесты не требуют каких-либо компонентов пользовательского интерфейса, они выполняются быстро. Для каждого теста создайте базу данных в памяти, чтобы убедиться, что у теста нет побочных эффектов, таких как изменение файлов базы данных на диске…

    Внимание: Room позволяет указать реализацию базы данных, поэтому можно протестировать DAO, предоставив реализацию JSQL для SupportSQLiteOpenHelper. Однако такой подход не рекомендуется, поскольку работающая на устройстве версия SQLite может отличаться от версии SQLite на компьютере разработчика.

  • Веб-сервис: в этих тестах избегайте сетевых вызовов на ваш сервер. Для всех тестов, особенно веб-, важно быть независимым от внешнего мира. Несколько библиотек, включая MockWebServer, могут помочь вам создать поддельный локальный сервер для этих тестов.
  • Тестирование артефактов: Компоненты архитектуры предоставляют артефакт maven для управления фоновыми потоками. Артефакт тестирования ядра androidx.arch.core: содержит следующие правила JUnit:
    • InstantTaskExecutorRule: Используйте это правило для мгновенного выполнения любой фоновой операции в вызывающем потоке.
    • CountingTaskExecutorRule: Используйте это правило для ожидания фоновых операций компонентов архитектуры. Вы также можете связать это правило с Espresso в качестве ресурса в режиме ожидания.

Лучшие практики

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

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

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

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

Создайте четкие границы ответственности между различными модулями вашего приложения.

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

Выставляйте как можно меньше от каждого модуля.

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

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

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

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

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

Сохраняйте как можно больше актуальных и свежих данных.

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

Назначьте один источник данных единственным источником истинны.

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

Дополнение: раскрытие статуса сети

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

В этом разделе показано, как отобразить состояние сети с помощью класса Resource, который инкапсулирует как данные, так и их состояние.

Следующий фрагмент кода предоставляет пример реализации Resource:

// Общий класс, который содержит данные и статус о загрузке этих данных.
sealed class Resource<T>(
   val data: T? = null,
   val message: String? = null
) {
   class Success<T>(data: T) : Resource<T>(data)
   class Loading<T>(data: T? = null) : Resource<T>(data)
   class Error<T>(message: String, data: T? = null) : Resource<T>(data, message)
}

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

На следующей диаграмме показано дерево решений для NetworkBoundResource:

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

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

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

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

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

В следующем фрагменте кода показан открытый API, предоставленный классом NetworkBoundResource для его подклассов:

// ResultType: Введите данные ресурса.
// RequestType: Введите ответ API.
abstract class NetworkBoundResource<ResultType, RequestType> {
   // Вызывается для сохранения результата ответа API в базу данных.
   @WorkerThread
   protected abstract fun saveCallResult(item: RequestType)

   // Вызывается с данными в базе данных, чтобы решить, следует ли извлекать
   // потенциально обновленные данные из сети.
   @MainThread
   protected abstract fun shouldFetch(data: ResultType?): Boolean

   // Вызывается для получения кэшированных данных из базы данных.
   @MainThread
   protected abstract fun loadFromDb(): LiveData<ResultType>

   // Вызывается для создания вызова API.
   @MainThread
   protected abstract fun createCall(): LiveData<ApiResponse<RequestType>>

   // Вызывается, когда получение не удается. Дочерний класс
   // может захотеть сбросить компоненты, такие как ограничитель скорости.
   protected open fun onFetchFailed() {}

   // Возвращает объект LiveData, представляющий ресурс,
   // реализованный в базовом классе.
   fun asLiveData(): LiveData<ResultType> = TODO()
}

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

  • Он определяет два параметра типа, ResultType и RequestType, поскольку тип данных, возвращаемый из API, может не соответствовать типу данных, используемому локально.
  • Он использует класс ApiResponse для сетевых запросов. ApiResponse — это простая оболочка для класса Retrofit2.Call, которая преобразует ответы в экземпляры LiveData.

Полная реализация класса NetworkBoundResource появляется как часть проекта GitHub android-Architecture-components.

После создания NetworkBoundResource мы можем использовать его для записи наших привязанных к диску и сети реализаций User в классе UserRepository:

// Информирует Dagger2, что этот класс должен быть создан только один раз.
@Singleton
class UserRepository @Inject constructor(
   private val webservice: Webservice,
   private val userDao: UserDao
) {
   fun getUser(userId: String): LiveData<User> {
       return object : NetworkBoundResource<User, User>() {
           override fun saveCallResult(item: User) {
               userDao.save(item)
           }

           override fun shouldFetch(data: User?): Boolean {
               return rateLimiter.canFetch(userId) && (data == null || !isFresh(data))
           }

           override fun loadFromDb(): LiveData<User> {
               return userDao.load(userId)
           }

           override fun createCall(): LiveData<ApiResponse<User>> {
               return webservice.getUser(userId)
           }
       }.asLiveData()
   }
}

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

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

Заключение

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

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

#Мнения

  • 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* на территории Российской Федерации по основаниям осуществления экстремистской деятельности».

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

Overview

When first building Android apps, many developers might start by relying on Model View Controller (MVC) patterns and usually end up writing most of the core business logic in activities or fragments. The challenge is that writing tests that can validate the app’s behavior is difficult to do because the code is often so closely tied to the Android framework and the various lifecycle events. While automated UI testing could be written to validate individual activities or fragments, maintaining and running them over the long-term is often difficult to sustain.

Architectural Patterns

There are currently three major approaches to architecting your Android apps:

  1. Standard Android (Model-View-Controller) — This is the «default» approach with layout files, Activities/Fragments acting as the controller and Models used for data and persistence. With this approach, Activities are in charge of processing the data and updating the views. Activities act like a controller in MVC but with some extra responsibilities that should be part of the view. The problem with this standard architecture is that Activities and Fragments can become quite large and very difficult to test. You can learn more on this blog post.

  2. Clean Architecture (Model View Presenter) — When using MVP, Activities and Fragments become part of the view layer and they delegate most of the work to presenter objects. Each Activity has a matching presenter that handles all access to the model. The presenters also notify the Activities when the data is ready to display. You can learn more in the sections below.

  3. Data-binding MVVM (Model-View-ViewModel) — ViewModels retrieve data from the model when requested from the view via the Android data binding framework. With this pattern, Activities and Fragments become very lightweight. Moreover, writing unit tests becomes easier because the ViewModels are decoupled from the view. You can learn more on this blog post.

Check this blog post from Realm for a detailed comparison of the options. Refer to this sample app for an example of each architecture type.

Clean Architecture

Clean architecture principles, as espoused by Robert Martin (also known as «Uncle Bob»), attempt to focus the developer on thinking through the app’s core functionality. It does so by separating the architecture of app into three major layers: how the app shows the data to the user (presentation layer), what are the core functions of the app (domain or use case layer), and how the data can be accessed (data layer). The presentation layer sits as the outermost layer, the domain layer sits in the middle layer, and the data layer resides in the inner layer.

It’s important to note that each layer has its own data model, and data can only be exchanged between layers and usually flows only in one direction (i.e. outer to inner, or inner to outer) Anytime data needs to be exchanged, usually a converter is used to map one layer’s model to another. In this way, a boundary of separation is created that helps limit changes in one layer to cause unintended side effects in other layers.

At the data layer, every source (i.e. file, network, memory) of data should be represented using the repository data pattern. There are many different ways of implementing this repository pattern, but the ultimate goal is to expose an interface that defines the different queries that need to be performed in order to abstract away the type of data source used. The underlying implementations can therefore be swapped regardless of the type of data source used.

Clean architecture introduces more abstractions and attempts to apply single responsibility principles in Android development. While there may be concerns about this approach adding more complexity, slow performance, and poor testability, it has been shown to work successfully in production apps (see this Droidcon talk or this Droidcon 2016 talk).

Model View Presenter

In Android app development, the traditional «Model / View / Controller pattern» is often being dropped in preference for the «Model / View / Presenter» pattern. The Model-View-Presenter (MVP) architecture comprises:

  • Model: the data layer
  • View: the UI layer, displays data received from Presenter, reacts to user input. On Android, we treat Activities, Fragments, and android.view.View as View from MVP.
  • Presenter: responds to actions performed on the UI layer, performs tasks on Model objects (using Use Cases), passes results of those tasks to Views.

What we want to achieve by using MVP are simpler tasks, smaller objects, and fewer dependencies between Model and Views layers. This, in turns makes our code easier to manage and test. Major differences from MVC include:

  • View is more separated from Model. The Presenter is the mediator between Model and View.
  • Easier to create unit tests
  • Generally there is a one to one mapping between View and Presenter, with the possibility to use multiple Presenters for complex Views

The easiest method for understanding this is multiple specific examples with sample code. Check out these useful resources for an in-depth look:

  • MVP Architecture in Android
  • Ray Wenderlich on MVP
  • MVP Explained with official sample code
  • MVP Tutorial with handy sample code
  • Avenging Android MVP Article
  • Introduction to MVP
  • Android MVP Basics with Sample
  • Android MVP Pattern, Part 1, 2, and 3.
  • Basic MVP Architecture
  • Brief MVP Introduction with sample code
  • Introduction to MVP Library Nucleus
  • How to Adopt MVP
  • MVP Guidelines
  • MVC vs MVP vs MVVM in Android
  • MVP Guidelines for Android
  • Android Architecture Components

Migrating to Clean Architecture

If you’re attempting to migrate towards clean architecture without necessarily rewriting your entire code base, your best approach is to first try to isolate and encapsulate your logic by moving it outside of your Activities or Fragments. Moving towards the Model-View-Presenter (MVP), which is a popular way of structuring your presentation layer, should probably be your first step. There is no exact way of implementing this approach, so here are a few recommended links:

  • https://github.com/Arello-Mobile/Moxy/
  • http://www.tinmegali.com/en/model-view-presenter-android-part-1/
  • https://medium.com/mobiwise-blog/android-basic-project-architecture-for-mvp-72f4b33252d0
  • http://antonioleiva.com/mvp-android/
  • http://thefinestartist.com/android/mvp-pattern
  • https://www.youtube.com/watch?v=BlkJzgjzL0c
  • https://github.com/antoniolg/androidmvp
  • https://speakerdeck.com/alphonzo79/better-android-architecture/

Templates

The following template projects are built to act as a starting point for this architecture:

  • https://github.com/ribot/android-boilerplate
  • https://github.com/dmilicic/Android-Clean-Boilerplate
  • https://github.com/android10/Android-CleanArchitecture
  • https://github.com/googlesamples/android-architecture

References

Clean architecture:

  • https://medium.com/@dmilicic/a-detailed-guide-on-developing-android-apps-using-the-clean-architecture-pattern-d38d71e94029
  • https://www.youtube.com/watch?v=BlkJzgjzL0c
  • http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/
  • http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/
  • https://github.com/dmilicic/android-clean-sample-app
  • https://speakerdeck.com/romainpiel/ingredients-for-a-healthy-codebase/
  • http://macoscope.com/blog/model-view-presenter-architecture-in-android-applications/
  • https://github.com/macoscope/RoomBookerMVP/tree/master/mvp/src/main/java/com/macoscope/
  • https://github.com/alphonzo79/AndroidArchitectureExample/

MVVM Pattern:

  • https://labs.ribot.co.uk/approaching-android-with-mvvm-8ceec02d5442

Other tutorial articles:

  • A Simple Android Apps with MVP, Dagger, RxJava, and Retrofit
  • MVP, Dagger 2, RxJava
  • Dagger 2 and MVP Architecture
  • Offline-First Reactive Android Apps — Repository Pattern + MVP + Dagger 2 + RxJava + ContentProvider
  • MVP + Dagger Tutorial
  • Dagger 2 and RxJava 1 Tutorial

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

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

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. Это простая в использовании платформа без кода, где вы можете легко создать мобильное приложение и веб-приложение для ваших требований с помощью простых шагов. Если у вас возникли вопросы, свяжитесь с нашей командой, чтобы они могли помочь вам ответить на них и направить вас к разработке ваших бизнес-приложений и лучшей архитектуре мобильных приложений, которая подходит для вас и ваших потребностей.

Понравилась статья? Поделить с друзьями:
  • Капли от геморроя эскузан инструкция по применению цена отзывы
  • Роватинекс цена в нижнем новгороде инструкция по применению
  • Кордиамин ампулы инструкция по применению цена для сердца
  • Нокиа 230 инструкция для пользователя на русском
  • Yamaha yz250f мануал на русском языке