Для начала хотелось бы затронуть так называемую «микросервисную архитектуру», которая стала довольно популярной. Однако называть её архитектурой не совсем корректно, как заметил Роберт Мартин в своей книге «Чистая архитектура». Микросервис — это один из способов представления компонента общего приложения. Но архитектура, взаимодействие между компонентами, при этом может остаться такой же. Под компонентом понимается наименьшая единица развёртывания — пакет, библиотека или отдельное приложение, сервис, микросервис. В книге критикуется данная «микросервисная архитектура», а точнее, неправильное и неуместное её применение, которое есть во многих случаях и влечёт за собой негативные или крайне негативные последствия для проекта, и происходит «благодаря» низкой компетентности разработчиков или управляющих, которые хотят найти несуществующую здесь «серебряную пулю».

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

Примеры будут для PHP и Symfony, но и для других языков и систем программирования многое может быть похожим. Примеры абстрактные, и в разных приложения может всё отличаться. В ранних версиях Symfony была рекомендация делить приложение на бандлы-плагины. Но начиная с версии 3.4 рекомендовано делить приложение через пространства имён. Это практичнее. Структура проекта будет стандартной. В App\Service\, будет находиться основная логика приложения, бизнес-логика.

Пусть, нужно сделать возможность оформления заказа. Для этого можно создать App\Service\Order\Checkout\OrderCheckoutService. Здесь будет общая логика оформления заказа. Входные данные можно принимать через параметры функции, но часто удобнее передавать структуру с данными, DTO. Могут понадобиться App\Service\Order\Checkout\InputDto, возможно, и InputDtoInterface. Он может содержать массив, коллекцию, из OrderInputDto, если заказов сразу несколько, например. Похожим образом обстоит дело и с выходными данными.

Если в OrderCheckoutService много разнообразной логики, можно выносить её в другие классы с разными названиями. Чтобы следовать принципу единой ответственности можно разделить его на несколько классов, например, OrderCheckoutAcmeFeature1Service, OrderCheckoutAcmeFeature2Service и так далее.

Ещё понадобится хранить данные заказов в БД, поэтому создадим сущность по зеркальному к сервису пути App\Entity\Order\Order. Соответственно будет создан и репозиторий App\Repository\Order\OrderRepository. Имеет смыл делать логику независимой от Entity и Repository. Проще всего это сделать через интерфейсы в App\Service\Order\Checkout\: OrderInterface (или CheckoutOrderInterface) и OrderRepositoryInterface (или CheckoutOrderRepositoryInterface). OrderInterface должен содержать только геттеры и сеттеры данных, необходимых для оформления заказа. OrderRepositoryInterface должен содержать только нужные запросы к БД и логику связанную с построением запросов. Сущность и репозиторий реализуют эти интерфейсы соответственно. Можно вместо интерфейсов использовать и промежуточную структуру, DTO, но такой способ будет более объёмным в реализации и иметь некоторые другие минусы.

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

Ещё понадобится фабрика или creator для создания экземпляра конкретной сущности. Для этого создадим интерфейс App\Service\Order\OrderFactoryInterface (или OrderCreatorInterface, как кому нравится). Обязанность создания можно возложить, например, на класс App\Factory\Order\OrderFactory, реализующий этот интерфейс и другие подобные интерфейсы, если появятся. Фабрика может содержать единственный метод create. Фабрику можно положить и в App\Service\Order\OrderFactory, но нужно помнить, что она относится к инфраструктуре, в отличие от интерфейса.

Надо находить наиболее важную логику приложения, так называемую бизнес-логику, и выделять её в отдельные классы, делать более независимой от других частей приложения, библиотек. Для этого понадобится делать обёртки, адаптеры, посредники, применять инверсию зависимости.

Если интерфейс реализован только в одном месте, сервис-контейнер Symfony, сам подставит соответствующую реализацию в конструктор сервиса. Если реализаций несколько, то нужно уточнить конкретную в файле конфигурации контейнера. У контейнера есть много возможностей, которые полезно знать.

И так, у нас есть OrderCheckoutService, содержащий бизнес-логику оформления заказа. Он может вызывать напрямую другие сервисы, выше уровнем. Или через инверсию зависимости другие, более конкретные реализации, менее важные детали. Например, может вызвать App\Service\Order\Delivery\OrderDeliveryService, содержащий логику связанную с доставкой заказа. И эта логика может относиться не только к оформлению. В таком случае мы подразумеваем, что OrderDeliveryService — это более важная логика, более высокого уровня, чем OrderCheckoutService, бизнес-логика оформления заказа зависит от бизнес-логики доставки заказа. OrderDeliveryService, в свою очередь, может вызвать уже конкретный расчёт через сторонний API, например, с помощью App\Service\Delivery\DeliveryCalculatorInterface с реализацией App\Service\Delivery\DeliveryCalculator. Здесь калькулятор находится в App\Service\Delivery потому, что он содержит логику доставки, которая может быть использована отдельно от Order. Интерфейс использован, чтобы не было зависимости от инфраструктурного кода. Если есть какая-то общая бизнес-логика, которая относится к доставке Order и к доставке чего-то ещё не являющегося Order, что сложно представить, то можно поместить её, например, в App\Service\Delivery\DeliveryService.

Здесь DeliveryCalculator зависит от бизнес-логики, он реализует её интерфейс. А если понадобится сделать калькулятор независимым от данного приложения и, возможно, вынести его в отдельный пакет и использовать в других приложениях, можно выделить его логику и поместить, например, в App\Util\Delivery\DeliveryCalculator. А в калькуляторе приложения уже просто вызывать его. Тогда класс App\Service\Delivery\DeliveryCalculator становится конкретной реализацией адаптера. К нему может добавиться ещё некоторая логика, тогда его уже можно назвать декоратором. А если он будет обращаться к нескольким классам, то это уже посредник. Но смысл один и тот же. Не помешает так же создать обёртки над другими сторонними компонентами, например, над EntityManager. Если есть необходимость в ещё более чётком отделении бизнес-логики, можно весь подобный инфраструктурный код, соединяющий инфраструктуру с бизнес-логикой, перенести из App\Service\, например, в App\ServiceBridge\ c зеркальной к App\Service\ структурой. И в App\Service\ тогда останется чистая бизнес-логика.

Ещё может быть логика оформления заказа пользователем на сайте и администратором в панели управления, она может в чём-то совпадать, в чём-то отличаться. Тогда можно создать App\Service\Order\Checkout\UserOrderCheckoutService и App\Service\Order\Checkout\AdminOrderCheckoutService со своей логикой. Они могут обращаться к общему OrderCheckoutService, который будет более важен, выше уровнем в таком случае. А могут быть и самостоятельными, тогда их следует перенести на уровень выше, например, в App\Service\Order\UserCheckout\. Логику, связанную с взаимодействием с пользователем, со страницей оформления заказа можно вынести в App\Service\Order\UserCheckout\UserOrderCheckoutInteractor, который будет обращаться к UserOrderCheckoutService. И уже в контроллере вызывать сервис/интерактор. Так же и с AdminOrderCheckoutService.

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

Что касается сокрытия классов, в PHP нет такой возможности. Есть какие-то сторонние решения. Но в общем случае делать этого не нужно, так как должно быть понятным из документации или из примеров, как использовать какой-либо компонент, библиотеку. Если этого нет, то это уже проблема другого уровня, профессионального, организационного, и методом сокрытия классов её не решить. Зато сокрытие классов может создать проблемы в использовании пакета. Можно помечать некоторые классы и функции, которые нежелательно переопределять, аннотацией internal, этого должно быть достаточно. Также, при создании пакета не следует злоупотреблять final, и private, а использовать их только там, где это действительно надо, если есть другое, более правильное решение. Если private используется в коде самого приложения, поправить его будет легко, а вот в стороннем пакете это сделать уже сложнее.

В директории config можно создать поддиректорию и подключить всё её содержимое в основном файле конфигурации фреймворка services.yaml. Тогда проще будет разделять конфигурацию на части по файлам, чтобы соотносить с частями приложения. Подобным образом следует делить и другие директории, если они есть: templates, translations, docs, tests, App\Controller, App\EventListener и другие.

С возможностями современных IDE выделить какой-то определённый компонент из такой системы будет не сложно, если это, конечно, потребуется.

Комментарии (49)


  1. AlexSteelax
    21.05.2023 18:05
    +16

    Какая-то сумбурщина полная чес слово


    1. maxkain Автор
      21.05.2023 18:05
      -15

      Не конструктивно


      1. k4ir05
        21.05.2023 18:05

        Так и статья не конструктивна) А комментарий зато лаконичный, и спасает от траты времени.


        1. maxkain Автор
          21.05.2023 18:05

          Это почему статья не конструктивна?


    1. maxkain Автор
      21.05.2023 18:05
      -6

      Если вы не понимаете написанного, видимо, вам оно не нужно.


  1. Myclass
    21.05.2023 18:05
    +12

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


    1. maxkain Автор
      21.05.2023 18:05
      -10

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


      1. SaintSet
        21.05.2023 18:05
        +4

        Когда речь заходит за архитектуру любого ПО лучше прибегать к средствам визуального моделирования UML, как минимум диаграмма классов, круто когда есть диаграмма последовательностей. Это не делает красивее статью, это делает ее понятнее, если посмотреть на статьи по паттернам там везде прилагают схемы.


        1. maxkain Автор
          21.05.2023 18:05
          -5

          Пока остановился на таком формате, со схемами получится всё гораздо объемнее.


          1. SaintSet
            21.05.2023 18:05
            +1

            Конечно, это скорее рекомендательный характер, в любом случае спасибо)


  1. pOmelchenko
    21.05.2023 18:05
    +8

    Имхо. Начиная с примеров из середины статьи — архитектура становится всё менее чистой.

    Складывая всё в App... раскидывая по папочкам типа "сервисы в Service", "энтити в Entity" и тому подобное, вы начинаете увеличивать вес "чистых" спаггети. Когда сущностей в проекте увеличится до значительных размеров — сидеть с такой "красотой" станет невыносимо грустно.

    Ребята из симфони может и не рекомендуют писать бандлы, но коллеги почему-то забывают, что использовать нэймспэйсы это не только "брать что дали", но еще и то что можно сделать что-то типа того:

    ...
    "autoload": {
        "psr-4": {
          "App\\": "app/",
          "YourCompaty\\SomeContextA\\": "domain/SomeContextA/",
          "YourCompaty\\SomeContextB\\": "domain/SomeContextB/"
        }
      },
    ...
    

    , где в директории domain/ContextName вы уже можете хранить ту же структуру с "сервисы в Service", "энтити в Entity", которая не будет пересекаться с соседними контекстами. Что позже, при необходимости, поможет вам в вычленении "контекста" в отдельными микросервис.


    1. k0rinf
      21.05.2023 18:05
      +1

      Плюс можно добавить Modulite и/или Deeptrac и все будет еще лучше и чище.

      Честно сказать, из текста статьи вообще не понятно ничего. Ни одного примера нету, где мы нарушили SOLID и как мы переписали код, таким образом чтобы соблюдать SOLID. Мало примеров кода и картинок. Советы типа делать UserOrderCheckoutService, приводят к тому что появляется куча однотипных названий сервисов, и потом никто не знает какой из них использовать в той или иной ситуации.

      Например что делать, если наши юзеры разбиваются еще на группы, у этих групп есть разные настройки и разные права. Получаться разработчики захотят создать UserSallerCheckoutService, UserOptovikWhithoutRuleReservCheckoutService и т.д.


      1. maxkain Автор
        21.05.2023 18:05
        -7

        Примеры есть как соблюдать SOLID есть.

        Если вы делаете сервисы и не знаете как их потом использовать, тут даже не знаю как помочь, может быть стоит давать понятные имена, разъяснять из назначения. Но уменьшение количества классов эту проблему не решает. Такая же проблема будет и с функциями.

        Я упоминал, что можно разделять код на классы с различными названиями.


      1. ilitaiksperta
        21.05.2023 18:05
        +2

        Ни одного примера нету, где мы нарушили SOLID и как мы переписали код, таким образом чтобы соблюдать SOLID

        Это все конечно интересно. Но меня больше интересует вопрос - а зачем вообще соблюдать SOLID? На это есть какойото адекватный практический ответ, а не "потому что так правильно" ?


        1. maxkain Автор
          21.05.2023 18:05
          -5

          Множество литературы об этом есть. Думаю, вам никто не будет здесь её пересказывать.


          1. ilitaiksperta
            21.05.2023 18:05
            +5

            "потому что так в книжке написано", ага. Не удивляйтесь что с вас смеются в коментах


        1. SaintSet
          21.05.2023 18:05
          +2

          Эти вещи актуальны для всех видов проектов, особенно чаще начинаешь вспоминать про эти принципы когда пытаешься написать юнит тесты, там ошибки проектирования всплывают моментально и становиться больно смотреть на код. Например начинаешь понимать что лучше использовать DI потому что так легче мокать какие то объекты, самое банальное это когда пишется какая то интеграция с внешним апи, и используется Guzzle, и вот в тестах становиться вопрос особенно остро, как засунуть в свой сервис объект Guzzle, так что бы были заранее замоканы ответы в нем, да еще во всех возможных вариантах, вот тут и начинает большую роль играть SOLID. Можно и без тестов да, но если у вас есть какая то интеграция с платежными системами то лучше что бы тесты были.


          1. ilitaiksperta
            21.05.2023 18:05
            -1

            Каверкать код ради тестов для логики складывания джейсонов в базу, отличная идея.

            замоканы ответы в нем

            Интересный вопрос, что вы вообще тестируете, если часть логики у вас моки, а не реальный код

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


    1. maxkain Автор
      21.05.2023 18:05
      -3

      Чем отличается domain/ContextName от App\Service\ContextName?


      1. pOmelchenko
        21.05.2023 18:05
        +4

        Тем что у вас в одном случае явное ограничение скоупа, а во втором просто какие-то файлы в общей директории/нэймспэйсе Service

        use Domain\Order\{
        	Service,
        	Repository,
        	Entity,
        	Exception
        }
        

        Которые могут жить в чем-то типа:

        domain\
              \src\
                  \Service
                  \Repository
                  \Entity
                  \Exception
              \tests\
                    \Service
                    \Repository
                    \Entity
                    \Exception
        

        Вы просто открыли директорию с контекстом бизнесовой логики, и далеко ходить никуда не надо. Всё кучно, всё понятно. Очень легко и не принужденно приходит понимание что "модель из контекста А" не протечет в "контексте Б" просто потому что она лежала рядом +/- директория.

        Очень грубый пример:

        Обед\
            \морковь\
            	\суп\
            	\гуляш\
            \кортофель\
            	\суп\
            	\гуляш\
            \соль\
            	\суп\
            	\гуляш\
            \перец\
            	\суп\
            	\гуляш\
            \вода\
            	\суп\
            	\гуляш\
        

        и/или

        Обед\
        	\суп\
        		\морковь\
        		\кортофель\
        		\соль\
        		\перец\
        		\вода\
            \гуляш\
            	\морковь\
            	\кортофель\
            	\соль\
            	\перец\
            	\вода\
        

        Я сейчас играюсь с ларавелькой, где такой подход вывел в "локальные" композер пакеты, которые регистрируются автоматически. То есть это не внешняя (пока не внешняя) зависимость которая ставится по сети, а директория с оформлена в виде пакет, в которой не только бизнесовые сущности ограничены, но и внешние зависимости тянутся под контекст задачи. Получилось красиво. При этом вытащил абстракции в свой отдельный пакет, от которого наследую финальные реализации


        1. maxkain Автор
          21.05.2023 18:05
          -2

          Что значит явное ограничение скоупа? Код, лежащий в Domain\Order идентичен коду из App\Service\Order. Это дело вкуса, но второй вариант более стандартный, если можно так выразиться.


          1. SaintSet
            21.05.2023 18:05
            +2

            Логика проста, причина такого подхода это изолироваться от немспейса самого фреймворка, и это стремление отгородить именно "бизнес логику", по сути можно и в App все складывать, а можно просто создать директорию и дописать одну строчку в композере и будет отдельный неймспейс под бизнес логику. Одна из причин почему я так когда то сделал, я хотел избежать протечек фреймворка в бизнес логику, просто что бы не один одна строчка с App/... не попала в логику, правда вот до доменов все никак не доберусь но идея в примере классная, один из плюсов такого подхода, если надо будет как то отделить код от проекта то как минимум меньше времени уйдет, не будет зависимостей у других доменов, так и у самого домена.


          1. pOmelchenko
            21.05.2023 18:05
            +1

            scope — the extent of the area or subject matter that something deals with or to which it is relevant.

            Для работы над Order, в вашем случае, приходится держать в фокусе много лишнего "шума" от других бизнесовых сущностей.

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


          1. hello_my_name_is_dany
            21.05.2023 18:05
            +1

            Суть в том, что бизнес-логика в идеале должна быть независима ни от чего кроме домена, а добавляя её в папку фреймворка и указывая его неймспейсы вы ничем её на защищаете и даже наоборот, прямо кричите, что это является частью вашего приложения на Symfony, но никак не зависимостью.


            1. maxkain Автор
              21.05.2023 18:05
              -1

              Вы ее никак не защитите, если положите в другую папку. Меняется только название пути. Можно, конечно, ее положить и в отдельный неймспейс, возможно, это сделает какой-то семантический акцент на том, что она не должна зависеть от инфраструктуры.


              1. hello_my_name_is_dany
                21.05.2023 18:05
                +1

                Так и надо в отдельный неймспейс, а в идеале завернуть в пакет. Вон на C# вообще делают отдельную компилируемую библиотеку динамическую.


          1. Arkemlar
            21.05.2023 18:05

            То, что второй вариант (класть все в App/Service) более стандартный (наверное в силу того, что так в симфони гайдах написано) не делает его более правильным.

            Я не раз встречался с проектами, которые начинали свою структуру именно по такому шаблону... Как итог, с этим становится невыносимо грустно работать: в директории Entity дохреналиард файлов с сущностями и вложенных директорий с ними же, аналогичная история с Service, DTO, Repository и др. В итоге все файлы компонентов перемешаны, нет чёткого понимания где определённый класс может быть заюзан (буквально через use), а где нет. А уж в плане навигации по коду проекта так совсем не весело скролить по несколько экранов вверх-вниз (ведь раскрывая директорию выпадает куча файлов). Все эти лишние сложности исчезают, когда логически взаимосвязанный код лежит рядом, в одном корневой неймспейсе (домене, модуле, пакете).

            Симфони гайды я думаю на новичков рассчитаны, чтобы не усложнять погружение в и без того не простой фреймворк рассуждениями про контексты, домены и тд. Когда пострадаете с моё с разросшимися проектами основанными на стандартном шаблоне, то я думаю поймёте меня :)

            DDD в помощь, но без фанатизма, во всем важна золотая середина.


            1. maxkain Автор
              21.05.2023 18:05
              -1

              Положить можно куда угодно, я для примера взял App\Service. Да, в основном код можно класть рядом, туда же. Но, например, удобно видеть все точки входа в приложение, открыв App\Controller. С EventListener систуация неоднозначная, думаю, можно и рядом с сервисом хранить. Если же entity хранить рядом с сервисом, придётся прописывать их в конфиге для каждого модуля, что не очень удобно, да и maker создает классы в App\Entity. Можно, конечно, с этим что-то придумать, но это уже отдельная тема. Да и вопрос, стоит ли? Если следовать правилу, что первый уровень в Entity, Repository и т.д. это название модуля, то проблемы быть не должно. То есть Entity\Module1, Repository\Module1 и т.д.

              Можно рассматривать первый уровень в App, как некоторые адаптеры к фреймворка к логике приложения.

              Еще, как вариант, делить приложение на бандлы, как делалось в старых версиях Symfony. В общем-то неплохой вариант, но добавляются некоторые издержки на поддержку бандлов. Но, конечно, не такие, как на поддержку микросервисов. Бандл, если нужно, можно всегда доработать до микросервиса. Да и микросервис то к чему? Можно же запустить два инстанса приложения на разных серверах. Что даст микросервис, сэконмит несколько мегабайт оперативной памяти? Делать отдельные сервисы есть смысл в довольно редких случаях.

              А книги на тему DDD это вообще, больше похоже на какой-то бизнес-тренинг, секту, чем на техническую литературу. Не даром, есть сокращенные до брошюры книги на эту тему, но и в них полезной информации мало, если ее вообще можно так назвать.


              1. Arkemlar
                21.05.2023 18:05
                +1

                На тему DDD есть много отличных статей, в тч на русском. Не обязательно читать красную книгу чтобы вникнуть, я собственно со статей начинал. Для меня чистый код - это когда его легко читать, как хорошую книгу, а это не выполняется когда взаимосвязанные вещи разбросаны по разным веткам путей в файловой системе.

                Аналогия очень простая в жизни есть: представь, что у тебя бизнес и есть разные виды документов - договора покупки с поставщиками, договора продажи с клиентами, транспортные накладные (необходимы для транспортировки груза), дополнительные соглашения с кем угодно, квитанции об оплате и т.д... И вот как лучше их хранить - складывать по типу (как я только что перечислил) или лучше все документы регулирующие отношения с каждой конкретной организацией хранить в своей папке (и при необходимости уже внутри неё делить по типам)? В первом случае чтобы разобраться в вопросе придётся рыться в разных папках, каждый раз вчитываясь и пытаясь понять оно или не оно, а во втором случае находишь одну папку и в ней всё, что нужно. По-моему очевидно второй вариант лучше. Причём этот пример никак не касается DDD.


              1. SerafimArts
                21.05.2023 18:05
                +1

                Директория Services является вырожденной (Так же как и Entity), т.к. не соответствует никакой задаче. Это плохой пример и явно не соответствует понятию "чистая архитектура", но допустимо в рамках небольшого ПО.


                Можно взять себе за правило, что любой кейс, который содержит кейворды Service, Util, Manager, Support, etc почти всегда (как и в вашем случае) является некорректным с точки зрения нейминга и построения структуры ПО.


                1. maxkain Автор
                  21.05.2023 18:05

                  В книге "Чистая архитектура", как раз и приводится один из примеров, где в Service хранится бизнес-логика. Директория не всегда должна соответствовать какой-то конкретной задаче, не выдумывайте.


  1. tzlom
    21.05.2023 18:05
    +1

    Это какая то мешанина из классов, обсуждение куда положить OrderFactory вместо того чтобы объя“снить для чего он и зачем ему интерфейс.

    Выбор между заказом зависящим от доставки или доставкой зависящей от заказа (в пределе у них собственные жизненные циклы связанные весьма условно)


    1. maxkain Автор
      21.05.2023 18:05
      -1

      Выше же написано: "Имеет смыл делать логику независимой от Entity и Repository". И дальше рассказывается как это сделать.

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


      1. tzlom
        21.05.2023 18:05

        Какой смысл в фабрике под интерфейсом? если это место где происходит бизнес-логика(из статьи создаётся впечатление что бизнес логика где то еще), то не понятно зачем там интерфейс, т.к. или фабрика будет одна, или их будет несколько принципиально разных.

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


        1. maxkain Автор
          21.05.2023 18:05
          -1

          Интерфейс - часть бизнес-логики, сама фабрика - часть инфраструктуры


  1. ilitaiksperta
    21.05.2023 18:05
    +5

    Хороший пример к чему на практике приводит следование чистой архитектуре, solid, gang of four прочему вредному мусору

    С этой плашки отдельно кекнул:


    1. ilitaiksperta
      21.05.2023 18:05
      +7

      Охлол, и это я еще теги не читал:

      гексагональная архитектура, слоистая архитектура, предметно-ориентированное


    1. BitRayDever
      21.05.2023 18:05

      Можете аргументировать почему это "вредный мусор"?

      Мне кажется что вы просто не понимаете зачем все это и никогда не сталкивались с архитектурными проблемами, для решения которых эти вещи существуют, что странно. Вы либо гений и пишете по SOLID'у сами того не осознавая, либо у вас проекты такого уровня где проблемы не успевают всплыть, либо они у вас есть но вы их игнорируете, наращивая технический долг и не осознавая в какой ад это может вылиться со временем.

      Чем больше кодовая база и сложнее проектируемая система, тем труднее её поддерживать, для этого и существуют архитектуры, SOLID'ы, GRASP'ы и другой на ваш взгляд "мусор".


    1. k4ir05
      21.05.2023 18:05

      Но текст ведь действительно сложный... для восприятия и понимания (зачем он).


      1. maxkain Автор
        21.05.2023 18:05

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


  1. dopusteam
    21.05.2023 18:05
    +4

    Входные данные можно принимать через параметры функции, но часто удобнее передавать структуру с данными, DTO.

    Которая всё ещё будет как параметр функции. Смысл понятен, но донесён не совсем удачно, имхо.

    Могут понадобиться App\Service\Order\Checkout\InputDto, возможно, и InputDtoInterface.

    Возможно? А зачем?

    Если в OrderCheckoutService много разнообразной логики, можно выносить её в другие классы с разными названиями. Чтобы следовать принципу единой ответственности можно разделить его на несколько классов, например, OrderCheckoutAcmeFeature1ServiceOrderCheckoutAcmeFeature2Service и так далее.

    А можно не выносить в другие классы с разными названиями? Этот совет ничего полезного вообще не несём.

    Зачем, кстати, выделяете все ключевые слова жирным, чтоб в поиске выпадать чаще?

     Можно вместо интерфейсов использовать и промежуточную структуру, DTO, но такой способ будет более объёмным в реализации и иметь некоторые другие минусы.

    Можете раскрыть мысль? Как я вместо интерфейса могу DTO использовать? Какие некоторые другие минусы?

    Таким образом, принцип единой ответственности из SOLID можно легко реализовать не привязываясь к структуре БД

    Очень общая фраза, которая никак не следует из предыдущих рассуждений и непонятно при чём тут SRP.

    Логика не привязана к конкретной сущности, её можно применить к любым другим данным — принцип инверсии зависимости

    О какой конкретно логике речь?

    Класс с реализацией можно легко подменить через сервис-контейнер — принцип открытости-закрытости

    Этот принцип тут ни при чём вообще, я вполне могу туда подсунуть реализацию, нарушающую принцип.

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

    Пустые слова, ибо нет конкретных аргументов

    И так, у нас есть OrderCheckoutService, содержащий бизнес-логику оформления заказа. Он может вызывать напрямую другие сервисы, выше уровнем. Или через инверсию зависимости другие, более конкретные реализации, менее важные детали

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

    Тогда класс App\Service\Delivery\DeliveryCalculator становится конкретной реализацией адаптера. К нему может добавиться ещё некоторая логика, тогда его уже можно назвать декоратором. А если он будет обращаться к нескольким классам, то это уже посредник

    Выглядит как попытка впихнуть как можно больше паттернов в текст (даже не в код).


    1. maxkain Автор
      21.05.2023 18:05
      -5

      А можно не выносить в другие классы с разными названиями? Этот совет ничего полезного вообще не несём.

      Можно выносить, можно не выносить, это решает разработчик. Я предлагаю вариант, который вижу подходящим, чтобы следовать SOLID.

      Можете раскрыть мысль? Как я вместо интерфейса могу DTO использовать? Какие некоторые другие минусы?

      Создаёте DTO, и переносите в нее данные из сущности. В это минусы - создание нового экземпляра, необходимость переноса данных. В классе сущности не видно связи с DTO, в отличие от интерфейса. Интерфейс более гибок, можно создать DTO, реализующий интерфейс, если нужно.

      Очень общая фраза, которая никак не следует из предыдущих рассуждений и непонятно при чём тут SRP.

      В сущности, в таблице БД могут быть разнородные данные. И логика их обработки разносится по разным классам - сервисам. В этом и применение SRP.

      О какой конкретно логике речь?

      О логике обработки данных сущности, о логике приложения, какой же еще?

      Этот принцип тут ни при чём вообще, я вполне могу туда подсунуть реализацию, нарушающую принцип.

      Это каким образом? Если вы не правите оригинальный код, а наследуетесь, принцип соблюдается.

      Пустые слова, ибо нет конкретных аргументов

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

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

      Да, может ходить вверх и вниз. Вниз ходит через инверсию зависимости, чтобы не зависеть от нижних уровней.


  1. makarovpro
    21.05.2023 18:05

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


    1. maxkain Автор
      21.05.2023 18:05
      +2

      В программировании это уже устоявшееся слово, имеет отношение к проектированию.


  1. hardtop
    21.05.2023 18:05
    +7

    Статья о том, как с помощью названия путей на диске без единой строчки кода объяснить "чистую" архитектуру. Автор просто увеличивает энтропию вселенной используя слова Энтити, Домен, Сервис...


    1. maxkain Автор
      21.05.2023 18:05
      -6

      Если вы откроете другую литературу по схожей теме, в том числе книгу "Чистая архитектура", то очень удивитесь, что там практически нет кода.


      1. ilitaiksperta
        21.05.2023 18:05
        +3

        очень удивитесь, что там практически нет кода.

        Как и стоящих мыслей


  1. sgzmd
    21.05.2023 18:05

    Вот тут вот еще хорошо было описано, ага.


  1. candyboah
    21.05.2023 18:05

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