<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>Архитектура ПО · Tommy Cheese</title>
    <link>https://tommycheese.github.io/ru/tags/%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84/</link>
    <description>Личный блог Tommy Cheese о программной инженерии, искусственном интеллекте и практическом обучении.</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>ru</language>
    <lastBuildDate>Mon, 29 Jul 2024 06:13:15 +0800</lastBuildDate>
    <atom:link href="https://tommycheese.github.io/ru/tags/%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84/index.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Перечитывая «Чистую архитектуру» (6): границы</title>
      <link>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%85%AD/</link>
      <pubDate>Mon, 29 Jul 2024 06:13:15 +0800</pubDate>
      <guid>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%85%AD/</guid>
      <description>&lt;h2 id="什么是边界"&gt;Что такое границы&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Проектирование архитектуры ПО само по себе является искусством проведения границ.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Границы разделяют ПО на элементы, ограничивая зависимости между сторонами. Проведение границ в начале проекта позволяет максимально отложить некоторые решения, чтобы в будущем они не мешали &lt;strong&gt;основной бизнес-логике&lt;/strong&gt; системы. Оно также уменьшает или устраняет ненужную связанность архитектуры. Именно связанность, особенно порождённая преждевременными и незрелыми решениями вроде выбора фреймворка или базы данных, поглощает больше всего трудозатрат. Такие детали следует откладывать.&lt;/p&gt;
&lt;p&gt;Как проводить границы? Можно опираться на следующие основные принципы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Границы следует проводить между несвязанными вещами, например GUI и бизнес-логикой. Ввод и вывод (GUI) несущественны для бизнес-логики: с ней могут работать разные реализации GUI.&lt;/li&gt;
&lt;li&gt;Границы следует проводить вдоль осей изменения системы. Компоненты по разные стороны должны меняться по разным причинам и с разной скоростью. Оси изменения помогают определить SRP на уровне модулей и классов и CCP на уровне компонентов.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Разобравшись с ролью границ и принципами их проведения, можно перейти к основному процессу:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Сначала разделите систему на компоненты: одни содержат основную бизнес-логику, другие являются плагинами, не относящимися к ней, но предоставляющими необходимые функции.&lt;/li&gt;
&lt;li&gt;Измените исходный код так, чтобы неосновные компоненты зависели от компонентов основной бизнес-логики.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Проведение границ — конкретное применение принципа инверсии зависимостей и принципа устойчивых абстракций. Стрелка зависимости должна идти от низкоуровневой конкретной реализации к высокоуровневой абстракции.&lt;/p&gt;
&lt;h2 id="边界剖析"&gt;Разбор границ&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Архитектуру системы определяют программные компоненты и границы между ними&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Границы бывают разными. В этой главе они рассматриваются на примере вызовов через границу.&lt;/p&gt;
&lt;p&gt;Во время выполнения вызов через границу означает, что функция с одной стороны вызывает функцию с другой и передаёт ей данные. Чтобы устроить такие вызовы правильно, нужно управлять зависимостями исходного кода. Иначе изменение кода одного модуля может потребовать изменения или перекомпиляции других модулей и их повторного развёртывания. Чёткие границы помогают сократить число таких случаев.&lt;/p&gt;
&lt;p&gt;Вызовы через границы можно разделить на уровни исходного кода, развёртывания, сервисов и физические границы.&lt;/p&gt;
&lt;h3 id="源码层次的跨边界调用"&gt;Вызовы через границы на уровне исходного кода&lt;/h3&gt;
&lt;p&gt;Такие вызовы встречаются в монолитной архитектуре. Для управления внутренними зависимостями ей обычно требуется динамический полиморфизм в той или иной форме.&lt;/p&gt;
&lt;p&gt;Самый простой случай — низкоуровневый клиент вызывает высокоуровневую сервисную функцию. И во время выполнения, и при компиляции зависимость направлена одинаково: от низкоуровневого компонента к высокоуровневому.&lt;/p&gt;
&lt;p&gt;(Исходная схема пока отсутствует)&lt;/p&gt;
&lt;p&gt;Но когда клиент высокоуровневого компонента должен вызвать службу низкоуровневого, динамический полиморфизм необходим для инверсии зависимости. Интерфейс Service на схеме представляет собой одну из форм SPI.&lt;/p&gt;
&lt;p&gt;(Исходная схема пока отсутствует)&lt;/p&gt;
&lt;h3 id="部署层次的跨边界调用"&gt;Вызовы через границы на уровне развёртывания&lt;/h3&gt;
&lt;p&gt;На уровне развёртывания каждая развёртываемая единица упаковывается в удобный для работы формат файла. Помимо этого, компоненты, разделённые на таком уровне, почти не отличаются от монолитной структуры.&lt;/p&gt;
&lt;p&gt;Эти вызовы затрагивают физические границы, потому что отдельные развёртываемые единицы должны пересекать их для взаимодействия. Типичные физические границы — динамические библиотеки, потоки и локальные процессы.&lt;/p&gt;
&lt;h3 id="服务层次的跨边界调用"&gt;Вызовы через границы на уровне сервисов&lt;/h3&gt;
&lt;p&gt;Сервисы образуют самую сильную границу в архитектуре: каждый сервис является процессом. При проведении архитектурных границ необходимо максимально ограничивать число взаимодействий. На этом уровне обмен должен выдерживать высокие задержки. В остальном применимы те же правила, что для локальных процессов: низкоуровневые сервисы должны становиться плагинами высокоуровневых.&lt;/p&gt;
</description>
    <category>Архитектура ПО</category><category>Чистая архитектура</category></item>
    <item>
      <title>Перечитывая «Чистую архитектуру» (5): архитектура ПО</title>
      <link>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%94/</link>
      <pubDate>Sun, 28 Jul 2024 19:23:23 +0800</pubDate>
      <guid>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%94/</guid>
      <description>&lt;p&gt;Пора перейти к архитектуре ПО. Ранее мы рассмотрели модули, классы и компоненты; эта часть посвящена основному определению программной архитектуры.&lt;/p&gt;
&lt;h2 id="写在最前面"&gt;Прежде всего&lt;/h2&gt;
&lt;p&gt;🌟 Архитектор ПО должен сам быть программистом и продолжать непосредственно писать код. Не сталкиваясь лично с трудностями, вызванными устройством системы, он не почувствует последствий плохого проектирования и постепенно утратит правильное направление.&lt;/p&gt;
&lt;h2 id="什么是软件架构"&gt;Что такое архитектура ПО?&lt;/h2&gt;
&lt;p&gt;Суть архитектурной работы — обсуждать и планировать разделение системы на компоненты, определять их расположение, связи и способы взаимодействия, то есть устанавливать границы.&lt;/p&gt;
&lt;p&gt;У проектирования архитектуры обычно две цели:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Упростить разработку, развёртывание, эксплуатацию и сопровождение компонентов.&lt;/li&gt;
&lt;li&gt;Чтобы все эти виды работы продвигались легче, при проектировании нужно как можно дольше &lt;strong&gt;сохранять как можно больше вариантов выбора&lt;/strong&gt;;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Качество архитектуры слабо связано с тем, работает ли система правильно, то есть с её поведением. В мире немало систем с плохой архитектурой, которые исправно работают. Настоящие проблемы чаще возникают при разработке, развёртывании и последующем расширении. Это косвенно отражает мысль автора: архитектурная ценность системы в определённой мере важнее поведенческой.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Цели архитектурного проектирования:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Основная направленность: хорошая архитектура строится вокруг вариантов использования, сосредоточивается на них и изолирует их от окружающих факторов. Для этого выборы откладывают как можно дольше, сохраняя больше возможностей.&lt;/li&gt;
&lt;li&gt;Главная цель: поддерживать весь жизненный цикл ПО. Хорошая архитектура делает систему понятной, удобной для изменения и сопровождения, а также простой в развёртывании.&lt;/li&gt;
&lt;li&gt;Конечная цель: максимизировать производительность программистов, освобождая их возможности, и минимизировать &lt;strong&gt;совокупные&lt;/strong&gt; эксплуатационные затраты системы.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Главное в архитектурном проектировании — отделить друг от друга &lt;strong&gt;правила&lt;/strong&gt;, а затем перегруппировать их по характеру изменений, проведя границы. Здесь можно опираться на SRP, CCP, CSP, SDP, SAP и другие принципы.&lt;/p&gt;
&lt;h2 id="软件架构的职责"&gt;Обязанности архитектуры ПО&lt;/h2&gt;
&lt;p&gt;Архитектура отвечает за разработку, развёртывание, эксплуатацию и сопровождение.&lt;/p&gt;
&lt;p&gt;В разработке архитектура должна упрощать создание системы. Поэтому разным командам нужны разные архитектурные решения. В определённой мере это отражает закон Конвея.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Почему закон Конвея связывает архитектуру со структурой команды? Подробнее — в видео:&lt;a href="https://www.bilibili.com/video/BV1bb421E7i6/?spm_id_from=333.337.search-card.all.click&amp;amp;vd_source=8c0f6bcaf6b2e92f574553f45e565994"&gt;Закон Конвея: почему архитектура отражает структуру команды? — bilibili&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;В развёртывании архитектура должна обеспечивать простое развёртывание одним действием.&lt;/p&gt;
&lt;p&gt;В эксплуатации:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Как уже отмечалось, влияние архитектуры на эффективность выполнения меньше, чем на остальные аспекты. Автор объясняет это тем, что дополнительные аппаратные ресурсы могут компенсировать недостаточное внимание к выполнению, тогда как архитектурная работа обычно требует более дорогих человеческих ресурсов.&lt;/li&gt;
&lt;li&gt;Архитектура должна не только влиять на эффективность, но и отражать эксплуатационные требования системы. Хорошее устройство должно позволять разработчику сразу понимать, как система работает. Варианты использования, функции и обязательное поведение нужно сделать видимыми сущностями первого уровня, упрощая понимание. Именно об этом говорится в главе «Кричащая архитектура».&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Сопровождение обычно обходится дороже всего. Его затраты делятся на &lt;strong&gt;исследование&lt;/strong&gt; и &lt;strong&gt;риск&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Исследование (spelunking): затраты на изучение существующей системы, чтобы определить лучшее место и способ добавления функции или исправления проблемы.&lt;/li&gt;
&lt;li&gt;Риск (risk): при таких изменениях всегда возможно появление новых проблем. Эта вероятность и составляет затраты риска.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="保持可选项"&gt;Сохранять варианты выбора&lt;/h2&gt;
&lt;p&gt;Вспомним два вида ценности: поведенческую и архитектурную. Повысить архитектурную ценность значит сделать ПО гибче, а для этого архитектура должна сохранять как можно больше вариантов выбора.&lt;/p&gt;
&lt;p&gt;Элементы программной системы делятся на &lt;strong&gt;правила&lt;/strong&gt; и &lt;strong&gt;детали&lt;/strong&gt;. Правила охватывают бизнес-правила и рабочие процессы и представляют настоящую ценность системы. Детали позволяют пользователям, другим системам и программистам взаимодействовать с правилами, не влияя на сами правила. Примеры — устройства IO и базы данных. Выбор таких деталей нужно по возможности оставлять открытым.&lt;/p&gt;
&lt;p&gt;Цель архитектора — создать форму системы, в которой правила служат &lt;strong&gt;основополагающими элементами&lt;/strong&gt;, а детали отделены от них, позволяя в процессе принятия решений &lt;strong&gt;откладывать решения, связанные с деталями&lt;/strong&gt;. Чем дальше продвигается проект, тем больше информации у нас для обоснованного выбора. Хороший архитектор стремится максимизировать количество доступных вариантов.&lt;/p&gt;
&lt;p&gt;Автор объясняет важность отсрочки деталей на примере независимости от устройств. Современные ОС поддерживают множество устройств ввода-вывода. На заре вычислительной техники основным, почти единственным способом была перфолента, поэтому программисты естественным образом вплетали код её чтения и записи в системный код. С появлением магнитной ленты пришлось писать новый код, затем появились оптические диски… Чтобы избежать повторной разработки и слабой адаптируемости, устройства стали абстрагировать функциями. OS взаимодействует с ними через функции, а разработчики предоставляют конкретные реализации. Ввод-вывод становится плагином OS. Это также прообраз принципа открытости/закрытости: приветствовать 👏 добавление, сопротивляться 🥊 изменению!&lt;/p&gt;
&lt;h2 id="保持独立性"&gt;Сохранять независимость&lt;/h2&gt;
&lt;p&gt;Хорошая архитектура должна обеспечивать достаточную независимость.&lt;/p&gt;
&lt;p&gt;Разделение зависимостей помогает её сохранять.&lt;/p&gt;
&lt;p&gt;Разделение бывает горизонтальным и вертикальным.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Горизонтальное разделение, или разделение по слоям: систему можно разделить на горизонтальные слои — UI, базу данных и так далее.&lt;/li&gt;
&lt;li&gt;Разделение вариантов использования, или вертикальное разделение: одновременно со слоями выделяют вертикальные срезы по вариантам использования. Например, UI добавления заказа отделяют от UI удаления заказа.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Если разделять систему по разным причинам изменений, можно постоянно добавлять новые варианты использования, не затрагивая существующие.&lt;/p&gt;
&lt;p&gt;Уровни разделения также различаются: исходный код, развёртывание и сервисы.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Уровень исходного кода: контролируются зависимости между модулями, а компоненты взаимодействуют вызовами функций. Такой режим называется монолитной структурой.&lt;/li&gt;
&lt;li&gt;Уровень развёртывания: контролируются зависимости между единицами развёртывания, например файлами Jar. Компоненты могут взаимодействовать между потоками — обратите внимание, не по сети, — через Socket или разделяемую память.&lt;/li&gt;
&lt;li&gt;Уровень сервисов: зависимости между компонентами сводятся к структурам данных, а взаимодействие происходит через сетевые пакеты. Например, микросервисы обмениваются данными через rpc или rest.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Строгого правила, какой уровень лучше, нет: по мере зрелости проекта лучший способ разделения может меняться. Хорошая архитектура позволяет начать с монолита, развёртываемого одним файлом, затем вырасти в набор независимых единиц развёртывания, сервисов или микросервисов, а при изменении обстоятельств постепенно вернуться к монолиту. При этом большая часть исходного кода должна оставаться защищённой от изменений. Для системы в целом &lt;strong&gt;способ разделения тоже должен оставаться вариантом выбора&lt;/strong&gt;. Для крупного развёртывания можно использовать один способ, а для небольшого — другой.&lt;/p&gt;
&lt;p&gt;Теперь рассмотрим, как разделение влияет на независимость системы.&lt;/p&gt;
&lt;h3 id="解耦对系统运行独立性的意义"&gt;Значение разделения для независимости выполнения&lt;/h3&gt;
&lt;p&gt;Если варианты использования разных направлений хорошо изолированы, сценарии с высокой и низкой пропускной способностью естественным образом отделяются друг от друга. Если UI и база данных отделены от бизнес-логики, они могут работать на разных серверах. Приложениям, требующим большой пропускной способности, можно предоставить несколько экземпляров на нескольких серверах.&lt;/p&gt;
&lt;h3 id="解耦对系统开发独立性的意义"&gt;Значение разделения для независимости разработки&lt;/h3&gt;
&lt;p&gt;Если система правильно разделена по горизонтальным слоям и вариантам использования, архитектура поддерживает работу нескольких команд независимо от того, организованы ли они по функциям, компонентам, слоям или иным признакам.&lt;/p&gt;
&lt;h3 id="解耦对系统部署独立性的意义"&gt;Значение разделения для независимости развёртывания&lt;/h3&gt;
&lt;p&gt;При хорошем разделении реализации слоёв и конкретных вариантов использования можно даже заменять во время работы системы (hot-swap). Тогда для нового сценария достаточно добавить файлы jar или запустить сервисы, совершенно не затрагивая остальные части.&lt;/p&gt;
&lt;h2 id="什么是代码重复"&gt;Что такое дублирование кода&lt;/h2&gt;
&lt;p&gt;Дублирование бывает двух видов:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Настоящее дублирование: повторения, которые нужно устранить.&lt;/li&gt;
&lt;li&gt;Мнимое дублирование: похожий код, который в действительности развивается по разным путям, с разной скоростью и по разным причинам. Согласно CRP, код с разной частотой использования следует разделять по компонентам. Если не учитывать этого при чтении, мнимое дублирование легко принять за настоящее. Иногда такие повторы необходимы. Помните пример расчёта зарплаты?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Конец пятой статьи)&lt;/p&gt;
</description>
    <category>Архитектура ПО</category><category>Чистая архитектура</category></item>
    <item>
      <title>Введение в предметно-ориентированное проектирование</title>
      <link>https://tommycheese.github.io/ru/blogs/ddd%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%E5%88%9D%E8%AF%86/</link>
      <pubDate>Fri, 26 Jul 2024 19:49:45 +0800</pubDate>
      <guid>https://tommycheese.github.io/ru/blogs/ddd%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%E5%88%9D%E8%AF%86/</guid>
      <description>&lt;h1 id="领域驱动设计概述"&gt;Обзор предметно-ориентированного проектирования&lt;/h1&gt;
&lt;p&gt;Предметно-ориентированное проектирование (DDD, Domain-Driven Design) — подход к проектированию на основе моделей. Он фиксирует знания предметной области в модели и использует её для создания ПО, &lt;strong&gt;которое легче сопровождать&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Проектирование в DDD делится на стратегическое и тактическое. Стратегическое рассматривает предметные области, подобласти и ограниченные контексты; тактическое — сущности, объекты-значения, события предметной области и другие элементы. Их связь показана ниже:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tommycheese.github.io/blogimages/DDD1.png" alt="img"&gt;&lt;/p&gt;
&lt;h2 id="战略设计阶段相关概念"&gt;Понятия стратегического проектирования&lt;/h2&gt;
&lt;h3 id="领域"&gt;Предметная область&lt;/h3&gt;
&lt;p&gt;Предметная область — это область задач, которые должна решать система. Например, ею может быть управление информацией о товарах.&lt;/p&gt;
&lt;h3 id="子域"&gt;Подобласти&lt;/h3&gt;
&lt;p&gt;По различиям используемого языка предметную область можно разделить на подобласти:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ключевая подобласть определяет основное конкурентное преимущество продукта. Это наиболее важная, центральная и специфическая часть бизнеса.&lt;/li&gt;
&lt;li&gt;Общая подобласть предоставляет универсальные функции нескольким подобластям. Примеры — общие системы аутентификации и управления правами. Они не ограничены особенностями предприятия и не требуют большой адаптации.&lt;/li&gt;
&lt;li&gt;Вспомогательная подобласть не содержит ни ключевых, ни универсальных функций. Она специфична для предприятия и не является общей; пример — системы справочников с кодами данных.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="限界上下文"&gt;Ограниченный контекст&lt;/h3&gt;
&lt;p&gt;Ограниченный контекст определяет область, внутри которой используется конкретная модель для решения соответствующих задач.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ограниченный контекст объединяет одну или несколько подобластей&lt;/strong&gt;, &lt;strong&gt;Нужно обеспечить поддержку полного бизнес-процесса одним ограниченным контекстом&lt;/strong&gt;, &lt;strong&gt;и включить в него все предметные области, задействованные в этом процессе&lt;/strong&gt;. Ограниченный контекст служит основанием для выделения микросервисов: каждому контексту соответствует один микросервис.&lt;/p&gt;
&lt;p&gt;Зачем вводить ограниченные контексты? Потому что &lt;strong&gt;контекст&lt;/strong&gt; усложняет написание кода. Рассмотрим пример:&lt;/p&gt;
&lt;p&gt;В подборе персонала «платформа» может обозначать учебное заведение или организацию. В прыжках в воду это вышка, с которой прыгает спортсмен. На железной дороге это посадочная платформа. Представим, что однажды вместе оказались прыгун в воду, HR и проводник, не зная профессий друг друга. И HR спросил спортсмена: «С какой вы платформы?»…&lt;/p&gt;
&lt;p&gt;Видите? Все трое используют слово «платформа», но &lt;strong&gt;без предварительной договорённости&lt;/strong&gt; могут понимать его по-разному. Решение — разделить области прыжков в воду, найма и транспорта и определить термины внутри каждой. Обсуждение в пределах одной области максимально уменьшает неоднозначность. Это и есть договорённость. По той же причине связанные с бизнесом области лучше определить в одном ограниченном контексте: чтобы уменьшить неоднозначность.&lt;/p&gt;
&lt;p&gt;Аналогичная проблема возникает при проектировании ПО. Допустим, большой продукт охватывает прыжки в воду, найм и поездки, но сервисы никак не разделены. В логике прыжков структура platform означает вышку, в найме — организационную платформу, в транспорте — перрон. Когда три направления неизбежно пересекутся, начнётся беда: что делать с экраном, заполненным почти одинаковыми именами переменных с разным смыслом?&lt;/p&gt;
&lt;p&gt;Если точно ограничить смысл термина в предметной области, внутри неё его можно применять свободно. В этом и состоит важная роль ограниченного контекста.&lt;/p&gt;
&lt;h2 id="战术设计阶段相关概念"&gt;Понятия тактического проектирования&lt;/h2&gt;
&lt;h3 id="值对象和实体"&gt;Объекты-значения и сущности&lt;/h3&gt;
&lt;p&gt;Сначала познакомимся с объектами-значениями и сущностями.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Объект-значение определяется значениями своих свойств: два таких объекта с одинаковыми внутренними значениями считаются равными. Следовательно, свойства объекта-значения неизменяемы.&lt;/li&gt;
&lt;li&gt;Сущность — бизнес-объект с уникальным идентификатором, состоянием и жизненным циклом. Её свойства изменяемы. Даже полностью одинаковые свойства не делают две сущности одной и той же: совпасть должны их идентификаторы, например ID.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Различим эти понятия на примере:&lt;/p&gt;
&lt;p&gt;Предположим, у нас есть объект-значение белого цвета Color white{R:255, G:255, B:255} и сущность шины tire{Air:&lt;em&gt;,Size:&lt;/em&gt;}:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Изменяемость: каждое свойство белого цвета неизменно, потому что после его изменения объект уже не обозначает белый цвет. Давление, размер и другие параметры шины могут меняться: она всё равно остаётся шиной.&lt;/li&gt;
&lt;li&gt;Сравнимость: неизменяемость значений определяет правило равенства объектов-значений. Значения двух объектов белого цвета обязательно совпадают, поэтому объекты с одинаковыми внутренними значениями считаются равными. У сущностей из-за изменяемости свойств такого свойства нет.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;У сущностей обычно выделяют четыре формы модели:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Модель без поведения содержит только данные и методы getter/setter. Вся бизнес-логика и прикладная логика размещается в сервисном слое. В Java такой класс называют POJO.&lt;/li&gt;
&lt;li&gt;Анемичная модель включает часть бизнес-логики, но не ту, что зависит от слоя хранения. Логика, зависящая от хранения, находится в сервисном слое.&lt;/li&gt;
&lt;li&gt;Богатая модель содержит всю бизнес-логику, включая зависимую от слоя хранения.&lt;/li&gt;
&lt;li&gt;Перегруженная модель помещает в предметную модель и другую прикладную логику, не относящуюся к бизнес-логике, например авторизацию и транзакции.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Репозиторий Repo&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Repo определяет операции хранения для Entity. Операции должны быть по возможности низкоуровневыми, иметь короткие имена и не быть слишком многочисленными. MyBatis Mapper в Java можно рассматривать как реализацию Repo.&lt;/p&gt;
&lt;h3 id="聚合与聚合根"&gt;Агрегаты и корни агрегатов&lt;/h3&gt;
&lt;p&gt;Агрегат — инкапсуляция большего масштаба. Он объединяет сущности и объекты-значения с общим жизненным циклом, неразделимые с точки зрения бизнеса. Внешние ссылки разрешено предоставлять только корню агрегата. Агрегат также выражает связность. Корни могут вызывать друг друга; их названия обычно являются существительными.&lt;/p&gt;
&lt;p&gt;Корень агрегата обеспечивает инкапсуляцию следующими способами:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Работать со всем агрегатом нужно через его корень; внешний код не должен напрямую обращаться к внутренним элементам. Например, фиолетовый цвет кузова + шины + стальная рама + … = автомобиль. При вождении мы не управляем отдельно шиной, рулём или внешностью. Мы действуем через автомобиль как корень агрегата, косвенно управляя остальными сущностями и объектами-значениями.&lt;/li&gt;
&lt;li&gt;Агрегат задаёт &lt;strong&gt;границу&lt;/strong&gt;, внутри которой все компоненты должны быть корректны с точки зрения бизнес-логики.&lt;/li&gt;
&lt;li&gt;Работа с агрегатом должна проходить в одной атомарной транзакции, иначе возможны ошибки. Агрегат — единица операции: извлечение из репозитория, изменение и возврат составляют атомарное действие. Например, если автомобиль включает колёса и стальную, алюминиевую или карбоновую раму, нельзя снять колесо для ремонта и после ремонта не установить его обратно.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="领域事件domain-events"&gt;События предметной области — Domain Events&lt;/h3&gt;
&lt;p&gt;Изменение свойств сущности порождает событие предметной области — происходящее в ней событие, которое эксперты считают значимым и заслуживающим внимания. Обычно оно означает изменение состояния предметного объекта.&lt;strong&gt;События передают сообщения, запускают другие действия и служат важным средством уменьшения зависимостей предметной модели&lt;/strong&gt;. Для их передачи часто используют &lt;strong&gt;очередь сообщений&lt;/strong&gt;, чтобы каждая подписанная подобласть выполнила собственную внутреннюю реакцию.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Шина сообщений — ещё один способ реализации 👋.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Например, изменение давления в сущности шины порождает событие утечки leaked. Оно передаёт сообщение «шина спускает», вызывая другие реакции: изменение работы силовой системы, ощущений на руле и так далее. Названия событий обычно ставят в прошедшее время, показывая, что событие уже произошло и необратимо.&lt;/p&gt;
&lt;h3 id="领域服务domain-service"&gt;Сервис предметной области — Domain Service&lt;/h3&gt;
&lt;p&gt;Некоторые действия предметной области, по-видимому, &lt;strong&gt;не принадлежат никакому объекту&lt;/strong&gt;. Они выражают важное поведение, поэтому их нельзя игнорировать или просто включить в случайную сущность либо объект-значение. Когда такое поведение выделено, рекомендуют объявить его сервисом предметной области.&lt;/p&gt;
&lt;p&gt;Сервис предметной области — не тот же «сервис», что в микросервисах. Сервисы и корни агрегатов отчасти похожи: оба могут работать с несколькими сущностями. Но их идеи и роли различаются. &lt;strong&gt;Корень агрегата объединяет сущности, а сервис синхронизирует состояния нескольких сущностей, например добавляет сущность item в сущность list&lt;/strong&gt; — скажем, доставляет mail в inbox. Поэтому название Service обычно выражается глаголом.&lt;/p&gt;
&lt;h2 id="ddd领域建模设计领域模型"&gt;Моделирование предметной области в DDD&lt;/h2&gt;
&lt;p&gt;Обычная последовательность моделирования:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;На основе требований предварительно выделить подобласти, ограниченные контексты и связи между контекстами.&lt;/li&gt;
&lt;li&gt;Подробно изучить каждый контекст, определить сущности и объекты-значения, а затем выбрать форму модели сущностей: без поведения, анемичную, богатую или перегруженную.&lt;/li&gt;
&lt;li&gt;Связать сущности и объекты-значения в агрегаты, определить их границы и корни.&lt;/li&gt;
&lt;li&gt;Спроектировать repo для корней агрегатов и продумать создание сущностей и объектов-значений.&lt;/li&gt;
&lt;li&gt;Применить модель в проекте, проверить её обоснованность практикой, выявить недостатки и выполнить рефакторинг.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Это двухэтапный подход DDD: сначала стратегическое проектирование, затем тактическое.&lt;/p&gt;
&lt;p&gt;На практике рекомендуются сущности без поведения, с одними setter/getter, или анемичные модели с простой логикой без операций базы данных, например проверкой допустимости свойств.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Это лишь один способ моделирования DDD — сверху вниз. Возможен и подход снизу вверх: сначала определить предметные модели, сущности и объекты-значения, а затем подобласти и ограниченные контексты…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(Конец раздела)&lt;/p&gt;
</description>
    <category>Архитектура ПО</category><category>Предметно-ориентированное проектирование</category></item>
    <item>
      <title>Перечитывая «Чистую архитектуру» (4): принципы компонентов</title>
      <link>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%9B%9B/</link>
      <pubDate>Sat, 06 Jul 2024 17:27:18 +0800</pubDate>
      <guid>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%9B%9B/</guid>
      <description>&lt;h1 id="再读整洁架构之道四组件构建原则"&gt;Перечитывая «Чистую архитектуру» (4): принципы построения компонентов&lt;/h1&gt;
&lt;p&gt;Третья статья была посвящена проектированию модулей и классов. Теперь пойдём дальше и рассмотрим проектирование компонентов.&lt;/p&gt;
&lt;p&gt;Компонент — единица развёртывания ПО, наименьшая сущность системы, которую можно развернуть независимо. Компоненты можно разрабатывать отдельно; компонентная плагинная архитектура уже стала привычным способом построения ПО.&lt;/p&gt;
&lt;h2 id="组件聚合"&gt;Связность компонентов&lt;/h2&gt;
&lt;p&gt;Принципы связности объясняют, какие модули и классы объединять в компоненты. Основных принципов три:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Принцип эквивалентности повторного использования и выпуска.&lt;/li&gt;
&lt;li&gt;Принцип общей закрытости.&lt;/li&gt;
&lt;li&gt;Принцип совместного повторного использования.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="复用发布等同原则rep"&gt;Эквивалентность повторного использования и выпуска — REP&lt;/h3&gt;
&lt;p&gt;REP утверждает: минимальная единица повторного использования ПО должна совпадать с минимальной единицей его выпуска.&lt;/p&gt;
&lt;p&gt;REP рассматривает повторное использование кода. Он позволяет объединять код с общей темой и функциями в компонент и рекомендует повторно использовать ПО именно компонентами.&lt;/p&gt;
&lt;p&gt;Проще говоря, упакованный выпуск компонента получает номер версии или уникальный идентификатор. Мы повторно используем код, подключая готовую библиотеку: единицей подключения служит пакет компонента, а идентификатором — версия.&lt;/p&gt;
&lt;h3 id="共同闭包ccp"&gt;Общая закрытость — CCP&lt;/h3&gt;
&lt;p&gt;CCP предписывает объединять в одном компоненте классы, которые меняются одновременно и по одной причине. Классы, которые не меняются одновременно или по одной причине, следует размещать в разных компонентах.&lt;/p&gt;
&lt;p&gt;CCP смотрит на сопровождение кода. Объединение кода с общей причиной и целью изменений снижает нагрузку, связанную с выпуском, проверкой и развёртыванием ПО.&lt;/p&gt;
&lt;p&gt;Как уже отмечалось, CCP — компонентная версия SRP. Оба принципа можно выразить так:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Объединяйте то, что меняется одновременно по одной причине. Разделяйте то, что меняется по разным причинам и в разное время.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;В SRP речь идёт о функциях и других составляющих класса или модуля.&lt;/li&gt;
&lt;li&gt;В CCP — о классах и модулях, составляющих компонент.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="共同复用crp"&gt;Совместное повторное использование — CRP&lt;/h3&gt;
&lt;p&gt;CRP утверждает: не заставляйте пользователей компонента зависеть от того, что им не нужно.&lt;/p&gt;
&lt;p&gt;Код с разными сценариями и частотой использования следует разносить по компонентам. CRP направлен на предотвращение ненужного разделения и является обобщением принципа разделения интерфейсов ISP.&lt;/p&gt;
&lt;p&gt;Как обобщённая форма разделения интерфейсов, CRP вместе с ISP выражается так:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Не зависите от того, чем не пользуетесь.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;В ISP это функции или классы, содержащие ненужные методы.&lt;/li&gt;
&lt;li&gt;В CCP это классы или модули, содержащие ненужные функции.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="思考"&gt;Размышление&lt;/h3&gt;
&lt;p&gt;Как связаны REP, CCP и CRP?&lt;/p&gt;
&lt;p&gt;Между ними существует &lt;strong&gt;конкуренция&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;REP и CCP — объединяющие принципы: они подсказывают, какие классы и модули собрать в компонент. CRP — исключающий принцип: он показывает, какие классы и модули не следует объединять.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tommycheese.github.io/blogimages/image-20240706143525187.png" alt="image-20240706143525187"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Если учитывать только REP и CCP, зависимости ПО будут содержать много ненужных частей, что приведёт к лишним выпускам.&lt;/li&gt;
&lt;li&gt;Если учитывать только CRP и CCP, повторное использование станет очень трудным.&lt;/li&gt;
&lt;li&gt;Если учитывать только REP и CRP, изменение некоторых классов или модулей неизбежно потребует изменений множества связанных модулей.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Задача архитектора — находить компромисс между тремя принципами. Состав компонентов должен эволюционировать вместе с приоритетами проекта и балансом удобства разработки и повторного использования. Поэтому направление развития нужно оценивать динамически.&lt;/p&gt;
&lt;h2 id="组件耦合"&gt;Зависимости между компонентами&lt;/h2&gt;
&lt;p&gt;Принципы зависимостей объясняют, как организовывать отношения между компонентами. Их три:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Принцип ацикличности зависимостей — ADP.&lt;/li&gt;
&lt;li&gt;Принцип устойчивых зависимостей — SDP.&lt;/li&gt;
&lt;li&gt;Принцип устойчивых абстракций — SAP.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="无依赖环原则adp"&gt;Ацикличность зависимостей — ADP&lt;/h3&gt;
&lt;p&gt;ADP утверждает: в графе зависимостей компонентов не должно быть циклов.&lt;/p&gt;
&lt;p&gt;Управление номерами версий решает проблему «синдрома следующего утра», но для этого нужно соблюдать ADP. Сначала посмотрим, как автор описывает этот синдром:&lt;/p&gt;
&lt;p&gt;Вы весь день трудились и наконец заставили код работать. На следующее утро обнаруживаете, что он по непонятной причине перестал работать. Скорее всего, кто-то изменил компонент, от которого зависит ваш проект.&lt;/p&gt;
&lt;p&gt;Обычно предлагают два решения:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Еженедельная сборка.&lt;/li&gt;
&lt;li&gt;Управление номерами версий.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;Еженедельная сборка&lt;/u&gt;&lt;/strong&gt;: каждый работает в собственном репозитории, а раз в неделю, например в пятницу, проект собирают и устраняют конфликты.&lt;/p&gt;
&lt;p&gt;Ограничения этого подхода очевидны:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;По мере роста проекта интеграцию всё труднее завершать вовремя.&lt;/li&gt;
&lt;li&gt;Сборка и тестирование усложняются, цикл обратной связи удлиняется, и качество разработки закономерно падает.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Поэтому требуется управление номерами версий.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;Управление номерами версий&lt;/u&gt;&lt;/strong&gt;: после выпуска новой версии компонента каждая зависимая команда самостоятельно решает, переходить ли на неё сразу.&lt;/p&gt;
&lt;p&gt;Управление номерами версий &lt;strong&gt;не допускает циклов в графе зависимостей компонентов&lt;/strong&gt;, то есть требует соблюдения ADP. Иначе синдром следующего утра неизбежен. Почему?&lt;/p&gt;
&lt;p&gt;Цикл объединяет все входящие в него компоненты в один большой компонент. Их версии должны согласованно совпадать, чтобы остальные компоненты могли успешно подключать их. Циклы также затрудняют тестирование. Хотя mock-заглушки распространены, повторно создавать их для компонентов всего цикла весьма неэлегантно.&lt;/p&gt;
&lt;p&gt;Как устранить циклическую зависимость? Есть два способа:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Создать интерфейсы с помощью инверсии зависимостей.&lt;/li&gt;
&lt;li&gt;Создать новый компонент.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Применение DIP понятно. Рассмотрим рабочий случай, показывающий, как разорвать цикл созданием нового компонента.&lt;/p&gt;
&lt;p&gt;Команда проектирует систему с помощью DDD. Сущность Entity Dog зависит от Dog Repo для операции сохранения Save, поэтому модуль Entity зависит от Repo. К несчастью, Dog PO тоже определён в Entity — неудачное решение. Функции Save в Repo нужен PO для преобразования и сохранения объекта. Возникает цикл Entity ↔ Repo. К счастью, Go запрещает взаимные зависимости модулей, и проверка сообщает о циклическом импорте. Как поступить?&lt;/p&gt;
&lt;p&gt;Нужно создать модуль PO, перенести в него PO из Entity и отнести туда функции Repo, зависящие от PO. Это устраняет цикл. Другой вариант — внедрение зависимостей. При использовании SprintBoot проблему может решить @AutoWired, но по существу он также создаёт новый компонент — пул зависимостей, — устраняя цикл.&lt;/p&gt;
&lt;p&gt;Подобных примеров много. Помните конфликты версий библиотек при установке пакетов Python через anaconda?&lt;/p&gt;
&lt;p&gt;Небольшое отступление: проектируя компоненты, я естественным образом пытался сопоставить их функциям системы один к одному. Тогда и граф зависимостей совпадал бы с функциональным разбиением. Казалось очевидным, что такой граф можно получить уже в начале проектирования.&lt;/p&gt;
&lt;p&gt;Но автор специально отмечает невозможность проектирования сверху вниз. Посмотрим на объяснение в книге:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;/p&gt;
&lt;p&gt;Граф компонентов должен меняться и расширяться вместе с системой; идеально спроектировать его с самого начала нельзя. Он не соответствует функциям один к одному, а скорее представляет карту возможностей сборки и сопровождения приложения.&lt;/p&gt;
&lt;p&gt;По мере появления новых спроектированных и реализованных модулей возникает потребность управлять зависимостями. Мы стремимся максимально ограничить область влияния изменений, поэтому с помощью SRP и CCP объединяем классы, которые часто меняются одновременно.&lt;/p&gt;
&lt;p&gt;Важная цель графа компонентов — показывать, как изолировать частые изменения. Часто меняющиеся компоненты не должны влиять на те, которые обязаны оставаться устойчивыми.&lt;/p&gt;
&lt;p&gt;По мере роста приложения возрастает и потребность в повторно используемых компонентах. Тогда на их состав начинает влиять CRP. Наконец, при появлении циклических зависимостей применение ADP вызывает перестройку и расширение графа.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="稳定依赖原则sdp"&gt;Устойчивые зависимости — SDP&lt;/h3&gt;
&lt;p&gt;SDP утверждает: зависимости должны указывать в сторону большей устойчивости. Как правило, нижние уровни архитектуры должны быть устойчивее.&lt;/p&gt;
&lt;p&gt;От компонента, который должен часто меняться, не должен зависеть компонент, который трудно изменить. Иначе первый тоже станет трудноизменяемым. В этом одна из трудностей разработки: тщательно спроектированный гибкий компонент может стать жёстким из-за единственной чужой зависимости. Соблюдение SDP позволяет этого избежать.&lt;/p&gt;
&lt;p&gt;Для оценки устойчивости автор предлагает показатель:
$$
I=\frac{Fan-out}{Fan-in+Fan-out}
$$
Fan-in — число входящих зависимостей, Fan-out — исходящих. Чем меньше у компонента исходящих зависимостей, тем он устойчивее: при нулевом значении отсутствуют внешние факторы, заставляющие его меняться.&lt;/p&gt;
&lt;p&gt;SDP требует, чтобы показатель I каждого компонента был больше показателя I компонентов, от которых он зависит. Иными словами, цель зависимости должна быть устойчивее.&lt;/p&gt;
&lt;p&gt;SDP не требует устойчивости всех компонентов. Граф проектируют именно для выбора, какие компоненты должны быть устойчивыми, а какие — нет. Полностью устойчивая архитектура негибка, а негибкая архитектура не обладает достаточной архитектурной ценностью.&lt;/p&gt;
&lt;h3 id="稳定抽象原则sap"&gt;Устойчивые абстракции — SAP&lt;/h3&gt;
&lt;p&gt;SAP утверждает: степень абстрактности компонента должна соответствовать его устойчивости.&lt;/p&gt;
&lt;p&gt;Высокоуровневые правила должны находиться в устойчивых компонентах, но тогда их трудно менять. К счастью, OCP позволяет проектировать устойчивые компоненты удобными для расширения. Абстрактные классы позволяют сочетать устойчивость с возможностью расширения и изменения.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Абстрактный класс — промежуточная зона между интерфейсом и классом.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;SAP связывает устойчивость компонента с его абстрактностью. Устойчивые компоненты должны быть абстрактными, чтобы устойчивость не мешала расширению. Неустойчивые компоненты должны содержать конкретную реализацию, которую легко изменить. Поэтому компонент, претендующий на устойчивость, должен состоять из интерфейсов и абстрактных классов, допускающих дальнейшее расширение.&lt;/p&gt;
&lt;p&gt;Аналогично устойчивости, абстрактность можно измерять показателем:
$$
A=\frac{N_c}{N_a}
$$
Nc — число классов компонента, Na — число абстрактных классов и интерфейсов. A принимает значения от 0 до 1: 0 означает отсутствие абстрактных классов, 1 — наличие только абстрактных классов.&lt;/p&gt;
&lt;h3 id="思考-1"&gt;Размышление 🤔&lt;/h3&gt;
&lt;p&gt;Связаны ли SDP и SAP?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Во-первых, связь SDP, SAP и DIP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Фактически SDP + SAP = DIP для компонентов. SDP требует направлять зависимости к более устойчивому, а SAP говорит, что устойчивость подразумевает абстрактность. Следовательно, зависимости должны направляться к более абстрактному.&lt;/p&gt;
&lt;p&gt;Как это понимать?&lt;/p&gt;
&lt;p&gt;DIP обеспечивает гибкость на уровне классов. SDP + SAP обеспечивают достаточную гибкость компонентов при сохранении устойчивости. И DIP, и SDP + SAP направляют архитектурные зависимости от конкретных реализаций к абстрактным классам и интерфейсам. Поэтому по своей роли SDP + SAP равны компонентному DIP.&lt;/p&gt;
&lt;p&gt;Но различия остаются. Для класса нет серой зоны: он либо абстрактный, либо нет. SDP и SAP применяются к компонентам, которые могут быть частично абстрактными и частично устойчивыми.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Во-вторых, общность целей SAP и SDP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SDP направляет зависимости к устойчивому, а SAP требует размещать высокоуровневые правила в абстрактных компонентах. Это позволяет отделять правила от деталей и поддерживать плагинную разработку. Их цели согласованы.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Снова встретилось выражение &lt;strong&gt;плагинная разработка&lt;/strong&gt;. Где оно было раньше? В DIP, инверсии зависимостей. Это ещё раз показывает сходство SAP + SDP с DIP для компонентов.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="主序列"&gt;Главная последовательность&lt;/h3&gt;
&lt;p&gt;Если совместить показатель неустойчивости I и показатель абстрактности A на графике, прямая a = -i + 1 называется линией главной последовательности.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tommycheese.github.io/blogimages/image-20240706171725797.png" alt="image-20240706171725797"&gt;&lt;/p&gt;
&lt;p&gt;На графике выделяют три области:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Зона боли около (0, 0): компоненты очень устойчивы и одновременно конкретны, поэтому их трудно менять. Пример — таблицы базы данных. Вспомогательные библиотеки также относятся к зоне боли: хотя их I равен 1, поскольку они зависят от множества компонентов и потому неустойчивы, изменять их нельзя без проблем в большом количестве кода.&lt;/li&gt;
&lt;li&gt;Зона бесполезности около (1, 1): компоненты чрезмерно абстрактны, но другие от них не зависят, поэтому ими часто невозможно пользоваться. Проблемы исходного кода и классов здесь обычно имеют исторические причины, например забытый старый код.&lt;/li&gt;
&lt;li&gt;Зона главной последовательности: оптимальные положения компонентов — концы линии (0, 1) и (1, 0). Хороший архитектор должен стремиться приблизить к ним большую часть своих компонентов.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Как оценивать компонент в целом? Рассчитать расстояние до главной последовательности, количественно выражающее соответствие проекта ей. Это показатель D: 0 означает расположение прямо на линии, 1 — максимальное удаление.
$$
D=|A+I-1|
$$
Степень превышения D над нулём помогает направлять рефакторинг и перепроектирование компонентов.&lt;/p&gt;
&lt;p&gt;У D есть и другие применения. Например, среднее значение и дисперсия D всех компонентов позволяют статистически оценивать устройство системы:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;У хорошо спроектированной системы среднее значение и дисперсия D должны быть близки к нулю.&lt;/li&gt;
&lt;li&gt;Дисперсию можно использовать как порог соответствия, выявляя нетипичные компоненты.&lt;/li&gt;
&lt;li&gt;Можно также отслеживать дисперсию D во времени, наблюдая, как меняются устойчивость и абстрактность архитектуры.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Конец четвёртой статьи)&lt;/p&gt;
</description>
    <category>Архитектура ПО</category><category>Чистая архитектура</category></item>
    <item>
      <title>Перечитывая «Чистую архитектуру» (3): принципы SOLID</title>
      <link>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%89/</link>
      <pubDate>Fri, 05 Jul 2024 14:11:30 +0800</pubDate>
      <guid>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%89/</guid>
      <description>&lt;h1 id="再读整洁架构之道三solid原则"&gt;Перечитывая «Чистую архитектуру» (3): принципы SOLID&lt;/h1&gt;
&lt;p&gt;При построении программных модулей преследуют три основные цели:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Сделать ПО устойчивым к изменениям.&lt;/li&gt;
&lt;li&gt;Сделать ПО более понятным.&lt;/li&gt;
&lt;li&gt;Создавать компоненты, пригодные для повторного использования в разных программных системах.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Принципы SOLID прежде всего объясняют, как объединять данные и функции в классы, а затем связывать классы в программу. Они направляют проектирование модулей; на уровне архитектуры действуют другие принципы.&lt;/p&gt;
&lt;p&gt;SOLID включает принцип единственной ответственности &lt;strong&gt;S&lt;/strong&gt;RP, принцип открытости/закрытости &lt;strong&gt;O&lt;/strong&gt;CP, принцип подстановки Лисков &lt;strong&gt;L&lt;/strong&gt;SP, принцип разделения интерфейсов &lt;strong&gt;I&lt;/strong&gt;SP и принцип инверсии зависимостей &lt;strong&gt;D&lt;/strong&gt;IP.&lt;/p&gt;
&lt;h2 id="单一职责原则srp"&gt;Принцип единственной ответственности — SRP&lt;/h2&gt;
&lt;p&gt;SRP определяет для каждого модуля одну ответственность: &lt;strong&gt;должна существовать только одна причина для его изменения&lt;/strong&gt;. Каждый программный модуль должен отвечать только перед одной категорией действующих лиц.&lt;/p&gt;
&lt;p&gt;На уровне компонентов SRP называется принципом общей закрытости — CCP.&lt;/p&gt;
&lt;p&gt;Что произойдёт, если проектировать модуль без соблюдения SRP? Рассмотрим пример.&lt;/p&gt;
&lt;p&gt;Пусть финансовый отдел, кадровая служба и отдел разработки совместно зависят от программы расчёта зарплаты и рабочего времени. В программе три функции:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CalculateSalary: расчёт зарплаты.&lt;/li&gt;
&lt;li&gt;CalculateTime: расчёт рабочего времени.&lt;/li&gt;
&lt;li&gt;Save: сохранение информации.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Три отдела спокойно пользуются программой. Однажды у финансового отдела меняется порядок расчёта зарплаты, и его специалисты изменяют функцию CalculateSalary. После этого происходит неожиданное: одновременно меняется зарплата у кадровой службы и отдела разработки…&lt;/p&gt;
&lt;h2 id="开闭原则ocp"&gt;Принцип открытости/закрытости — OCP&lt;/h2&gt;
&lt;p&gt;«Приветствовать добавление, сопротивляться изменению!»&lt;/p&gt;
&lt;p&gt;OCP утверждает: чтобы программную систему было легче менять, её устройство должно позволять изменять поведение добавлением нового кода, а не только правкой существующего. Хорошо спроектированное ПО легко расширяется и сопротивляется изменению.&lt;/p&gt;
&lt;p&gt;OCP относится не только к классам и модулям, но и к компонентам. Он вырос из идеи независимости от устройств. Например, инверсия зависимостей позволяет оформить устройства IO как плагины, чтобы легко добавлять новые, не изменяя прежние.&lt;/p&gt;
&lt;p&gt;OCP реализуют, разделяя систему на компоненты и организуя их зависимости в иерархию, при которой изменения низкоуровневых компонентов не затрагивают высокоуровневые.&lt;/p&gt;
&lt;h2 id="里式替换lsp原则"&gt;Принцип подстановки Лисков — LSP&lt;/h2&gt;
&lt;p&gt;LSP утверждает: чтобы строить систему из взаимозаменяемых компонентов, все эти компоненты должны соблюдать один и тот же контракт.&lt;/p&gt;
&lt;p&gt;Суть LSP — взаимозаменяемость: если для каждого объекта o1 типа S существует объект o2 типа T, причём поведение программы P, работающей с T, не меняется при замене o2 на o1, то S можно назвать подтипом T.&lt;/p&gt;
&lt;p&gt;LSP можно и нужно применять на архитектурном уровне: нарушение взаимозаменяемости заставляет добавлять множество сложных компенсирующих механизмов.&lt;/p&gt;
&lt;h2 id="接口隔离原则isp"&gt;Принцип разделения интерфейсов — ISP&lt;/h2&gt;
&lt;p&gt;ISP предписывает избегать ненужных зависимостей. На любом уровне проектирования зависимость от того, что на самом деле не требуется, может привести к неожиданным проблемам.&lt;/p&gt;
&lt;h2 id="依赖反转dip"&gt;Инверсия зависимостей — DIP&lt;/h2&gt;
&lt;p&gt;DIP утверждает: код высокоуровневых правил не должен зависеть от кода низкоуровневых деталей. Напротив, низкоуровневая реализация должна зависеть от высокоуровневых правил.&lt;/p&gt;
&lt;p&gt;Для простого объяснения инверсии зависимостей нужно различать поток управления и зависимости исходного кода. Пусть управление идёт от компонента A к B. В таком устройстве B должен знать реализацию A, а A должен подключать модуль B, поэтому зависимость кода тоже направлена от A к B. Поведение системы определяет поток управления, а он — зависимости кода, и у архитектуры не остаётся выбора. После введения интерфейса B лишь реализует его; связь с B проходит через интерфейс, а A подключает интерфейс. A больше не нужно подключать модуль B. Направления потока управления и зависимости исходного кода становятся противоположными — это и есть инверсия зависимостей.&lt;/p&gt;
&lt;p&gt;Инверсия зависимостей даёт свободу архитектурному проектированию. Для гибкой системы зависимости исходного кода должны чаще ссылаться на абстракции — интерфейсы, абстрактные классы и подобные типы, — а не на конкретные реализации.&lt;/p&gt;
&lt;p&gt;Из DIP следуют несколько правил написания кода:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Чаще используйте абстрактные интерфейсы и по возможности избегайте изменчивых конкретных классов реализации.&lt;/li&gt;
&lt;li&gt;Не создавайте производные классы от конкретных классов реализации.&lt;/li&gt;
&lt;li&gt;Не переопределяйте (override) функции, содержащие конкретную реализацию.&lt;/li&gt;
&lt;li&gt;Избегайте в коде имён, привязанных к конкретной реализации, и других легко меняющихся сущностей.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Конец третьей статьи)&lt;/p&gt;
</description>
    <category>Архитектура ПО</category><category>Чистая архитектура</category></item>
    <item>
      <title>Перечитывая «Чистую архитектуру» (2): парадигмы программирования</title>
      <link>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%8C/</link>
      <pubDate>Fri, 05 Jul 2024 07:25:33 +0800</pubDate>
      <guid>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%8C/</guid>
      <description>&lt;h1 id="再读整洁架构之道二"&gt;Перечитывая «Чистую архитектуру» (2)&lt;/h1&gt;
&lt;p&gt;Парадигма программирования — это модель написания программ. Она подсказывает, какую структуру кода и в каких обстоятельствах следует использовать.&lt;/p&gt;
&lt;p&gt;К настоящему времени появились три парадигмы: структурное, объектно-ориентированное и функциональное программирование.&lt;/p&gt;
&lt;p&gt;По мнению автора, каждая парадигма не расширяет арсенал архитектора. Напротив, у архитекторов и программистов уже достаточно средств, а эти три парадигмы вводят &lt;strong&gt;ограничения&lt;/strong&gt; на их применение. Именно поэтому они и называются парадигмами.&lt;/p&gt;
&lt;h2 id="结构化编程范式"&gt;Структурное программирование&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Структурное программирование ограничивает и упорядочивает прямую передачу управления; в частности, оно ограничивает произвольное использование goto.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="分解程序"&gt;Декомпозиция программы&lt;/h3&gt;
&lt;p&gt;Dijkstra стремился доказывать правильность программ с помощью математических рассуждений: сделать программу евклидовой структурой, чтобы соединять уже доказанные конструкции в новые программы и затем выводить корректность всей программы.&lt;/p&gt;
&lt;p&gt;Он также обнаружил, что большое количество goto затрудняет декомпозицию программы, хотя все задачи оператора goto можно решать с помощью ветвлений и циклов.&lt;/p&gt;
&lt;p&gt;Структурная парадигма показывает, что программы можно декомпозировать: большую задачу разбивают на сочетание высокоуровневых функций, каждую из которых далее делят на низкоуровневые функции, рекурсивно продолжая этот процесс. Кроме того, каждую полученную функцию также можно написать в рамках структурной парадигмы.&lt;/p&gt;
&lt;p&gt;Однако программирование с такими формальными доказательствами не стало основным подходом. Чаще применяется научный метод проверки.&lt;/p&gt;
&lt;h3 id="科学证明法"&gt;Научный метод проверки&lt;/h3&gt;
&lt;p&gt;Научные теории и законы можно опровергнуть, но нельзя окончательно доказать. Аналогично, тесты могут опровергнуть корректность программы, но не доказать её: «Тестирование может показать наличие ошибок, но не доказать их отсутствие». Применяя научный метод, структурная парадигма побуждает сначала рекурсивно разложить программу на небольшие проверяемые функции, а затем написать соответствующие &lt;strong&gt;тесты&lt;/strong&gt;, пытаясь показать, что функции ошибочны. Если тесты не опровергают их корректность, функции можно считать достаточно правильными, а на этом основании — и всю программу.&lt;/p&gt;
&lt;h2 id="面向对象编程范式"&gt;Объектно-ориентированное программирование&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Объектно-ориентированное программирование ограничивает и упорядочивает косвенную передачу управления.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;В частности, объектно-ориентированное программирование использует &lt;strong&gt;полиморфизм&lt;/strong&gt;, чтобы ограничить применение указателей на функции. Их цели можно понимать как ограниченное множество: например, полиморфизм в java обычно возникает между объектами двух классов, связанных наследованием.&lt;/p&gt;
&lt;p&gt;Автор также считает полиморфизм главным свойством OOP. С его помощью достигается &lt;strong&gt;инверсия зависимостей&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Инверсия зависимостей означает введение интерфейсов, позволяющих полностью управлять зависимостями исходного кода системы независимо от потока управления.&lt;/p&gt;
&lt;p&gt;Она позволяет разрабатывать систему как набор плагинов. Например, инвертировав зависимости между web UI, базой данных и бизнес-логикой, можно отделить основную бизнес-логику от UI и базы данных. Тогда UI и база данных становятся её плагинами.&lt;/p&gt;
&lt;p&gt;Объектно-ориентированное программирование — это способность управлять зависимостями исходного кода посредством полиморфизма. Она позволяет архитектору создавать плагинную архитектуру, отделяя высокоуровневые правила от низкоуровневых реализаций. Низкоуровневые компоненты можно компилировать в плагины, разрабатывать и развёртывать независимо от высокоуровневых.&lt;/p&gt;
&lt;h2 id="函数式编程范式"&gt;Функциональное программирование&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Функциональное программирование ограничивает и упорядочивает присваивание в программе.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Иными словами, переменные в функциональном языке неизменяемы.&lt;/p&gt;
&lt;p&gt;Автор высказывает следующую мысль; это цитата из оригинала:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;«Все гонки, взаимные блокировки и проблемы параллельного обновления обусловлены изменяемыми переменными. Если переменные никогда не изменяются, гонки и конфликты параллельного обновления невозможны. Если состояние блокировки неизменно, взаимная блокировка никогда не возникнет». &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Если не учитывать ограничения памяти и быстродействия процессора, неизменяемость осуществима. Но на практике эти факторы учитывать необходимо, поэтому реализовать её можно лишь в определённой мере. Для этого требуется &lt;strong&gt;изоляция изменяемости&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="可变性的隔离"&gt;Изоляция изменяемости&lt;/h3&gt;
&lt;p&gt;Распространённый способ изолировать изменяемость — разделить приложение или его внутренние службы, &lt;strong&gt;выделив изменяемые и неизменяемые компоненты&lt;/strong&gt;. Неизменяемые компоненты выполняют задачи чистыми функциями, не меняя никакого состояния. Для изменения состояния переменных они взаимодействуют с одним или несколькими нефункциональными, то есть изменяемыми, компонентами.&lt;/p&gt;
&lt;p&gt;Пример — система управления версиями GIT. Она фиксирует добавление, удаление и изменение файлов с помощью перемещения указателей. При этом сами файлы фактически не изменяются и не удаляются: остаются только добавление и поиск. Разве это не пример неизменяемости?&lt;/p&gt;
&lt;p&gt;Эта же идея используется в управлении транзакциями mysql и в транзакционной памяти.&lt;/p&gt;
&lt;h2 id="总结"&gt;Итоги&lt;/h2&gt;
&lt;p&gt;Три парадигмы тесно связаны с архитектурой ПО:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Полиморфизм позволяет пересекать границы.&lt;/li&gt;
&lt;li&gt;Функциональное программирование упорядочивает и ограничивает размещение данных и права доступа к ним.&lt;/li&gt;
&lt;li&gt;Структурное программирование служит основой реализации модулей.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Эти три парадигмы совпадают с тремя главными заботами архитектуры ПО: &lt;strong&gt;независимостью компонентов&lt;/strong&gt; (объектно-ориентированная парадигма), &lt;strong&gt;управлением данными&lt;/strong&gt; (функциональная парадигма) и функциональностью (структурная парадигма).&lt;/p&gt;
&lt;h2 id="再次思考"&gt;Ещё одно размышление&lt;/h2&gt;
&lt;p&gt;Все три парадигмы вводят для программиста новые &lt;strong&gt;ограничения&lt;/strong&gt;. Каждая ограничивает какой-либо способ написания кода; ни одна не добавляет новых возможностей. Иными словами, парадигмы говорят нам, чего делать не следует.&lt;/p&gt;
&lt;p&gt;(Конец второй статьи)&lt;/p&gt;
</description>
    <category>Архитектура ПО</category><category>Чистая архитектура</category></item>
    <item>
      <title>Перечитывая «Чистую архитектуру» (1)</title>
      <link>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%80/</link>
      <pubDate>Wed, 03 Jul 2024 23:10:20 +0800</pubDate>
      <guid>https://tommycheese.github.io/ru/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%80/</guid>
      <description>&lt;h2 id="前记"&gt;Предисловие&lt;/h2&gt;
&lt;p&gt;Почему я перечитываю эту книгу? Я уже читал «Чистую архитектуру», но тогда мне не хватало проектной практики, проб и ошибок, поэтому понимание казалось поверхностным. Во время работы мне наконец довелось столкнуться с реальными проектами и требованиями. Ещё удачнее оказалось то, что наша команда использовала именно чистую архитектуру — точнее, сочетание событийной разработки, DDD и чистой архитектуры. Подробнее о DDD я расскажу позже. Я перечитал книгу 📚 и многое из неё вынес. В этой серии я попробую изложить собственное понимание чистой архитектуры; точнее будет назвать её читательскими заметками для коллег.&lt;/p&gt;
&lt;p&gt;Книгу можно найти здесь: &lt;a href="https://weread.qq.com/web/bookDetail/480322f072021a3248038c8"&gt;Чистая архитектура — Роберт К. Мартин — WeChat Read (qq.com)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Серия будет следовать структуре глав книги и постепенно углубляться в тему вместе с автором. Поскольку это повторное чтение, я неизбежно буду обращаться и к другим частям книги. Если встретится что-то непонятное или незнакомое, скорее всего, эта тема рассматривается в следующих главах.&lt;/p&gt;
&lt;p&gt;Начнём.&lt;/p&gt;
&lt;h2 id="设计与架构的含义"&gt;Что означают дизайн и архитектура&lt;/h2&gt;
&lt;p&gt;По существу, между дизайном и архитектурой нет различия: низкоуровневые детали проектирования и высокоуровневая архитектура вместе определяют программную систему.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Конечная цель архитектуры ПО — удовлетворять потребности в создании и сопровождении системы с минимальными трудозатратами&lt;/strong&gt;. Поэтому затраты позволяют оценивать качество архитектурного решения.&lt;/p&gt;
&lt;p&gt;Система, поспешно созданная без продуманного проектирования, может превратиться в &lt;strong&gt;запутанный клубок&lt;/strong&gt;. При её развитии качеством кода и улучшением структуры долгое время пренебрегают. Внимательное проектирование архитектуры в определённой мере помогает избежать такого состояния и тем самым снизить затраты.&lt;/p&gt;
&lt;p&gt;У разработки ПО есть две важные особенности:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Чтобы двигаться быстро, сначала нужно двигаться устойчиво.&lt;/li&gt;
&lt;li&gt;Излишняя самоуверенность приводит лишь к тому, что переработка архитектуры попадает в ту же ловушку, что и исходный проект.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="两个价值维度"&gt;Два измерения ценности&lt;/h2&gt;
&lt;p&gt;У программной системы есть два вида ценности:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Поведенческая ценность: заставить машину работать заданным образом и приносить или увеличивать прибыль пользователей системы.&lt;/li&gt;
&lt;li&gt;Архитектурная ценность: ПО должно быть достаточно гибким, а стоимость его изменения — зависеть от масштаба требований, а не от их формы.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Как понимать масштаб и форму требований? На мой взгляд, масштаб — это приблизительный объём требований и соответствующая &lt;strong&gt;предметная область&lt;/strong&gt;, а форма — конкретные детали этих требований.&lt;/p&gt;
&lt;p&gt;Какое измерение ценности важнее? Автор считает архитектурную ценность более важной, чем поведенческую, потому что:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Если программа работает, но её нельзя изменить, при изменении требований она перестанет работать должным образом. Исправить её для новых условий также не получится. В результате её ценность станет нулевой.&lt;/li&gt;
&lt;li&gt;Если программа пока не работает правильно, но её легко изменить, исправить её и затем приспосабливать к новым требованиям должно быть несложно. Такая программа будет продолжать создавать ценность.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Общая ошибка бизнеса и разработчиков — не отделять действительно срочные и важные функции от срочных, но неважных. В результате важные архитектурные вопросы уступают место неважным поведенческим функциям.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Именно разработчики отвечают за баланс между важностью архитектуры системы и срочностью функциональных требований&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Примечание: подобные проблемы возникают и между командами разработчиков. Например, некоторые фронтенд-команды считают, что бэкенду достаточно предоставить набор простых интерфейсов, не осознавая важности качественного проектирования.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="为好的软件架构持续斗争"&gt;Продолжать бороться за хорошую архитектуру ✊&lt;/h3&gt;
&lt;p&gt;Если пренебрегать архитектурной ценностью, сопровождать систему станет всё труднее, пока однажды её уже нельзя будет изменить. Такое состояние означает, что команда разработки недостаточно отстаивала свою позицию перед заказчиками требований и не выполнила собственные обязанности.&lt;/p&gt;
</description>
    <category>Архитектура ПО</category><category>Чистая архитектура</category></item>
    </channel>
</rss>
