«Каждый программист должен создать свой архитектурный паттерн»
Народная мудрость.
Постановка проблемы
На сегодняшний день наиболее известны такие архитектурные паттерны как MVC, MVVM, MVP, Viper, Clean Code.
Все они в той или иной мере работают с тремя основными сущностями - Модель, Вью, Контроллер, добавляя время от времени некоторые дополнительные, например, Presenter.
Вторая общая особенность данных архитектурных паттернов состоит в том, что названные выше сущности выделяются и классифицируются исходя из их технических характеристик. Например, Вью - это то, что отображает данные на экране, Модель - содержит в себе данные и их обработку, а Контроллер осуществляет взаимодействие между ними.
Но эти характеристики не отражают сущности приложения в целом. Это как если бы мы разделили воду на водород и кислород и пытались бы из их особенностей понять сущность воды.
Фрагментарность используемых сущностей и отсутствие целостного видения приложения приводит к общеизвестным проблемам, связанным с трудностями понимания кода и его управлением.
Отсюда, ни один из этих паттернов не гарантирует, что на определённом этапе разработки приложения не возникнет ситуация, когда код станет тяжеловесным и очень сложным для управления.
Именно в такие моменты приходится переосмысливать общую архитектуру проекта и отвечать на вопросы “Зачем нужен тот или иной код, какую задачу он решает?”, “Где расположен код, реализующий ту или иную функциональность и как он работает?”. И т.д.
Продолжая пример с изучением воды следует сказать, что единицей её анализа является молекула воды. Это мельчайшая частица воды, которая тем не менее содержит в себе все её свойства.
В программе такой мельчайшей и одновременно целостной единицей является задача, которую решает тот или иной блок кода.
Отсюда, возникла идея использовать в качестве отправного пункта для организации кода именно те задачи, которые этот код решает.
При этом, задача понимается как бизнес-процесс.
Что такое бизнес-процесс
Термин “бизнес-процесс” пришёл из концепции реинжиниринга бизнес процессов. Наиболее известная и основополагающая книга по этой теме - Michael Hammer, James Champy. "Reengineering the Corporation: A Manifesto for Business Revolution".
Само слово "бизнес" в русском языке понимается в большей мере как коммерческая деятельность, связанная с зарабатыванием денег. В то же время в английском языке оно имеет более широкий смысл, включая, например, и такие смыслы как "дело", "занятие", "действие" и др.
Необходимо отметить, что понятие бизнес-процесса не чуждо программированию. Например, здесь используются такие термины как бизнес-логика, предметная логика, поток пользователя и др. Более того, в программном обеспечении класса ERP и CRM вся логика программ строится именно на основе бизнес-процессов предприятия.
Существует несколько, достаточно разных, определений бизнес-процесса.
В данном случае мы придерживаемся мнения о том, что бизнес-процесс - это определённое действие (активность, деятельность), реализующее задачу, связанную с работой приложения.
Это действие имеет линейный, последовательный характер, т.е., у него есть начало и окончание.
Бизнес-процесс всегда решает определённую задачу, у него есть цель и результат.
Бизнес-процессы могут взаимодействовать между собой.
Результат одного бизнес-процесса может быть использован другими бизнес-процессами.
Бизнес-процесс может включать в себя подчиненные суб-процессы.
Выделение бизнес-процесса, различение родительских и дочерних, определяется разработчиками проекта.
Пример бизнес-процессов
Например, предположим, что у нас есть приложение, на одном из экранов которого отображается список элементов.
Таким образом, мы имеем бизнес-процесс “Показать элементы”.
В свою очередь, этот бизнес-процесс включает в себя загрузку данных в оперативную память, создание макета для отображения данных, передачу данных в таблицу.
Далее, передача данных в таблицу может включать в себя настройку таблицы, конфигурацию динамического прототипа ячейки, конвертацию исходных значений в текстовый формат и др.
Все эти действия есть бизнес-процессы разных уровней декомпозиции.
Уровни бизнес-процессов
Полезно различать бизнес-процессы по трём уровням:
1-й уровень: Пользовательский
Это задачи, которые задаёт пользователь.
Например, посчитать количество калорий за день.
2-й уровень. Пользовательско-технический
Это первичное описание программы для решения пользовательских задач.
Например: показать на экране список приёмов пищи, а также количество калорий для каждого из них. И отобразить общее количество калорий за день.
3-й уровень. Технический
Это технические задачи, которые подробно описывают работу программы и которые пользователь не видит.
Например: Загрузить данные, сохранённые на диске, в оперативную память для отображения их в таблице.
Различение этих уровней помогает выделить те или иные бизнес-процессы по ходу разработки программы.
Исходя из вышесказанного, при структурировании кода первым шагом является определение базовых бизнес-процессов, а затем, путём декомпозиции, определение дочерних бизнес-процессов вплоть до отдельных функций.
Обозначение (называние) бизнес-процессов
Задачи, которые решают те или иные бизнес-процессы, указываются в названиях папок в навигаторе проекта.
Идеальный вариант, когда в названии бизнес-процесса используются 2 слова: действие и объект, на который оно направлено. Например, Display Foods.
Но в ряде случаев может использоваться и большее количество слов. Например, Display Editing Meal.
Названия файлов
В свою очередь, в названиях файлов указываются название объекта, который реализует (выполняет) задачу, затем тип объекта (класс, метод, свойство и др.) и его локация, т.е., где он находится: то ли является глобальным объектом, то ли является расширением какого-то объекта.
Один файл - одна задача
Основное правило при структурировании кода - помещать каждое действие, каждую отдельную задачу нижнего уровня в отдельный файл.
Для этого активно используются расширения.
При этом, файлы с расширениями группируются не по принадлежности к определённым объектам, таким как классы, структуры и т.д., а по принадлежности к определённым бизнес-процессам.
Отсюда, расширение одного класса может оказаться в группе файлов, принадлежащей бизнес-процессу другого класса.
Многозадачные файлы
В силу особенностей языка Swift и Xcode соблюдение данного правила - один файл одна задача - не всегда возможно.
Так например, свойства класса могут находится только в том файле, в котором объявлен класс. Таким образом получается, что один файл содержит код для решения нескольких задач.
В этом случае, папка, которая включает в себя файл с объявленным классом, использует слово manage, которое означает, что в данном файле выполняются несколько действий, относящихся к разным задачам и/или бизнес-процессам.
В свою очередь, один и тот же бизнес-процесс может реализовываться в нескольких файлах. Например, переход и передача значения в другой контроллер.
В этом случае файлы данного родительского бизнес-процесса группируются вместе, в одном блоке с названием этого бизнес-процесса. А папки, включающие отдельные файлы, называются по названиям задач, которые включены в данный родительский бизнес-процесс.
Пример реализации подхода Бизнес-процесс нотация
Рассмотрим образец реализации Бизнес-процесс нотации на примере приложения, которое отслеживает количество калорий.
Бизнес-процессы верхнего уровня
Самый верхний родительский бизнес-процесс - это задача всего приложения, т.е., отслеживание калорий.
Этот бизнес-процесс включает в себя бизнес-процессы второго уровня, которые показаны на Рис. 1
Здесь соблюдается алгоритм, показанный выше. А именно, что обозначение бизнес-процесса производится в названии папки.
И это обозначение включает в себя как правило действие и объект, которым это действие управляет.
В данном случае практически все бизнес-процессы обозначены с помощью слово manage. Это обобщающее слово, показывающее, что такой бизнес-процесс включает в себя несколько дочерних бизнес-процессов.
Базовые бизнес-процессы приложения
Наибольшую смысловую нагрузку в обработке контента данного приложения несут на себе бизнес-процессы Manage Foods и Manage Meals.
Рассмотрим внутреннюю архитектуру первого из них.
Бизнес-процесс Manage Foods: Папки и дочерние бизнес-процессы
Как видим, бизнес-процесс Manage Foods включает в себя 6 дочерних бизнес-процессов.
Первое слово в названии дочерних процессов это глагол, который обозначает действие. Второе и последующие слова в названиях процессов обозначают объекты, по отношению к которым выполняются данные действия.
-
Model Food - моделировать продукт, создать модель объекта Food.
-
Test Foods - тестировать продукты, т.е., в данном случае создать демо-данные для проверки. Разработчик решил их использовать для тестирования, а также оставить их как подсказку пользователям на первых этапах освоения приложения.
-
Load Foods - загрузить данные о продуктах в приложение.
-
Storage Foods - сохранить данные о продуктах в оперативной памяти приложения.
-
Show Foods in the Table - показать данные о продуктах на экране.
-
Edit Foods - редактировать данные о продуктах.
Бизнес-процесс Manage Foods: Файлы и объекты
Опустимся теперь на уровень ниже и посмотрим как оформляются файлы бизнес-процессов. Рассмотрим это на примере дочерних бизнес-процессов родительского бизнес-процесса Manage Foods.
1. Model Food
Первая папка Model Food обозначает код, который решает задачу создать модель объекта Food. И эта папка содержит один файл, который называется Food_Class_Global.swift.
Где:
-
Food — это название объекта.
-
Class — это тип объекта.
-
Global — это локация объекта. В данном случае Global означает, что этот объект находится в глобальной зоне видимости.
2. Test Foods
Следующая папка - Test Foods - включает в себя код, который решает задачу проверки пригодности формата данных о продуктах для использования их в программе. Эта папка также содержит один файл с именем demoFoods_Variable_Global.swift.
При этом demoFoods - это название объекта, Variable - это тип объекта и Global это локация объекта в глобальной зоне видимости. Т.е., по сути, это Stub data.
3. Load Foods
Следующая папка — Load Foods — обозначает бизнес‑процесс загрузки данные в оперативную память приложения. Она включает в себя 2 файла:
loadRealOrDemoFoodsData_Method_ExtFoods.swift
Этот файл содержит в себе метод loadRealOrDemoFoodsData и является расширением класса Foods.
Как уже было сказано ранее, желательно, чтобы один файл содержал в себе один метод или одно свойство.
Данный файл как раз реализует это правило.
Заодно рассмотрим и его внутреннюю структуру.
В верхней части файла обозначается задача, которую решает код в этом файле. Второй строкой комментария добавляются детали данной реализации. После чего идёт тело расширения и сам метод.
Таким образом, структура файла проста и понятна. Обозначена задача и код решает эту задачу.
Как известно, именно к такой, простой и понятной структуре файла следует стремиться.
viewDidLoad_Method_ExtFoods.swift
Второй файл в этой папке также является расширением класса Foods и содержит в себе, как видно из названия файла , стандартный метод viewDidLoad.
Здесь необходимо также отметить, что метод viewDidLoad - это один из тех случаев, когда уместить одну задачу в один файл не представляется возможным, поскольку при запуске приложения и срабатывании метода viewDidload, он может запускать внутри себя код для реализации нескольких разных задач.
4. Storage Foods: Бизнес-процессы разработки и бизнес-процессы выполнения программы
В этом месте можно обратить внимание ещё на одну особенность группировки кода по бизнес-процессам. Дело в том, что в дополнение к предыдущим критериям различения разных видов бизнес-процессов можно добавить и бизнес-процессы разработки приложения, в отличие от бизнес-процессов работы уже готового приложения.
Например, если мы говорим о загрузке данных с диска во временную память приложения, то при разработке приложения вначале необходимо объявить свойство для хранения данных, а затем выполнить процесс загрузки. В случае же анализа работы уже готового приложения, оно не знает изготовления, разработки, и по умолчанию подразумевает, что приложение уже создано. Отсюда, вначале реализуется процесс загрузки данных, а затем процесс их сохранения в соответствующем свойстве.
Именно этот момент и иллюстрирует Рис. 4, где папка Load Foods и соответствующий ей бизнес-процесс, предшествует папке Storage Foods. И файл с объявленным классом Foods появляется в навигационном дереве уже после используемых своих же расширений.
Такое деление в ряде случаев помогает выделить бизнес-процессы в общей архитектуре программы.
Это второй случай файла с многозадачностью. Т.е., файл с классом Foods используется в первую очередь для хранения данных в оперативной памяти. В нем размещаются все свойства класса.
5. Show Foods in the Table
Следующий бизнес-процесс - показ данных на экране, в данном случае в таблице: Show Foods in the Table. Он содержит один файл, названный TableDataSource_Methods_ExtFoods.
Как видим, в названии этого файла используется слово не Method, а Methods, что означает, что в этот файл включены несколько методов.
Но в отличие от предыдущего файла с классом Foods, внутри которого может находиться несколько блоков кода для решения разных задач, внутри этого файла находится несколько методов, которые решают одну и ту же задачу - передача данных из оперативной памяти в таблицу, т.е., в видимую для пользователя часть приложения. Это типовые методы numberOfSections(in:), tableView(:numberOfRowsInSection:) и tableView(:cellForRowAt:).
Также отметим, что здесь есть и методы динамического построения интерфейса по ходу выполнения программы (количество секций, строк, конфигурирование ячейки), так и методы собственно выполнения программы(передача данных в ячейку).
Компоновка всех этих трёх методов в отдельное расширение класса Foods позволяет разгрузить базовый файл класса и сгруппировать код по принципу решения определённой бизнес-задачи.
Еще один момент, связанный с группировкой кода состоит в том, что в этом случае в названии файла невозможно обозначить один объект, поэтому даётся обобщающее название, понятное разработчику - TableDataSource_Methods.
Режимы показа данных
В этом же бизнес-процессе - Show Foods in the Table - есть два своих дочерних процесса: Sort Foods и Search and Filter Foods.
Эти дочерние бизнес-процессы реализуют ту же задачу показа данных, но в разных режимах: либо вывести на экран данные в определённом порядке, либо выбрать для показа только часть данных.
Каждая из этих задач реализуется в своём отдельном файле, которые являются расширениями базового класса Foods.
В целом, следует отметить, что активное использование расширений базового класса, в том числе вынос в них стандартных методов, таких как viewDidLoad и TableViewData Source методов, позволяет освободить, разгрузить базовый файл класса и таким образом нивелировать проблему Massive View Controller.
6. Edit Foods
Рассмотрим теперь дочерние процессы бизнес-процесса Edit Foods. Они показаны на Рис. 5.
Бизнес-процесс Edit Foods включает в себя следующие дочерние бизнес-процессы:
-
Delete Food
-
Pass Food to Edit
-
Load Editing Food
-
Storage Editing Food
-
Prevent Input Error
-
Pass Edited Food Back to Foods
-
Get Edited Food in Foods
-
Persist Edited Foods
Как видно из этого перечня бизнес-процессов, здесь выстраивается определённая последовательность действий по реализации задачи Edit Foods. Например, элемент передаётся для редактирования, получается, сохраняется, изменяется, возвращается обратно, получается и изменения сохраняются на диск.
Каждая задача в этом блоке реализуется в своём отдельном файле, что можно увидеть на Рис. 6.
Важно отметить, что здесь мы видим не только последовательность задач, но и названия знакомых методов, которые реализуют эти задачи.
Например, prepareForSegue, viewDidLoad, unwindSegue и др. Это позволяет легче ориентироваться в коде.
И теперь, на примере этих бизнес-процессов, необходимо сделать очень важное замечание.
Что такое Модель?
Что, в упомянутых в начале этой статьи архитектурных паттернах, понимается под Моделью?
В одном случае под Моделью понимается структура данных, модель объекта. Например, структура или класс могут включать в себя ряд свойств и методов.
Во втором, более неявном случае, под моделью понимается всё, что не относится к интерфейсу, к визуальной составляющей приложения.
И вот сейчас, на основе представленных выше совокупности и последовательности бизнес-процессов можно и следует говорить о Модели приложения в целом.
И действительно, глядя на Рис. 5 и на Рис. 6, мы видим целостную картину приложения, как приложение работает, какие составные части оно включает и в какой последовательности выполняются эти бизнес-процессы.
Это и есть целостная модель приложения, его внутренняя архитектура.
Примечание
Создание программ часто сравнивают с постройкой дома. С этой точки зрения, традиционная группировка кода по техническим характеристикам (view, model, service и т.д.) похожа на ситуацию, когда дом рассматривается просто как набор строительных материалов, например, кирпичи, оконные рамы, трубы для водопровода и т.д. Отсюда, во многом теряется видение дома как целостного объекта с его интенцией. То же происходит и с программами, что потом проявляется в трудностях, связанных с целостным пониманием кода и с его управлением.
Типовые бизнес-процессы. Типовая архитектура
Рассмотрим ещё одну интересную особенность, выявленную в ходе структуризации кода на основе бизнес-процессов приложения.
На Рис. 7 показаны дочерние процессы двух базовых бизнес-процессов приложения: Manage Foods и Manage Meals.
Как видим они полностью совпадают и включают в себя по 6 дочерних процессов:
-
Model
-
Test
-
Load
-
Storage
-
Show
-
Edit
Такое совпадение наводит на мысль о том, что на определённом уровне анализа бизнес-процессов они могут быть одинаковыми для разных объектов приложения.
Другими словами, речь может идти о типовой архитектуре бизнес-процессов.
Заключение
Самый большой плюс от использования Бизнес-процесс нотации состоит в том, что с её помощью можно увидеть целостную модель всего приложения.
Т.е., увидеть набор задач, увидеть их последовательность как звенья, этапы определённого бизнес-процесса, а также видеть методы, свойства, которые реализуют те или иные задачи и их локацию.
Немаловажным также является возможность выполнять декомпозицию и композицию приложения в режиме реального времени, открывая в навигаторе проекта группу папок или скрывая их.
Все это значительно упрощает понимание структуры кода приложения. При такой компоновке гораздо легче найти код, который относится к определённой задаче, и с другой стороны, гораздо быстрее понять какую задачу решает соответствующий блок кода.
Все это существенным образом повышает возможность понимания кода и возможность качественного управления им.
P.S.
Данная статья представляет собой обобщение опыта автора по разработке мобильного приложения на основе подхода, в основу которого положен анализ программы как системы бизнес-процессов.
Очевидным преимуществом такого подхода является возможность построения целостной модели приложения.
Автор конечно хотел бы услышать мнение уважаемой аудитории по поводу жизнеспособности данного подхода.
Автор: AppCrafter