Привет, Хабр. Меня зовут Сергей Синяков, тимлид команды спецификации в разработке КОМПАС-3D, занимаюсь вопросами управления данными об изделии и формированием документа Спецификации. И сегодня мы поговорим про API.
Любой программный продукт не существует в вакууме и предназначен для взаимодействия с пользователем через пользовательский интерфейс (UI) и/или с внешним приложением через API. Учитывать будущий интерфейс приложения чрезвычайно важно еще на этапе проектирования, т.к. требования к UI и API могут накладывать существенные ограничения на архитектуру продукта. Не зря классическая пользовательская история, поступающая в разработку, звучит так: "Я как пользователь, выполнив определенные действия с UI, ожидаю получить соответствующий результат". Если же спроецировать пользовательские истории на API, получим сценарий для автотестов и разработку через тестирование - TDD, когда перед началом разработки структуры данных, функции API и их ожидаемое поведение фиксируются в тестах, а разработка фокусируется на обеспечении их корректного прохождения.
Антипаттерн разработки API для UI-приложений
Если приложение преимущественно используется пользователем через графический интерфейс, можно столкнуться с ситуацией, когда API уделяется недостаточно внимания при проектировании приложения, и функции для взаимодействия с другими приложениями реализуются уже после реализации фичи в UI. Это объяснимо с точки зрения ускорения разработки: зачем тратить время на проработку и реализацию “второстепенного” интерфейса - реализуем потом, а сначала выкатим UI-версию. Но в этом случае подобные API-функции формируют отдельный слой, постепенно обрастая своими структурами данных, алгоритмами, превращаясь в полноценный компонент. А отдельный компонент требует отдельную команду для своего развития, что еще больше усугубляет ситуацию, отдаляя реализацию API от фич, для которых он пишется.
В оптимальном варианте API для фич необходимо проектировать вместе с UI, а UI-реализацию строить на API. Оптимальная в этом контексте архитектура построения API монолитного приложения представлена на рисунке.
Многокомпонентная архитектура
По мере своего роста любой монолит неизбежно сталкивается с проблемой роста стоимости разработки новых фич. Отсутствие четких границ между компонентами, сильная связанность кода требуют все возрастающих усилий для встраивания новой функциональности в существующую модель. Каждая новая фича начинает требовать все больше программистов, тестировщиков и времени на свою реализацию, а что самое главное – возрастают требования к квалификации специалистов, знающих все нюансы монолитной модели.
Решением этой проблемы может стать разбиение монолитной модели на отдельные достаточно независимые друг от друга компоненты, решающие каждый свой круг задач и обладающие своим API. Это позволяет не только “удешевить” разработку новых фич, но и, открыв API компонентов для внешних разработчиков, создать своего рода “платформу” для реализации новых фич и даже систем, привлекая к разработке широкий круг программистов, которым достаточно изучить API платформы.
Ограниченный контекст
Самой сложной задачей в этом случае является, пожалуй, поиск тех самых границ, по которым нужно делить наш монолит на части. Я предпочитаю придерживаться подхода ограниченных контекстов, который описал Эрик Эванс в книге "Предметно-ориентированное проектирование (DDD). Структуризация сложных программных систем". Разбиение системы на компоненты следует начинать с разбиения множества решаемых задач на подмножества. Такое разбиение чаще всего определяется естественным образом исходя из экспертного анализа предметной области, например для САПР КОМПАС-3D можно выделить подобласти: 2D-моделирование, 3D-моделирование, Спецификация, Текстовый редактор. Такие подобласти уже достаточно независимы друг от друга и обладают некоторой степенью автономности. Ограниченный контекст формируется при моделировании решения задач подобласти, должен иметь четкие границы и для увеличения степени автономности и изоляции использует уникальный язык. Термины уникального языка доступны только внутри контекста. В случае пересечения терминов между контекстами, в каждом контексте под них заводятся свои сущности, реализуемые в виде своих структур данных. Контексты c входящими в них сущностями, которые можно выделить в САПР КОМПАС-3D, представлены на рисунке:
Обратите внимание, что сущность “Исполнение” встречается в двух контекстах: 3D (3D-моделирование) и PD (Product Data - данные об изделии). Если говорить про общую единую модель всей предметной области, в ней будет только одна соответствующая сущность, представленная структурой данных с полным набором всех полей, позволяющих максимально полно описать любой объект этого типа. При разбиении модели на ограниченные контексты в каждом контексте важны свои аспекты используемой сущности, и структуры данных для “Исполнения” в PD и 3D имеют свой набор полей. При необходимости передачи сущностей из одного контекста в другой, структуры данных могут транслируются друг в друга.
Боязнь дублирования сущностей
Выделение ограниченных контекстов со своим уникальным языком зачастую противоречит желанию разработчиков переиспользовать уже существующие сущности, которые были заведены для решения других задач, но имеют схожую семантику. Чаще всего подобное повторное использование сущностей из других контекстов приводит к добавлению новых полей в соответствующие структуры данных и, в конечном итоге, ведет к чрезмерному разрастанию классов и увеличению связности кода.
Данные об изделии как ограниченный контекст в САПР КОМПАС-3D
Выделение ограниченных контекстов позволяет получить максимально независимые автономные компоненты, обладающие своим API и подходящие для повторного использования. Например, в рамках работ по выделению компонента управления данными об изделии (PD) были выделены свои сущности: Изделие, Исполнение, Составная часть изделия, Свойства составных частей изделия и т.д. Для этих сущностей реализована своя логика и алгоритмы в рамках ограниченного контекста: например, реализована поддержка выражений для Свойств составных частей изделия, содержащих ссылки на Свойства других Составных частей изделия. Компонент данных об изделии (PD) может использоваться как в 3D, так и в 2D, и в Спецификации, при этом в сущность Составная часть изделия из контекста Данных об изделии транслируются сущности “Вставка компонента”, “Тело” из 3D, “Макро”, “Вставка фрагмента”, “Вставка вида” из 2D, “Объект спецификации” из Спецификации. При этом данные об изделии могут свободно передаваться между документами, с сохранением работоспособности всех алгоритмов, реализованных в рамках контекста. До выделения контекста данных об изделии(PD), для получения структуры изделия в Спецификации приходилось обходить все возможные источники Составных частей изделия: вставки компонентов, тела, макро, вставки фрагментов, вставки видов из подключенных документов, достаточно глубоко погружаясь в нюансы реализации 2D и 3D моделей, а ссылки между свойствами вставок в 3D и между макро в 2D потребовали бы своей реализации.
Закон Конвея
Еще одним инструментом формирования многокомпонентной архитектуры для открытой платформы является коммуникационная структура организации. Закон Конвея гласит: «Любая организация, которая разрабатывает систему, вынуждена создавать проекты, структуры которых являются копией структуры связей организации».
Смысл закона достаточно прост: сильные коммуникационные связи внутри команды определяют высокую связность кода, который пишет эта команда, а гораздо более слабые межкомандные коммуникационные связи обеспечивают слабое зацепление с кодом других команд. Таким образом код, написанный внутри одной команды, обладает свойствами хорошо спроектированного компонента.
В соответствии с законом Конвея один ограниченный контекст должен разрабатываться одной командой, что позволит естественным образом поддерживать его автономность и изолированность. Напротив, деление одного ограниченного контекста между двумя командами может привести к разбиению контекста и потребует дополнительных усилий для налаживания более тесных коммуникаций между командами.
Резюме
API требует пристального внимания разработчиков начиная с этапа проектирования системы. Формирование многокомпонентной архитектуры на основе разбиения модели на ограниченные контексты позволяет сформировать прозрачную расширяемую архитектуру приложения, многократно повысив эффективность разработки. Открытый API компонентов позволит максимально расширить круг решаемых задач и, в конечном итоге, число пользователей системы.