Разработка приложений и написание кода есть процесс достаточно увлекательный и результатом такой интересной разработки может стать беспорядочный код, представляющий собой спагетти-архитектуру, в которой сложно разобраться, и которая нуждается в некоторой структуризации.
Что такое "Clean Architecture"?
Архитектура означает общий дизайн проекта. Это организация кода по классам, файлам, компонентам или модулям, и то, как все эти группы кода соотносятся друг с другом. Архитектура определяет, где приложение выполняет свою основную функциональность и как эта функциональность взаимодействует с другими компонентами, такими как базы данных и пользовательским интерфейсом.
В этом контексте чистая архитектура (clean architecture) подразумевает организацию проекта таким образом, чтобы в нем можно было легко разобраться, и чтобы его можно было легко изменять по мере роста проекта.
Основные характеристики чистой архитектуры
При разработке большого проекта нам необходимо, чтобы его можно было легко поддерживать, при необходимости изменяя отдельные компоненты. Для этого нам необходимо разделять файлы и классы на компоненты, которые могут изменяться независимо друг от друга. На картинке ниже представлены несколько взаимосвязанных “компонентов”.
Что нужно сделать, если вы хотите заменить ножницы на нож, как показано на рисунке выше? Вам нужно развязать шнурки, которые идут к ручке, бутылочке с чернилами, скотчу и компасу. Затем вам нужно снова привязать эти предметы к ножу. Возможно, с ножом это сработает, но что, если при этом ручке и скотчу для работы нужны именно ножницы и без них они не будут работать
Рассмотрим другой вариант.
Теперь, в случае, если нам нужно заменить ножницы, мы можем всего лишь вытащить шнурок от ножниц из-под стикеров и добавить новый шнурок, привязанный к ножу. Это намного проще. Стикерам все равно, потому что к ним даже не был привязан шнурок.
Из представленных иллюстраций можно сделать выводы о том, что архитектуру, представленную на втором изображении, гораздо проще изменить. Поскольку не нужно часто менять примечания к приложению, эту систему будет очень легко поддерживать. Эта же концепция является архитектурой, которая упростит обслуживание и изменение вашего программного обеспечения.
Внутренний круг - это уровень предметной области вашего приложения. Здесь вы размещаете бизнес-правила. Под "бизнесом" мы необязательно подразумеваем компанию. Это просто означает суть того, что делает ваше приложение, основную функциональность кода. Приложение для перевода выполняет перевод. В интернет-магазине есть товары для продажи. Эти бизнес-правила, довольно стабильны, поскольку вы вряд ли будете часто менять суть того, что делает ваше приложение.
Внешний круг - это инфраструктура. Он включает в себя такие элементы, как пользовательский интерфейс, база данных, веб-API и фреймворки. Эти элементы подвержены изменениям с большей вероятностью, чем домен. Например, у вас больше шансов изменить внешний вид кнопки пользовательского интерфейса, чем способ расчета кредита.
Граница между доменом и инфраструктурой устанавливается таким образом, что домен ничего не знает об инфраструктуре. Это означает, что пользовательский интерфейс и база данных зависят от бизнес-правил, но эти правила не зависят от пользовательского интерфейса или базы данных. Это делает его архитектурой подключаемого модуля. Не имеет значения, является ли пользовательский интерфейс веб-интерфейсом, настольным приложением или мобильным приложением. Также не имеет значения, хранятся ли данные с использованием SQL, NoSQL или в облаке. Домену это все равно. Это упрощает изменение инфраструктуры.
Поговорим о терминологии
На изображении выше мы представили Clean architecture в виде двух кругов. Однако, для лучшего понимания принципов чистой архитектуры лучше выполнить небольшую декомпозицию.
Здесь мы подразделяем уровень домена на сущности и различные варианты использования, а уровень адаптера образует границу между доменом и уровнем инфраструктуры. Так как эти термины могут немного сбивать с толку, давайте рассмотрим их по отдельности.
Сущности и варианты использования
Сущность (entity) - это набор связанных бизнес-правил, которые имеют решающее значение для функционирования приложения. В объектно-ориентированном языке программирования правила для сущности были бы сгруппированы как методы в классе. Даже если бы не было приложения, эти правила все равно существовали бы. Например, взимание процентов по кредиту в размере 10% - это правило, которое может быть введено банком. Это было бы справедливо независимо от того, рассчитывались ли проценты на бумаге или с помощью компьютера.
При этом, важно понимать, что сущности ничего не знают о других слоях и они не от чего не зависят. То есть, они не используют имена каких-либо других классов или компонентов, которые находятся во внешних слоях.
Варианты использования (use cases) - это бизнес-правила для конкретного приложения. Они рассказывают, как автоматизировать систему, и с их помощью определяется поведение приложения. Они взаимодействуют с объектами и зависят от них, но они ничего не знают о других уровнях. Им все равно, веб-страница это или приложение для iPhone, и также, им все равно, хранятся ли данные в облаке или в локальной базе данных SQLite. Этот уровень определяет интерфейсы или содержит абстрактные классы, которые могут использовать внешние уровни.
Адаптеры и инфраструктура
Адаптеры, также называемые интерфейсными адаптерами, являются трансляторами между доменом и инфраструктурой. Например, они берут входные данные из графического интерфейса пользователя и переупаковывают их в форме, удобной для вариантов использования и сущностей. Затем они берут выходные данные и переупаковывают их в форму, удобную для отображения в графическом интерфейсе или сохранения в базе данных.
На этом уровне находятся все компоненты ввода-вывода: пользовательский интерфейс, база данных, фреймворки, устройства и т.д. Это самый нестабильный уровень. Поскольку на этом уровне очень часто происходят изменения, они находятся как можно дальше от более стабильных уровней домена. Поскольку они хранятся отдельно, относительно легко вносить изменения или заменять один компонент другим.
Принципы внедрения чистой архитектуры
В качестве основы внедрения чистой архитектуры мы рассмотрим набор принципов SOLID. Они являются принципами на уровне класса, но имеют схожие аналоги, применимые к компонентам (группам связанных классов).
Первая буква S – это принцип единой ответственности (SRP). В SRP говорится, что класс должен выполнять только одну задачу. У него может быть несколько методов, но все они работают вместе, чтобы выполнять одну основную задачу. У класса должна быть только одно основание для изменений. Например, если у финансового отдела есть одно требование, которое изменит класс, а у отдела кадров есть другое требование, которое изменит класс по-другому, то есть две причины для изменения. Класс должен быть разделен на два отдельных класса, у каждого из которых есть только одна причина для изменения.
Буква O - принцип "Открыто-закрыто" (OCP). "Открыто" означает "открыто для расширения". "Закрыто" означает "закрыто для модификации". Таким образом, вы должны иметь возможность добавлять функциональность к классу или компоненту, но вам не нужно изменять существующую функциональность. Нужно убедиться, что у каждого класса или компонента есть только одна задача, а затем скрыть более стабильные классы за интерфейсами, чтобы они не пострадали, когда придется менять менее стабильные классы.
Принцип замещения Лискова (LSP). Это буква L в слове SOLID. Этот принцип означает, что классы или компоненты более низкого уровня могут быть заменены, не влияя на поведение классов и компонентов более высокого уровня. Это можно сделать, реализовав абстрактные классы или интерфейсы. Например, в Java ArrayList и связанный список реализуют интерфейс List, поэтому их можно заменять друг на друга.
Принцип разделения интерфейсов (ISP) – буква I. ISP использует интерфейс для отделения класса от других классов, которые его используют. Интерфейс предоставляет только то подмножество методов, которое необходимо зависимому классу. Таким образом, изменения в других методах не влияют на зависимый класс.
И наконец, буква D - принцип инверсии зависимостей (DIP). Этот принцип означает, что менее стабильные классы и компоненты должны зависеть от более стабильных, а не наоборот. Если стабильный класс зависит от нестабильного класса, то каждый раз, когда меняется нестабильный класс, это также влияет на стабильный класс. Таким образом, направление зависимости должно быть изменено. Мы можем использовать абстрактный класс или скрыть стабильный класс за интерфейсом.
Таким образом, вместо стабильного класса используйте класс Volatile, подобное этому:
class StableClass {
void myMethod(VolatileClass param) {
param.doSomething();
}
}
Вы могли бы создать интерфейс, который реализует класс Volatile:
class StableClass {
interface StableClassInterface {
void doSomething();
}
void myMethod(StableClassInterface param) {
param.doSomething();
}
}
class VolatileClass implements StableClass.StableClassInterface {
@Override
public void doSomething() {
}
}
Это меняет направление зависимости на противоположное. Класс Volatile знает название стабильного класса, но стабильный класс ничего не знает о классе Volatile.
Стоит отметить, что это не единственный способ решения проблемы направления зависимости, но в рамках данной статьи другие методы мы не рассматриваем.
Заключение
В этой статье мы рассмотрели основы так называемой чистой архитектуры и принципы ее использования при разработке. С помощью чистой архитектуры можно сделать архитектуру проекта гораздо более понятной для понимания и разработки.
В завершение напоминаю об открытых уроках, которые пройдут в ближайшее время в рамках курса "Enterprise Architect":
15 августа: Роль корпоративного архитектора в продуктовой трансформации бизнеса. Записаться
20 августа: Бизнес-архитектура: ключевые объекты. Записаться