HTML Diff
1 added 1 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>При разработке приложений с богатой предметной областью во весь рост встаёт вопрос о том, как правильно организовать код приложения, а если смотреть шире, то какую выбрать архитектуру. О том, какие варианты может предложить вам индустрия мы сейчас и поговорим, но перед тем, как я расскажу про существующие подходы, важно запомнить несколько вещей.</p>
1 <p>При разработке приложений с богатой предметной областью во весь рост встаёт вопрос о том, как правильно организовать код приложения, а если смотреть шире, то какую выбрать архитектуру. О том, какие варианты может предложить вам индустрия мы сейчас и поговорим, но перед тем, как я расскажу про существующие подходы, важно запомнить несколько вещей.</p>
2 <p>Не существует единственного верного подхода при организации вашего приложения. Известные подходы - всего лишь видение конкретных людей для конкретных ситуаций и конкретных стеков (язык + инструментарий). Любая хорошая архитектура базируется на фундаментальных законах и принципах. Большую часть из них вы уже знаете:</p>
2 <p>Не существует единственного верного подхода при организации вашего приложения. Известные подходы - всего лишь видение конкретных людей для конкретных ситуаций и конкретных стеков (язык + инструментарий). Любая хорошая архитектура базируется на фундаментальных законах и принципах. Большую часть из них вы уже знаете:</p>
3 <ul><li>Изоляция побочных эффектов;</li>
3 <ul><li>Изоляция побочных эффектов;</li>
4 <li>Хорошая абстракция (абстракция данных, композиция, разделение);</li>
4 <li>Хорошая абстракция (абстракция данных, композиция, разделение);</li>
5 <li>Сильные барьеры (между абстракциями);</li>
5 <li>Сильные барьеры (между абстракциями);</li>
6 <li>Слабые связи (возможность замены/независимого развития).</li>
6 <li>Слабые связи (возможность замены/независимого развития).</li>
7 </ul><p>Из популярного можно выделить следующие словосочетания:</p>
7 </ul><p>Из популярного можно выделить следующие словосочетания:</p>
8 <ul><li>The Clean Architecture</li>
8 <ul><li>The Clean Architecture</li>
9 <li>Onion Architecture</li>
9 <li>Onion Architecture</li>
10 <li>Hexagonal Architecture</li>
10 <li>Hexagonal Architecture</li>
11 </ul><p>Все эти архитектуры сводятся так или иначе к тому, что наше приложение представляет из себя набор слоёв (тех самых абстракций), которые связаны друг с другом определённым образом и отвечают за определённые аспекты системы. Лучше всего начать анализ с красивой картинки:</p>
11 </ul><p>Все эти архитектуры сводятся так или иначе к тому, что наше приложение представляет из себя набор слоёв (тех самых абстракций), которые связаны друг с другом определённым образом и отвечают за определённые аспекты системы. Лучше всего начать анализ с красивой картинки:</p>
12 <p>Начать стоит с того, что:<em>Фреймворк - это не ваше приложение</em>. Остановитесь на секундочку и хорошо обдумайте фразу. В типичных веб-приложениях фреймворк определяет вообще всё. Приложение на 100% переплетается с ним и становится его частью. Программист начинает мыслить в рамках возможностей фреймворка и его ограничений, и в его голове появляются несуществующие причинно-следственные связи.</p>
12 <p>Начать стоит с того, что:<em>Фреймворк - это не ваше приложение</em>. Остановитесь на секундочку и хорошо обдумайте фразу. В типичных веб-приложениях фреймворк определяет вообще всё. Приложение на 100% переплетается с ним и становится его частью. Программист начинает мыслить в рамках возможностей фреймворка и его ограничений, и в его голове появляются несуществующие причинно-следственные связи.</p>
13 <p>Да, конечно, сложно (и не нужно) делать абсолютную изоляцию, но и всегда нужно проводить чёткую грань между приложением и тем фреймворком, который вы используете.</p>
13 <p>Да, конечно, сложно (и не нужно) делать абсолютную изоляцию, но и всегда нужно проводить чёткую грань между приложением и тем фреймворком, который вы используете.</p>
14 <h2>Домен</h2>
14 <h2>Домен</h2>
15 <p>Первым и базовым слоем в приложении является Домен. Это реализация вашей модели предметной области. Чистая бизнес-логика без намёка на инфраструктуру.</p>
15 <p>Первым и базовым слоем в приложении является Домен. Это реализация вашей модели предметной области. Чистая бизнес-логика без намёка на инфраструктуру.</p>
16 <p>Вот что обычно характеризует домен:</p>
16 <p>Вот что обычно характеризует домен:</p>
17 <ul><li>Чистый код (pure)</li>
17 <ul><li>Чистый код (pure)</li>
18 <li>Plain Old X Object (POXO)</li>
18 <li>Plain Old X Object (POXO)</li>
19 <li>Бизнес-логика</li>
19 <li>Бизнес-логика</li>
20 <li>Валидация</li>
20 <li>Валидация</li>
21 </ul><p>POXO - это обобщённое название, которое в каждом конкретном языке приобретает своё собственное имя. В Java POJO, в Ruby PORO, и так далее. Этой аббревиатурой описывают объекты, которые построены исключительно на возможностях самого языка, без дополнительных абстракций. Так подчёркивается, что домен не использует внешних библиотек, которые влияют на его организацию. Не надо фанатично относиться к этой идее. В некоторых языках сформировались свои правила, и они идут вразрез с общими концепциями.</p>
21 </ul><p>POXO - это обобщённое название, которое в каждом конкретном языке приобретает своё собственное имя. В Java POJO, в Ruby PORO, и так далее. Этой аббревиатурой описывают объекты, которые построены исключительно на возможностях самого языка, без дополнительных абстракций. Так подчёркивается, что домен не использует внешних библиотек, которые влияют на его организацию. Не надо фанатично относиться к этой идее. В некоторых языках сформировались свои правила, и они идут вразрез с общими концепциями.</p>
22 <h2>Персистентность</h2>
22 <h2>Персистентность</h2>
23 <p>Реализовать логику только половина дела. В конце концов нужно сохранить наши изменения. Казалось бы, что эта часть должна быть самой простой, но нет. Состояние, его изменение и поддержка целостности настолько сложная история, что придуманы огромные и сложные фреймворки, называемые ORM. Обычно они построены вокруг двух самых распространённых паттернов:</p>
23 <p>Реализовать логику только половина дела. В конце концов нужно сохранить наши изменения. Казалось бы, что эта часть должна быть самой простой, но нет. Состояние, его изменение и поддержка целостности настолько сложная история, что придуманы огромные и сложные фреймворки, называемые ORM. Обычно они построены вокруг двух самых распространённых паттернов:</p>
24 <ul><li>ActiveRecord</li>
24 <ul><li>ActiveRecord</li>
25 <li>DataMapper</li>
25 <li>DataMapper</li>
26 </ul><p>В этом курсе мы их не рассматриваем, но в реальной жизни вам придётся с ними столкнуться.</p>
26 </ul><p>В этом курсе мы их не рассматриваем, но в реальной жизни вам придётся с ними столкнуться.</p>
27 <h2>Репозиторий</h2>
27 <h2>Репозиторий</h2>
28 - <p>Репозиторий - это хранилище однотипных сущностей. Позволяет как делать выборки, так и сохранять сущности внутри себя. Для простот в нашем приложении репозитории будут хранить все данные в памяти.</p>
28 + <p>Репозиторий - это хранилище однотипных сущностей. Позволяет как делать выборки, так и сохранять сущности внутри себя. Для простоты в нашем приложении репозитории будут хранить все данные в памяти.</p>
29 <h2>Инфраструктура</h2>
29 <h2>Инфраструктура</h2>
30 <p>Именно в эту категорию попадает фреймворк, UI и вообще любая прикладная история. На картинке этот слой находится на самой внешней стороне. Из него происходит отправка электронных писем, смс, и выполняется так называемая логика приложения. Например, перенаправление на определённую страницу после создания какой-то сущности.</p>
30 <p>Именно в эту категорию попадает фреймворк, UI и вообще любая прикладная история. На картинке этот слой находится на самой внешней стороне. Из него происходит отправка электронных писем, смс, и выполняется так называемая логика приложения. Например, перенаправление на определённую страницу после создания какой-то сущности.</p>
31 <h2>Сервисы</h2>
31 <h2>Сервисы</h2>
32 <p>Помните диаграмму вариантов использования? Вот именно эти варианты и являются единственным способом изменения состояния вашего приложения. Ваш домен обрастает слоем так называемых сервисов. Каждый сервис представляет собой набор функций, имитирующих бизнес-сценарии, например, "добавить в друзья", "поставить лайк".</p>
32 <p>Помните диаграмму вариантов использования? Вот именно эти варианты и являются единственным способом изменения состояния вашего приложения. Ваш домен обрастает слоем так называемых сервисов. Каждый сервис представляет собой набор функций, имитирующих бизнес-сценарии, например, "добавить в друзья", "поставить лайк".</p>
33 <p>Инфраструктурный слой является главным пользователем вашего слоя сервисов. Сервисы могут вызываться в ui, в контроллерах, в асинхронных обработчиках. Слой сервисов настолько важен сам по себе, что Мартин Фаулер описывает его как шаблон проектирования<a>Service Layer</a></p>
33 <p>Инфраструктурный слой является главным пользователем вашего слоя сервисов. Сервисы могут вызываться в ui, в контроллерах, в асинхронных обработчиках. Слой сервисов настолько важен сам по себе, что Мартин Фаулер описывает его как шаблон проектирования<a>Service Layer</a></p>
34 <p>При проектировании сервисов нужно придерживаться некоторых правил, которые позволяют абстракции не протекать и максимально поддерживать чистоту.</p>
34 <p>При проектировании сервисов нужно придерживаться некоторых правил, которые позволяют абстракции не протекать и максимально поддерживать чистоту.</p>
35 <p>Входными данными в функции сервиса не могут быть сущности предметной области. Причина такого правила очень проста. Сервисы - слой поверх предметной области, он инкапсулирует в себе все сценарии. Если сущности окажутся снаружи, то логика становится размазанной между слоями (потекла абстракция), пропадает изоляция. Но так сделать не всегда возможно. Иногда это связано с устройством конкретных фреймворков, которые не дают нормально абстрагировать предметную область от инфраструктуры. В такой ситуации не стоит бороться насмерть за концептуальную чистоту, идите на компромиссы.</p>
35 <p>Входными данными в функции сервиса не могут быть сущности предметной области. Причина такого правила очень проста. Сервисы - слой поверх предметной области, он инкапсулирует в себе все сценарии. Если сущности окажутся снаружи, то логика становится размазанной между слоями (потекла абстракция), пропадает изоляция. Но так сделать не всегда возможно. Иногда это связано с устройством конкретных фреймворков, которые не дают нормально абстрагировать предметную область от инфраструктуры. В такой ситуации не стоит бороться насмерть за концептуальную чистоту, идите на компромиссы.</p>
36 <p>То же самое касается выходных данных. В теории, отдавать наружу сущности нельзя по той же причине, по которой нельзя ими оперировать вне сервисов. Так как после возврата крайне просто начать ей оперировать, что сразу повлечёт за собой размазывание логики по слоям. Вместо сущности, как правило, отдают специальный "Data Transfer Object". В отличие от сущности он не содержит поведения и используется исключительно как контейнер для чтения.</p>
36 <p>То же самое касается выходных данных. В теории, отдавать наружу сущности нельзя по той же причине, по которой нельзя ими оперировать вне сервисов. Так как после возврата крайне просто начать ей оперировать, что сразу повлечёт за собой размазывание логики по слоям. Вместо сущности, как правило, отдают специальный "Data Transfer Object". В отличие от сущности он не содержит поведения и используется исключительно как контейнер для чтения.</p>
37 <blockquote><p>DTO - Используется для передачи данных между подсистемами приложения. DTO, в отличие от business object, не должен содержать какого-либо поведения</p>
37 <blockquote><p>DTO - Используется для передачи данных между подсистемами приложения. DTO, в отличие от business object, не должен содержать какого-либо поведения</p>
38 </blockquote><ul><li>Неизменяемый</li>
38 </blockquote><ul><li>Неизменяемый</li>
39 <li>Просто данные</li>
39 <li>Просто данные</li>
40 </ul><p>Опять же, чтобы не усложнять, в тех системах, где нет готовых механизмов для трансляции, возвращают и сущности, но на уровне соглашений используют их исключительно как DTO.</p>
40 </ul><p>Опять же, чтобы не усложнять, в тех системах, где нет готовых механизмов для трансляции, возвращают и сущности, но на уровне соглашений используют их исключительно как DTO.</p>
41 <p>И последнее по списку, но не последнее по важности, не вызывайте сервисы из сервисов. Если появляется общий код, то выносите общую функциональность, но не позволяйте самому сервису начинать мешаться с доменом. Последнее означает то, что если сервисы начинают использовать внутри себя сервисы (тот же или другие), то с большой вероятностью происходит нарушение принципа одного уровня абстракции. Сервисы - слой<em>поверх</em>домена, а это значит, что на одном уровне нельзя использовать и домен, и сервис.</p>
41 <p>И последнее по списку, но не последнее по важности, не вызывайте сервисы из сервисов. Если появляется общий код, то выносите общую функциональность, но не позволяйте самому сервису начинать мешаться с доменом. Последнее означает то, что если сервисы начинают использовать внутри себя сервисы (тот же или другие), то с большой вероятностью происходит нарушение принципа одного уровня абстракции. Сервисы - слой<em>поверх</em>домена, а это значит, что на одном уровне нельзя использовать и домен, и сервис.</p>