Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Подсказка
Это фрагмент из книги, архитектор современных веб-приложений с ASP.NET Core и Azure, доступный в .NET Docs или в виде бесплатного скачиваемого PDF-файла, который можно прочитать в автономном режиме.
"Если бы строители строили здания так же, как программисты пишут программы, то первый дятел, который попадётся, разрушил бы цивилизацию."
- Джеральд Вайнберг
Необходимо спроектировать и разработать программные решения с учетом легкости поддержки. Принципы, описанные в этом разделе, помогут вам в принятии архитектурных решений, которые будут приводить к чистым, обслуживаемым приложениям. Как правило, эти принципы помогут вам создавать приложения из дискретных компонентов, которые не тесно связаны с другими частями приложения, а скорее взаимодействуют через явные интерфейсы или системы обмена сообщениями.
Общие принципы проектирования
Разделение проблем
Руководящий принцип при разработке — разделение проблем. Этот принцип утверждает, что программное обеспечение должно быть разделено на основе типов выполняемой работы. Например, рассмотрим приложение, включающее логику для выявления заметных элементов для отображения пользователю, и форматирует такие элементы определенным образом, чтобы сделать их более заметными. Поведение, ответственное за определение, какие элементы форматировать, должно быть отдельно от поведения, ответственного за форматирование элементов, так как эти действия являются отдельными задачами, которые только случайно связаны друг с другом.
По архитектуре приложения можно логически создавать, чтобы следовать этому принципу, разделяя основное бизнес-поведение от инфраструктуры и логики пользовательского интерфейса. В идеале бизнес-правила и логика должны находиться в отдельном проекте, который не должен зависеть от других проектов в приложении. Это разделение помогает гарантировать, что бизнес-модель легко протестировать и может развиваться без тесной связи с низкоуровневой информацией о реализации (она также помогает, если проблемы инфраструктуры зависят от абстракций, определенных в бизнес-уровне). Разделение проблем является ключевым аспектом использования слоев в архитектуре приложений.
Инкапсуляция
Различные части приложения должны использовать инкапсуляцию , чтобы изолировать их от других частей приложения. Компоненты и слои приложений должны быть в состоянии настроить внутреннюю реализацию, не нарушая их совместную работу до тех пор, пока внешние контракты не нарушаются. Правильное использование инкапсуляции помогает добиться свободного взаимодействия и модульности в конструкциях приложений, так как объекты и пакеты можно заменить альтернативными реализациями до тех пор, пока тот же интерфейс поддерживается.
В классах инкапсуляция достигается путем ограничения внешнего доступа к внутреннему состоянию класса. Если внешний субъект хочет управлять состоянием объекта, он должен сделать это с помощью четко определенной функции (или метода задания свойств), а не иметь прямой доступ к частному состоянию объекта. Аналогичным образом, компоненты приложений и приложения сами должны предоставлять четко определенные интерфейсы для использования их сотрудниками, а не разрешать их состояние изменять напрямую. Этот подход позволяет внутренней структуре приложения развиваться с течением времени, не беспокоясь о том, что это нарушит взаимодействие с другими компонентами, при условии сохранения публичных контрактов.
Изменяемое глобальное состояние антиетично инкапсуляции. Значение, полученное из изменяемого глобального состояния в одной функции, нельзя полагаться на то же значение в другой функции (или еще дальше в той же функции). Понимание проблем с изменяемым глобальным состоянием является одной из причин, по которым языки программирования, такие как C#, поддерживают различные правила области, которые используются везде от операторов до методов к классам. Стоит отметить, что управляемые данными архитектуры, которые полагаются на центральную базу данных для интеграции в приложениях и между ними, осознанно зависят от изменяемого глобального состояния, которое представляет база данных. Ключевое внимание в проектировании, управляемом доменом, и чистой архитектуре заключается в том, как инкапсулировать доступ к данным и как убедиться, что состояние приложения не становится недействительным из-за прямого доступа к его формату хранения данных.
Инверсия зависимостей
Направление зависимости в приложении должно находиться в направлении абстракции, а не сведений о реализации. Большинство приложений разрабатываются таким образом, чтобы зависимости времени компиляции направлялись в сторону выполнения во время работы, создавая прямой граф зависимостей. То есть, если класс A вызывает метод класса B и класса B вызывает метод класса C, то во время компиляции класс A будет зависеть от класса B, а класс B будет зависеть от класса C, как показано на рис. 4-1.
Рис. 4-1. Граф прямых зависимостей.
Применение принципа инверсии зависимостей позволяет A вызывать методы на абстракции, реализованной B, что делает возможным вызовы A к B во время выполнения, но B зависит от интерфейса, контролируемого A во время компиляции (таким образом, инвертируя типичную зависимость во время компиляции). Во время выполнения поток выполнения программы остается неизменным, но введение интерфейсов означает, что различные реализации этих интерфейсов можно легко подключить.
Рис. 4-2. Инвертированные графы зависимостей.
Инверсия зависимостей является ключевой частью создания слабо связанных приложений, так как сведения о реализации можно записать в зависимости от и реализовать абстракции более высокого уровня, а не наоборот. Полученные приложения являются более тестируемыми, модульными и обслуживаемыми в результате. Практика внедрения зависимостей возможна, следуя принципу инверсии зависимостей.
Явные зависимости
Методы и классы должны явно требовать любые объекты совместной работы, необходимые для правильной работы. Он называется принципом явных зависимостей. Конструкторы классов предоставляют возможность для классов определить необходимые им вещи, чтобы находиться в допустимом состоянии и правильно функционировать. Если вы определяете классы, которые можно создать и вызвать, но они будут работать правильно только если имеются определенные глобальные или инфраструктурные компоненты, эти классы обманывают своих клиентов. Контракт конструктора сообщает клиенту, что он нуждается только в указанных вещах (возможно, ничего, если класс просто использует конструктор без параметров), но тогда во время выполнения объект действительно нуждается в чем-то другом.
Следуя принципу явных зависимостей, ваши классы и методы честно сообщают клиентам то, что необходимо для их функционирования. Следуя принципу, ваш код становится более самодокументирующимся, а контракты вашего кода — более удобочитаемыми, так как пользователи начнут доверять, что, пока они предоставляют необходимые параметры метода или конструктора, объекты, с которыми они работают, будут вести себя правильно во время исполнения.
Единая ответственность
Единый принцип ответственности применяется к объектно-ориентированному проектированию, но также может рассматриваться как архитектурный принцип, аналогичный разделению проблем. В нем говорится, что объекты должны иметь только одну ответственность и что они должны иметь только одну причину изменения. В частности, объект должен измениться только в одной ситуации: если необходимо обновить способ выполнения его единственной задачи. Следуя этому принципу, можно создавать более слабо связанные и модульные системы, так как многие виды нового поведения могут быть реализованы как новые классы, а не путем добавления дополнительной ответственности к существующим классам. Добавление новых классов всегда безопаснее, чем изменение существующих классов, так как код пока не зависит от новых классов.
В монолитном приложении мы можем применить единый принцип ответственности на высоком уровне к уровням в приложении. Ответственность за презентацию должна оставаться в проекте пользовательского интерфейса, а ответственность за доступ к данным должна храниться в рамках проекта инфраструктуры. Бизнес-логика должна храниться в основном проекте приложения, где его можно легко тестировать и развиваться независимо от других обязанностей.
Когда этот принцип применяется к архитектуре приложения и доведен до логической конечной точки, результатом являются микрослужбы. У данной микрослужбы должна быть одна ответственность. Если необходимо расширить поведение системы, это обычно лучше сделать, добавив дополнительные микрослужбы, а не добавив ответственность к существующему.
Дополнительные сведения об архитектуре микрослужб
Принцип "Не повторяйся"
Приложение должно избегать указания поведения, связанного с определенной концепцией в нескольких местах, так как эта практика является частым источником ошибок. В какой-то момент изменение требований потребует изменения этого поведения. Скорее всего, по крайней мере одна из функций не обновится, и система будет вести себя несогласованно.
Вместо того чтобы дублировать логику, инкапсулировать ее в конструкции программирования. Создайте конструкцию, являющуюся единым центром управления этим поведением, и убедитесь, что любая другая часть приложения, требующая этого поведения, использует новую конструкцию.
Замечание
Избегайте связывания совместного поведения, которое является только случайно повторяющимся. Например, только потому, что две разные константы имеют одинаковое значение, это не означает, что у вас должна быть только одна константа, если концептуально они ссылаются на разные вещи. Дублирование всегда предпочтительнее привязывания к неправильной абстракции.
Сохраняемость невежества
Игнорирование сохраняемости (PI) относится к типам, которые необходимо сохранить, но код которых не затрагивается выбором технологии сохраняемости. Такие типы в .NET иногда называются обычными старыми объектами CLR (POCO), поскольку им не требуется наследовать от определенного базового класса или реализовывать определенный интерфейс. Сохраняемость незнания ценна, так как позволяет сохранять одну бизнес-модель несколькими способами, предлагая дополнительную гибкость для приложения. Варианты хранения данных могут изменяться со временем, переходя от одной технологии базы данных к другой, или могут потребоваться дополнительные методы хранения, кроме тех, с которыми приложение начинало работу (например, использование кэша Redis или Azure Cosmos DB в дополнение к реляционной базе данных).
Ниже приведены некоторые примеры нарушений этого принципа:
Обязательный базовый класс.
Требуемая реализация интерфейса.
Классы, отвечающие за собственное сохранение (например, шаблон Active Record).
Обязательный конструктор без параметров.
Свойства, требующие виртуального ключевого слова.
Обязательные атрибуты для сохраняемости.
Требование, что классы имеют какие-либо из указанных выше функций или поведения, добавляет связь между типами, которые необходимо сохранить, и выбор технологии сохраняемости, что затрудняет внедрение новых стратегий доступа к данным в будущем.
Ограниченные контексты
Ограниченные контексты — это центральный шаблон в Domain-Driven проектировании. Они предоставляют способ решения сложности в крупных приложениях или организациях путем разбиения его в отдельные концептуальные модули. Затем каждый концептуальный модуль представляет контекст, разделенный от других контекстов (следовательно, привязанных) и может развиваться независимо. Каждый ограниченный контекст в идеале должен быть свободен выбирать собственные имена для концепций в нем и иметь эксклюзивный доступ к собственному хранилищу сохраняемости.
По крайней мере, отдельные веб-приложения должны стремиться быть собственным привязанным контекстом, с собственным хранилищем сохраняемости для своей бизнес-модели, а не совместно использовать базу данных с другими приложениями. Обмен данными между ограничивающими контекстами осуществляется через программные интерфейсы, а не через общую базу данных, которая позволяет выполнять бизнес-логику и события в ответ на изменения, которые происходят. Ограниченные контексты тесно сопоставляются с микросервисами, которые также идеально реализуются как отдельные ограниченные контексты.