Луковая архитектура. Часть 1

http://jeffreypalermo.com/blog/the-onion-architecture-part-1/
  • Перевод
Ранее, я несколько раз упоминал об особом типе архитектуры, которую я называю луковой (onion architecture). Эта архитектура идеально подходит для приложений с длительным жизненным циклом и сложной бизнес логикой. Я считаю, что ее использование в подобных проектах приводит к превосходным результатам, в следствии изначально заложенного в архитектуру акцента на разделение различных аспектов приложения. В луковой архитектуре уделяется особое внимание к описанию поведения системы в терминах контрактного программирования и выносу инфраструктурного кода во внешние модули. На диаграмме ниже, вы можете видеть графическое представление традиционной “многослойной” архитектуры. Это очень популярный подход, использующийся в различных вариациях во множестве виденных мной проектов.

image

Каждый следующий слой тесно связан с соседними и зависит от используемых инфраструктурных модулей и сервисов. Основной недостаток многослойной архитектуры в порождаемой ею высокой связанности. Как мы знаем, худшим проявлением высокой связанности является проникновение элементов UI и бизнес логики в слой доступа к данным. В проектах, использующих многослойную архитектуру, UI остается тесно связанным со слоем доступа к данным. Переходящая зависимость остается зависимостью. Слой UI не может функционировать отдельно от слоя бизнес логики, также как и слой бизнес логики не способен функционировать отдельно от слоя доступа к данным. Я намеренно игнорирую вопросы связанные с инфраструктурой, поскольку инфраструктура склонна меняться от проекта к проекту. Слой доступа к данным постоянно меняется. Исторически, подходы к работе с данными обновляются по крайней мере раз в три года. Поэтому, в приложениях с длительным жизненным циклом, нужно быть готовым к тому, что через три года придется вносить правки в слой доступа к данным. Если высокая связанность мешает заменять устаревшие модули независимо друг от друга, вся система может перейти в состояние упадка и команде ничего не останется, кроме как полностью ее переписать.

Я предлагаю новый подход к построению приложений. Если быть честным, он не совсем новый, но я предлагаю дать ему имя и закрепить в одном ряду с другими архитектурными паттернами. Паттерны полезны, поскольку предоставляют разработчикам общедоступный словарь для упрощения коммуникации. У луковой архитектуры множество аспектов, и нам необходимо дать ей название для более эффективной коммуникации. На диаграмме ниже изображена луковая архитектура.

image

Ее ключевая особенность в управлении связанностью. Фундаментальное правило — любой модуль приложения может зависеть от более близких к центру луковицы модулей, но не может зависеть от более дальних. Иными словами, любая связанность должна быть направлена к центру луковицы.
Луковая архитектура тяготеет к ООП и ставит объекты превыше любых других аспектов приложения. В самом центре находится доменная модель приложения. Вокруг доменной модели находятся другие слои с дополнительной бизнес логикой. Количество слоев в приложении может меняться, но доменная модель должна всегда оставаться в самом центре и, поскольку любая связанность направлена к центру, доменная модель связана только сама с собой. Первый слой вокруг доменной модели, это то место где обычно размещаются интерфейсы репозиториев, обеспечивающих сохранение и получение объектов. Необходимо отметить, что сам процесс сохранения объектов находится вне ядра приложения, поскольку этот процесс обычно включает в себя взаимодействие с сервером баз данных, а в центре ядра находятся только интерфейсы. На внешнем радиусе приложения находятся UI, инфраструктура и тесты. Внешний слой зарезервирован за элементами которые меняются часто. Эти элементы должны быть изолированы от ядра приложения. На самом краю, находятся классы реализующие интерфейсы репозиториев. Эти классы тесно связаны с конкретной технологией доступа к данным и именно поэтому должны быть вынесены за пределы ядра приложения.
Луковая архитектура основана на принципе инверсии зависимостей. Приложению для работы необходимы реализации расположенных в ядре интерфейсов, а поскольку конкретные реализации находятся на внешнем радиусе приложения, приложению также необходим и механизм для инжектирования зависимостей.

База данных распологается не в центре приложения. Перенос механизма по работе с базой данных из ядра в отдельный модуль, может оказаться серьезным шагом для людей, мыслящих о приложениях исключительно в контексте взаимодействия с источниками данных. Приложения конечно же могут использовать базы данных для сохранения объектов, но только через прослойку инфраструктурного кода, реализующего интерфейсы используемые в ядре приложения. Разрыв связанности между приложением и базой данных, файловой системой и т.д. уменьшает стоимость поддержки приложения на всем протяжении его жизненного цикла.
Alistair Cockburn в своем блоге как-то писал на тему Гексагональной архитектуры (Hexagonal architecture). Гексагональная и луковая архитектура основаны на общих принципах: перенос инфраструктуры во внешние модули и написание адаптеров к ним. Я планирую написать больше на тему использованя луковой архитектуры при построении приложений корпоративного уровня. Я по-прежнему остаюсь в этой среде и продолжу рассуждения на тему луковой архитектуры в этом контексте.

От переводчика. Этот пост первый из цикла постов Джеффри Палермо, посвященных луковой архитектуре. Если у хабрасообщества есть интерес, я могу сделать перевод и остальных частей.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Продолжить перевод

Поделиться публикацией

Комментарии 28

    +9
    Все же из топика не совсем понятно, чем же луковая архитектура отличается от многослойной. На первый взгляд мы имеем те же слои, на границах слоев интерфейсы и реализации интерфейсов, которые (в теории) можно легко заменять. В правильно реализованной многослойной архитектуре зависимость также является однонаправленной, сверху вниз. Разъясните, пожалуйста, чем же по сути эти два подхода отличаются?

    PS Может стоит сделать картинки больше? Прочитать написанный на них текст довольно тяжело.
      +3
      На самом деле, разницу между луковой и слоистой архитектурами на картинках не изобразить. Все дело в динамике развития приложения.

      В слоистой архитектуре мы начинаем разработку с базы, например определяем таблицу, где непосредственно лежит какая-то сущность, поверх кладем слой «абстрагирования» (и это совсем не абстрагирование, см. ниже), еще выше уровень логики, и потом — UI.

      В луковой мы сначала закладываем ядро — сущности как они есть, а потом уже придумываем способы их хранения.

      Таким образом, в слоистой архитектуре доменная модель завязана на модель хранения, в луковой — хранение зависит от модели.
        +1
        Спасибо, теперь мне понятна основная идея.
          +2
          не кидайтесь помидорами, но в чём плюс предлагаемой архитектуры?
            +2
            В том, что в основе всего лежит модель, а не инфраструктура, предназначенная для того, чтобы эту модель поддерживать.

            Модель, определяемая бизнес-потребностями, а не парадигмой выбранной БД.

            Польза полной независимости модели от инфраструктуры такая же, как и польза от независимости модели от UI.
            –3
            В слоистой архитектуре мы начинаем разработку с базы, например определяем таблицу, где непосредственно лежит какая-то сущность, поверх кладем слой «абстрагирования» (и это совсем не абстрагирование, см. ниже), еще выше уровень логики, и потом — UI.

            Это кто «мы»? Я вот привык считать, что у системы, в разработке которой я принимаю участие, обычная n-layer архитектура, но там разработка новой функциональности всегда начинается от домена.
              +2
              Это кто «мы»? Я вот привык считать

              Простите великодушно.
              +4
              А вам не кажется, что такой подход попахивает идеализмом и не практичностью? Мне опыт подсказывает, что моделировать домен лучше с оглядкой на бд. Если есть проблемы, то стоит либо пересмотреть модель, либо выбрать другую бд, например нереляционную, которая будет лучше подходить для данной задачи. А проектирование домена вообще без оглядки на бд приведет в будущем к кривизне в работе с данными. Конечно такой подход имеет смысл, если одно из требований — легкая сменяемость бд. Для типичного приложения бд скорее самая несменяемая часть :)
                +1
                Конечно, это в значительной степени идеализм. В реальном мире всегда будет какой-то компромисс.

                Для типичного приложения бд скорее самая несменяемая часть


                Вот в этом и проблема. Каркасом приложения является то, что призвано быть второстепенным элементом. И когда начинаются нестыковки, приходится подгонять модель под сервис.

                Это как если бы ассортимент магазина подгоняли под конкретную службу доставки, например, Почту России :) На практике так и делается, но в идеале надо иметь ассортимент, приносящий максимальную прибыль, а уже для него подбирать подходящую службу доставки. И менять ее при смене ассортимента.

                Реальный и частый пример того, что трудно меняется в проекте, созданном от БД и вообще с логикой в БД — смена отношений между сущностями (привет, эджайл :). Например, у каждого клиента был один телефон, а должно стать несколько. А потом — один телефон у нескольких клиентов.
                  –1
                  (давайте не путать проекты, созданные от хранилища, с проектами, у которых в БД зашита логика — это два совершенно разных класса)

                  А чем в реальности отличается изменение «у одного клиента был один телефон, должно стать несколько» в проектах «построенных от БД» от проектов «построенных от домена»? Кроме порядка внесения изменений?
                    0
                    Количеством изменений и их деструктивностью. А именно необходимостью переделывать схему БД и все, что от нее зависит, т.е. все. Плюс миграция данных, и без остановки приложения.
                      0
                      Вы не могли бы на примерах показать? «Вот классическое n-layer приложение, в нем нужно поменять а, б, ц»; «вот „луковое приложение“, в нем нужно поменять а, б, ц».

                      А то по вашему тексту выходит, что в проекте, «созданном не от БД», схему БД менять не надо… а данные-то новые где храниться будут?
                        0
                        Луковость архитектуры совершенно не влияет на переделку схемы БД и миграцию данных.
                      +1
                      За 7 лет было аж два случая, когда приходилось переделывать связи 1-n на n-n. Обычно предметная область предельно стабильна, правильная модель предметной области переживает несколько поколений программ, работающих с ней. Вот только далеко не все правильно строить предметную область в БД, от этого и проблемы.
                  0
                  +много к PS. На hidpi экранах, текст на этих картинках вдобавок еще и сильно мылится, были бы они побольше, было бы удобнее.
                  +2
                  При чтении возникло дикое чувство дежавю, сходил по ссылке на оригинал и понял, что читал эту статью в 2008 году ;)
                    +3
                    Перенос механизма по работе с базой данных из ядра в отдельный модуль, может оказаться серьезным шагом для людей, мыслящих о приложениях исключительно в контексте взаимодействия с источниками данных.

                    Я из таких людей. Мне кажется такой перенос приведет к неоптимальной работе СУБД. Сотни запросов на одно действие пользователя, неоптимальные запросы и структуры хранения и т.д.
                    Не?
                      0
                      Насколько я понимаю, не обязательно. Зависит от технологии доступа к БД, которую вы используете. Например, если речь об EF или LINQ to SQL, то мы получаем в той или иной степени всё, что вы упомянули, да и вообще все проблемы, связанные с ORM. Если вы отделились репозиторием, а под ним старый добрый ADO.NET, то с запросами должно быть всё нормально. Или я неправильно понял контекст дискуссии?
                        0
                        Это зависит от того, как именно вы реализуете репозиторий, и какую функциональность он будет предоставлять.
                        0
                        Если этот «отдельный модуль» отдает IQueryable, а в контроллерах сделано человеческое кеширование, то проблем не будет. Более того это получится идеальная архитектура, которая сочетает минимальность кода, высокое быстродействие и легкость поддержки.

                        Кстати таких «отдельных модулей» уже много существует — ORM называются.
                        0
                        2008 год, тогда это было, кхм, инновацией что ли… :)

                        Вот хорошая статья «посвежее», в ней взгляд на layered/onion/hexagonal/ports&adapters architectures с точки зрения DI.
                          +1
                          Что-то впервые слышу про термин Onion architecture и честно говоря совсем не понимаю чем он отличается от уже давно известного подхода DDD. В одной из своих статеек рисовал такую схемку DDD:
                          image
                          По-моему, один в один, нет?
                            0
                            Кмк, связь между ними такая — DDD приводит к Onion Architecture :)

                            Вообще, конечно, странно, что автор не упомянул про DDD.
                              0
                              >>Я предлагаю новый подход к построению приложений.
                              Интересно, автор (не переводчик) вообще читал Эванса\Фаулера?
                                +1
                                Да. Из третьей части:

                                I must stress again: I am not claiming any breakthroughs in technology or technique. I have learned from other industry thought leaders like Martin Fowler, Ward Cunningham, Kent Beck, Michael Feathers and others
                            0
                            Без примеров кода можно бесконечно рисовать прямоугольники, стрелочки и кружочки.
                            У автора с примерами как раз очень туго. На 4 статьи 5 строк кода всего, и то, который можно нарисовать в любой архитектуре.
                              +2
                              Что-то лыжи не едут. Переназвали DAL в Infrastructure и больше разницы в соединениях на картинках нет. В остальном они топологически идентичны.
                                +1
                                В том, чтобы согнуть слои в окружность, нет ничего ни нового, ни меняющего идеологию дизайна.
                                И уж тем более вынесение из основной картинки базы данных вообще размывает всю идею. Да и почему только базу данных? А если в доменной модели используется численная/финансовая библиотека? Она формально уж точно ни от чего не зависит. Почему бы не написать стандартный интерфейс и вынести ее тоже за пределы круга? А системная библиотека/система для GUI? А библиотека для коммуникации? А библиотека/подсистема для картографии? А библиотека… (подставьте еще с десяток-другой совершенно ни от чего не зависящих библиотек/подсистем). То есть, будем рисовать кучу прямоугольников вокруг круга? Да бред же. Если есть правило «зависимость только к центру», то оно и должно работать.

                                И совершенно не вижу, как замыкание слоев в кружок может повлиять на очередность дизайна: ядро или БД. Это вообще несвязанные вещи. Кстати, даже когда сначала разрабатывается БД, всегда сначала в голове формируется некая доменная модель, иначе просто нечего будет отображать в таблицах и записях. Просто не каждый раз люди записывают модель где-то в полуформальной нотации (хотя и зря).

                                В общем, тог такой: минимазия зависимости между подсистемами через интерфейсы? Отлично! Инверсия зависимости? Да ради бога! Сначала дизайн доменной модели? Прекрасно! Только при чем при этом лук, кружочки и прочие отдельно висящие фигуры? Непонятно.

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое