Обзор “Clean Architecture” (Часть I: дизайн и архитектура, парадигмы программирования)
--
Прошло пару лет с момента моего первого прочтения книги Clean Architecture. Я решил ее перечитать и понял, что мне самому нужно саммари по ней, поэтому и получилась эта статья, в которой представлена первая часть основных концепций этой книги (вторая часть доступна здесь).
Книга состоит из 7 частей и 34 глав, которые разбиты по этим частям не слишком равномерно. Основной фокус, как ни странно, приходится на часть про архитектуру.
Дальше перейдем к содержимому книги
Introduction
Основная мысль вступления такова
When software is done right, it requires a fraction of the human resources to create and maintain. Changes are simple and rapid. Defects are few and far between. Effort is minimized, and functionality and flexibility are maximized.
What is Design and Architecture
Основная мысль главы в том, что разницы между архитектурой и дизайном нет.
One of the goals of this book is to cut through all that confusion and to define, once and for all, what design and architecture are. For starters, I’ll assert that there is no difference between them. None at all.
Любые системы включают как высокоуровневые структуры, так и низкоуровневые детали. Иногда первые связывают с архитектурой и архитектурными решениями, а вторые — с решениями уровня дизайна или проектирования.
По мнению автора книги любые реальные системы включают и то, и другое. Между ними сложно провести границы и поэтому нет смысла в делении архитектуры и дизайна.
Данное утверждение в вакууме может и имеет смысл, но в реальной системе такие границы провести можно, поэтому я вижу смысл в разделении решений на архитектурные и дизайн-решения.
Остальная часть главы посвящена case study, в котором рассматривается эволюция реальной компании. Компания растет вместе с продуктом, количество сотрудников увеличивается, а их совокупная производительность выходит на плато и не растет с ростом команды. Продуктивность релизов падает, стоимость фичей растет и наступает момент, когда топ-менеджерам требуется принять решение как жить дальше. Автор книги разбирает этот случай и заключает, что
The only way to go fast, is to go well
Ну и из разбора видно, что компания изначально нарушила этот концепт. Забавно, что автор подтягивает под это подход TDD (Test Driven Development), а не полноценную историю с shift-left testing и автоматизацией по пирамиде тестирования.
Ну и заканчивается глава тем, что автор анонсирует основную тему книги и чем она полезна при создании систем
To build a system with a design and an architecture that minimize effort and maximize productivity, you need to know which attributes of system architecture lead to that end.
That’s what this book is about. It describes what good clean architectures and designs look like, so that software developers can build systems that will have long profitable lifetimes.
A Tale of Two Values
Каждое программное обеспечение содержит два разных аспекта с точки зрения стейкхолдеров. Они представлены на рисунке ниже.
Часто эти аспекты развиваются несбалансированно и это приводит к проблемам. С точки зрения бизнеса важны функциональные требования, которые характеризуют то, как система работает. С точки зрения команды разработки важны структурные аспекты и архитектура, которые характеризуют сложность внесения изменений. Эта сложность важна, так как успешные продукты имеют тенденцию развиваться, расширять функциональность. В итоге, нам надо уметь правильно принимать решения, чтобы не было перекоса в сторону поведения приложений относительно его архитектуры.
Для принятия решений можно воспользоваться матрицей Эйзенхауэр, изображенной ниже. В ней архитектурные вопросы попадают в квадранты важных, а поведенческие — в квадранты срочных.
В первом квадранте находятся важные и срочные задачи как с точки зрения бизнеса, так и архитектуры. Но основная проблема, что бизнес менеджеры возносят срочные, но не важные задачи из третьего квадранта в первый — это приводит к тому, что важные, но не срочные задачи из второго квадранта откладываются.
Автор дает такую рекомендацию.
The dilemma for software developers is that business managers are not equipped to evaluate the importance of architecture. That’s what software developers were hired to do. Therefore it is the responsibility of the software development team to assert the importance of architecture over the urgency of features.
И напоследок в этой главе автор отмечает
If architecture comes last, then the system will become ever more costly to develop, and eventually change will become practically impossible for part or all of the system.
Дальше автор переходит ко второй части, а именно
Starting with the bricks: programming paradigms
Начинается эта часть с главы
Paradigm Overview
Существует три основные парадигмы программирования перечисленные в таблице ниже. Каждая из них накладывает определенные ограничения на то, как надо писать программы. Каждая из этих парадигм имеет отношение к архитектуре. Какое именно указано в последней строке этой таблицы.
Дальше автор последовательно разбирает каждую парадигму и начинает со
Structured Programming
Развитием этой парадигмы занимался Э́дсгер Ви́бе Де́йкстра, который доказал, что любую программу можно сконструировать из трех структур: sequence, selection (if/then/else), iteration (do/while).
В 1968 году Дейкстра провозгласил, что оператор безусловного перехода GoTo вреден и предложил использовать вместо него указанные выше конструкции. Это привело к бурным обсуждениям, но в итоге Эдсгер выиграл и goto в некоторых новых языках программирования даже отсутствовал.
Структурная парадигма программирования позволяет декомпозировать систему на функциональные модули (функции), которые можно представить с помощью указанных выше структур управления. Эта парадигма программирования привела к появлению SADT (structured analysis and design technique)
Предполагалось, что декомпозировать систему можно до настолько мелких функций, которые будут формально доказуемыми, а значит мы сможем доказать корректность работы всей системы…
Добиться этого полностью не получилось, но на выручку пришел научный подход, который применим к научным теориям
The nature of scientific theories and laws: They are falsifiable but not provable.
В разработке это относится к тестированию, о котором однажды Дейкстра сказал
Testing shows the presence, not the absence, of bugs
И все что дают нам тесты — это возможность убедиться, что программа достаточно корректна для наших целей. И написать тесты нормально можно только для потенциально верифицируемых (provable) программ, а программы активно использующие goto часто такими не являются.
Итого, автор подводит итоги главы
Structured programming forces us to recursively decompose a program into a set of small provable functions. We can then use tests to try to prove those small provable functions incorrect.
И переходит к следующей
Object-oriented programming
Автор начинает с обсуждения, а что же это такое. Сначала он рассматривает стандартные, но слишком общие или неточные ответы, навроде
- Комбинация данных и функций
- Метод моделирования реального мира
А дальше переходит к тому, что OOP — это микс из инкапсуляции, наследования и полиморфизма. И рассматривает в деталях эту гипотезу, приводя примеры инкапсуляции, наследования и полиморфизма на C. В итоге, оказывается, что OOP не привнесло эти концепции как некоторую новинку, но сделало их безопасными и удобными. Подробнее логика размышлений автора представлена на рисунке ниже.
С точки зрения архитектора самое главное в OOP — это полиморфизм, который обеспечивает инверсию зависимостей и независимую разработку и деплой.
А вот так автор подводит итог всей главе про объектно-ориентированное программирование.
What is OO? There are many opinions and many answers to this question. To the software architect, however, the answer is clear: OO is the ability, through the use of polymorphism, to gain absolute control over every source code dependency in the system. It allows the architect to create a plugin architecture, in which modules that contain high-level policies are independent of modules that contain low-level details. The low-level details are relegated to plugin modules that can be deployed and developed independently from the modules that contain high-level policies.
Functional Programming
Эта парадигма программирования основана на теории лямбда исчислений, которую в 30х годах 20 века изобрел Алонзо Чёрч для формализации и анализа понятия вычислимости. Основной особенностью функциональной парадигмы является то, что переменные здесь иммутабельные. Что это значит для архитектуры?
The answer is absurdly simple: All race conditions, deadlock conditions, and concurrent update problems are due to mutable variables. You cannot have a race condition or a concurrent update problem if no variable is ever updated. You cannot have deadlocks without mutable locks.
Соответственно это золотая жила для приложений, которые должны работать конкурентно. А в наше время, когда закон Мура перестал работать и процессоры начали расти вширь и увеличивать количество ядер, а не их частоту, таких приложений становится все больше.
Но возникает вопрос, а практична ли иммутабельность? Ответ да, если у вас бесконечный storage и скорость процессора, если нет, то возникают нюансы и компромиссы.
Один из них Segregation of Mutability, который выглядит так, что приложение состоит из компонент двух видов: мутабельных и иммутабельных
The immutable components perform their tasks in a purely functional way, without using any mutable variables. The immutable components communicate with one or more other components that are not purely functional, and allow for the state of variables to be mutated
Так как тут есть изменяемое состояние, то общей практикой является применение подхода с transactional memory, который защищает изменяемые переменные от конкурентных изменений и состояний гонки (race conditions).
Transactional memory simply treats variables in memory the same way a database treats records on disk. It protects those variables with a transaction- or retry-based scheme.
Одна из концепций, которая пришла из функциональной парадигмы — это Event Sourcing
Event sourcing is a strategy wherein we store the transactions, but not the state. When state is required, we simply apply all the transactions from the beginning of time.
Для того, чтобы эта схема работала мы можем делать дополнительные финты, например, вычислять состояние раз в какое-то время, например, еженочно и дальше для того, чтобы получить текущее состояние, то накатывать только те изменения, что прошли после прошлого расчета состояния.
В такой схеме у нас из CRUD (Create-Read-Update-Delete) есть только Create и Read, а изменений и удалений нет. И мы можем использовать функциональную парадигму в полный рост … если у нас хватит места для хранения и CPU для накатывания транзакций для расчета состояния.
Автор заканчивает вторую часть ремаркой, что software развивается не так быстро как принято считать и что со времен Алонзо Черча и Алана Тьюринга (1930-е годы) код по сути изменился не слишком сильно:) и что
Software — the stuff of computer programs — is composed of sequence, selection, iteration, and indirection. Nothing more. Nothing less.
В следующей части обзора книги Clean Architecture мы поговорим про принципы дизайна (SOLID) и принципы разделения на компоненты.