как Разбивать базу проекта на Domain и Application Logic

Проверенные VDS на SSD в Европе от $4 и России: Датацентр №1 от 199руб

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

  1. grigori

    grigori ( ͡° ͜ʖ ͡°) Команда форума

    Сообщения:
    6.671
    Ваш город:
    Stormwind
    Address:
    Scottsdale, United States
    Country:
    Location on Map:
    Рядом сидит мастер Вова. (Чаще Дима, но неважно). 5 лет назад он закончил курсы по PHP, и взял кредит на машину. Он увидел модель User! У модели есть метод get(). Чтобы вывести список всех пользователей, можно просто взять список пользователей, и написать в цикле $U = User::find($user_id);
    Отлично! Задача сделана, тестировщик принял.
    Через год начинаются проблемы. Выводить одного юзера можно, выводить всех по циклу - нельзя.

    Потом решают, что у пользователя может быть несколько email-адресов. Метод create() ждет один адрес, и вызывается в 10 местах. Метод get возвращал одномерный массив, и вызывается в 50 местах. А теперь вместо массива надо использовать структуру. Реализация занимает месяц.

    CRUD для вывода списка в админке - нормально. Для сложной логики - нет.
     
    Последнее редактирование: 7 окт 2017
  2. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.974
    Ваш город:
    Russia, Moscow
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    Какое отношение имеет CRUD к выводу данных? Никакого.
     
  3. WMix

    WMix герр M:)ller Партнер клуба

    Сообщения:
    5.847
    Ваш город:
    Berlin
    Address:
    Berlin, Germany
    Country:
    Location on Map:
    ActiveRecord это когда entity имеет ссылку на connection когда она сама себя записывает
    rest это просто стиль http запросов недодуманый graphql
    rest+crud имеет смысл жить, типо быстро накидать
    проблема только в том что логика в контроллере, те в эту секунду смешалось все, логика, http, база данных, люди, кони. это как лапша из 4х языков
     
  4. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.974
    Ваш город:
    Russia, Moscow
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    CRUD не имеет отношения к логике в контроллере.
     
  5. WMix

    WMix герр M:)ller Партнер клуба

    Сообщения:
    5.847
    Ваш город:
    Berlin
    Address:
    Berlin, Germany
    Country:
    Location on Map:
    это да но в ресте все видят крад, такое ограниченое представление домена (достал, показал, изменил, удалил) отсюда и проблемы, те нагенерить админку на коленке не вдаваясь в суть вопроса. таких пхпмайадминов нафигачат и доказывают что дешево и решает все вопросы
     
  6. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.974
    Ваш город:
    Russia, Moscow
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    Это достаточно странное заблуждение, что для админки подходит CRUD. К примеру, @Adelf как-то рассказывал про случай. Я тоже на своей практике знаю кучу кейсов в админке, где это серьёзно вредило и мы вынуждены были делать серьёзный рефакторинг. Админка так или иначе взаимодействует с теми же сущностями, что и пользователь, поэтому странно полагать, что для пользователя мы должны чётко выделять команды/события, а для админов — нет. Ну да, бывают вещи, которые нужны чисто для внутреннего пользования, но это выходит за пределы нашего интересного разговора. Когда задача просто наговнокодить, ну уж я как-нибудь справлюсь, это мы все умеем. :)

    Отмечу, что REST — это не только CRUD, там вполне могут быть кастомные ресурсы, см., например, https://en.wikipedia.org/wiki/HATEOAS:
    Код:
    <account>
       <account_number>12345</account_number>
       <balance currency="usd">100.00</balance>
       <link rel="deposit" href="https://bank.example.com/accounts/12345/deposit" />
       <link rel="withdraw" href="https://bank.example.com/accounts/12345/withdraw" />
       <link rel="transfer" href="https://bank.example.com/accounts/12345/transfer" />
       <link rel="close" href="https://bank.example.com/accounts/12345/close" />
    </account>
    Другой вопрос, что в моих глазах REST действительно ассоциируется с CRUD, мы это уже обсуждали. Я по возможности топлю за честный CQRS-RPC API. Единственное, тут может быть проблема с количеством запросов: ну не хочу я в большинстве случаев возвращать что-то из методов-команд (речь про внешний API), особенно когда видно, что возвращаемые данные — исключительно из-за требований UI. Если я не ошибаюсь, то это как-то решено в GraphQL (до сих пор его толком не изучил), где можно (вроде бы) строить цепочки из мутаций и query-запросов (т.е. в рамках одного HTTP-запроса выполнять все необходимые действия: например, что-то получить из query, подставить в мутацию, сделать ещё один query для получения результата). Это очень круто.
     
  7. fixxxer

    fixxxer К.О. Партнер клуба

    Сообщения:
    12.354
    Ваш город:
    Moscow, Russia
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    Я эту проблему, помнится, решал безо всяких GraphQL небольшой модификацией батчей в jsonrpc2, добавив туда возможности ссылаться в параметрах на результаты другого элемента батча (по ID) и псевдокоманды типа Batch.match/Batch.repeat/Batch.filter.

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

    WMix герр M:)ller Партнер клуба

    Сообщения:
    5.847
    Ваш город:
    Berlin
    Address:
    Berlin, Germany
    Country:
    Location on Map:
    конечно не подходят, но есть куча ненужных vo к примеру производитель, группа товаров и тд, когда табличка это id+name, там можно быстренько нагенерить чистый crud. других действий и не будет.
    а сам по себе rest он вполне себе неплохо вписывается в домен, по старым примерам
    Код:
    post /user/42/rename
    name=Vasia
    другое дело он ограничен, если это не id а некий поисковый запрос (чел который в 42 родился, фамилия на П, с родинкой на правом полупопии)
    тут язык нужен и graphql это (описание) решает
     
  9. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.974
    Ваш город:
    Russia, Moscow
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    Что они делают? Например, у меня есть запрос на получение последних поисковых настроек и stateless-метод поиска:
    Код:
    {"jsonrpc": "2.0", "method": "ui.search.get_last_criteria", "id": 1}
    {"jsonrpc": "2.0", "method": "business.search.search", "params": {"name": "Foo"}, "id": 2}
    Как будет выглядеть запрос «возьми из last_criteria name и передай в business.search.search»? Тут как-то без уродства не получится.
     
  10. fixxxer

    fixxxer К.О. Партнер клуба

    Сообщения:
    12.354
    Ваш город:
    Moscow, Russia
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    А, забыл самое важное, давно это было :) Если ключ в params начинается с $, тогда эвалится селектор.
    Код:
    {"jsonrpc": "2.0", "method": "business.search.search", "params": {"$name": "1.path.to.name"}, "id": 2}
     
    Последнее редактирование: 8 окт 2017
  11. ivanov77

    ivanov77 Новичок

    Сообщения:
    52
    Ваш город:
    South Pole
    Address:
    Fort Lee, United States
    Country:
    Location on Map:
    Чистый юнит тест для реалий Yii будет непросто сделать, поэтому таких радикальных изменений пока не планируется. Codeception сам подгружает yii2, везде доступно Yii::$app, а AR модели жестко запрашивают базу, даже для получения имен столбцов. Но смотрю вы тут уже о своем говорите, а мой вопрос не заинтересовал, @Adelf молчит
     
  12. WMix

    WMix герр M:)ller Партнер клуба

    Сообщения:
    5.847
    Ваш город:
    Berlin
    Address:
    Berlin, Germany
    Country:
    Location on Map:
    уверен проблема не в Yii
     
  13. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.974
    Ваш город:
    Russia, Moscow
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    У тебя слишком много вопросов за раз, некоторые слишком абстрактные («где какие объекты создаваться должны»), просто лень отвечать. Если вкратце говорить про service layer, то:
    1. Это просто внутренний API твоего приложения, который не завязан на конкретный UI.
    2. web controller — это UI. Формы, сессии — это тоже UI. Чтобы представить что должно быть в контроллере, а что в SL, нужно представить как бы ты мог написать то же бизнес-действие в консольной команде, например.
    3. Валидация может быть и в формах web controller'а и внутри SL одновременно. Она может дублироваться. Различные security проверки на права могут быть как в контроллере, так и в специальном сервисе, который делегирует выполнение service layer (это может быть нужно, если, например, есть старые пользовательские контроллеры и точно такие же API-методы для пользовательского API и нужно переиспользование).
    4. Service layer должен быть максимально stateless: никаких Yii::$user, сессий, и прочего «текущего состояния». Если ты захочешь, например, в админке или в консольной команде от имени пользователя отправить сообщение, то должно быть достаточно указать userId. Как ты понимаешь, Yii::$user где-то внутри бизнес-логики будет этому сильно мешать, т.к. его либо не будет, либо он может указывать на другого пользователя (на админа, например).
     
    ivanov77 нравится это.
  14. fixxxer

    fixxxer К.О. Партнер клуба

    Сообщения:
    12.354
    Ваш город:
    Moscow, Russia
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    Я не знаю про устройство Yii AR, но если там нельзя даже сделать new User() без обращения к БД, то просто выкинь это говно. Но я подозреваю, что не все так плохо.

    А вообще AR вполне можно тестировать, никто же не заставляет вызывать методы типа save(), нам же методы модели надо протестировать, а не методы AR.
     
    ivanov77 нравится это.
  15. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.974
    Ваш город:
    Russia, Moscow
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    Хотел я было тут описать что я бы сделал если бы мне пришлось работать в проекте с Yii2 (ушёл бы из проекта), но тут внезапно понял, что похоже в Yii даже нет поддержки SELECT .. FOR UPDATE: https://github.com/yiisoft/yii2/issues/11730

    Как с этим вообще можно работать?
     
    Последнее редактирование: 9 окт 2017
  16. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.974
    Ваш город:
    Russia, Moscow
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    Нашёл костыль через findBySql.

    В общем, я бы скорее всего работал с ActiveRecord Yii2 как-то так:
    • отказался бы от изменений свойств снаружи (вероятно, пришлось бы перекрыть __set());
    • добавил бы транзакционности;
    • записывал бы события внутри сущности;
    • отказался бы от различных валидаторов от Yii внутри сущности (http://www.yiiframework.com/doc-2.0/yii-base-model.html#rules()-detail), использовал бы вместо этого https://github.com/beberlei/assert + валидировал бы формы;
    • объяснил бы коллегам, почему Yii нельзя использовать и либо поменял бы проект, либо поменял бы фреймворк.

    PHP:
    public function handle(RenameVkUser $command)
    {
        
    $now = ...; // new DateTime()

        
    VkUser::getDb()->transaction(
            function() use (
    $command$now) {
                
    $vkUser VkUser::findWithLock($command->userId);
                
    $vkUser->rename($command->newName$now);
                
    $vkUser->save();
            }
        );
    }

    // ...

    final class VkUser extends ActiveRecord
    {
        
    // ...

        
    public static function findWithLock(): VkUser
        
    {
            
    $sql 'SELECT ... FROM ' self::tableName() . ' WHERE id = ... FOR UPDATE';
            return 
    self::findBySql($sql)->one(); // or throw VkUserWasNotFound exception
        
    }

        public function 
    __set($name$value)
        {
            throw new \
    LogicException('You shall not pass');
        }

        public function 
    rename(string $newNameDateTime $now): void
        
    {
            
    Assert::notEmpty($newName);

            if (
                
    $now->toTimestamp() - $this->lastTimeChanged 86400
                
    && $this->registeredAt $now->toTimestamp() > 3600
            
    ) {
                
    $this->record(new VkUserNameSentForModeration($this->id$newName));
            } else {
                
    $this->name $newName;

                
    $this->record(new VkUserChangedName($this->id$newName));
            }
        }

        public function 
    afterSave($insert$changedAttributes)
        {
            
    parent::afterSave($insert$changedAttributes);
            
    EventDispatcher::getInstance()->dispatch($this->releaseEvents());
        }
    }
    — вот теперь метод rename() можно покрыть unit-тестами, тут доступ к БД не нужен, достаточно проверять, что нужные события происходят в зависимости от. Если же там нет логики, то и тестировать unit-тестами нечего.

    P.S. Хотя я вот понял, что наверное afterSave() тоже будет вызываться внутри транзакции и нужно сильно постараться, чтобы это исправить. Ещё одна причина не связываться с этим фреймворком.
     
    fixxxer и ivanov77 нравится это.
  17. ivanov77

    ivanov77 Новичок

    Сообщения:
    52
    Ваш город:
    South Pole
    Address:
    Fort Lee, United States
    Country:
    Location on Map:
    При new User() обращения к базе не будет, но вот при любом обращении к св-ву $model->property уже будет.
    Не знаю как в других ORM, но в yii не надо указывать имена свойств в классе, а только имя таблицы, пример класса , и имена св-в достаются автоматом из схемы таблицы в БД.
     
  18. ivanov77

    ivanov77 Новичок

    Сообщения:
    52
    Ваш город:
    South Pole
    Address:
    Fort Lee, United States
    Country:
    Location on Map:
    @Вурдалак, спасибо за развернутый ответ, уже какое то правило.
    Но момент, вы говорите:
    Почему web-controller и сессии - это UI?
    Вот читаю у Крэга Лармана(Применение UML 2.0 и шаблонов проектирования) про слои, он это все относит к какому то уровню приложения, насколько я из дальнейшего понял это C в MVC (красным надписи я добавил):
    [​IMG]
    Т.е. функция web-controllera будет получить данные от V, определить что с ними делать и передать обработку уже на M (сервисы и модели). Жаль все это терминологически путано, вот тут человек по сути тоже о об этих Сервисах говорит, но называет он их "Сервисы уровня приложения", хотя они не с этого(как на картинке) уровня приложения получается а с уровня бизнес логики приложения (M)
     
  19. ivanov77

    ivanov77 Новичок

    Сообщения:
    52
    Ваш город:
    South Pole
    Address:
    Fort Lee, United States
    Country:
    Location on Map:
    Да ну ладно, что это за AR тогда будет.
    Первый пример в вики о AR говорит что это и как используется, поменяешь эту основу, тогда уже и переименовывать надо для ubiquitous language :)
     
  20. Вурдалак

    Вурдалак Newbie

    Сообщения:
    5.974
    Ваш город:
    Russia, Moscow
    Address:
    Moscow, Russia
    Country:
    Location on Map:
    MVC — это UI pattern, там есть, соответственно, смещение акцентов в сторону UI. Примерное соотношение такое:
    Код:
    ┌─────────────────┐      ┌─────────────────┐   ┌──────────────┐
    │                 │      │                 │   │              │
    │                 │  ┌──>│  Domain layer   │<┐ │              │
    │                 │  │   │                 │ │ │              │
    │      Model      │──┤   └─────────────────┘ │ │              │
    │                 │  │   ┌─────────────────┐ │ │              │
    │                 │  │   │   Application   │ │ │              │
    │                 │  └──>│ (Service) layer │<┼─│Infrastructure│
    └─────────────────┘      └─────────────────┘ │ │              │
    ┌─────────────────┐      ┌─────────────────┐ │ │              │
    │      View       │──┐   │                 │ │ │              │
    └─────────────────┘  │   │  Presentation   │ │ │              │
    ┌─────────────────┐  ├──>│   layer (UI)    │<┘ │              │
    │   Controller    │──┘   │                 │   │              │
    └─────────────────┘      └─────────────────┘   └──────────────┘
    
    В MVC одним словом «модель» называют всю основную логику приложения — application/service layer и domain. В многослойной архитектуре одним словом «presentation» (aka «UI») называют view и controller.

    Популяризация термина MVC привела к тому, что из-за каких-то когнитивных искажений программисты считают центром архитектуры HTTP-контроллеры. Контроллеры/view — это UI stuff. Центром приложения должна быть модель, а не презренный UI. Я стараюсь вообще не использовать термин MVC, потому что я вырос из того возраста, когда контроллеры и view казались верхом архитектуры. Нужно расставлять приоритеты правильно.
     
    Последнее редактирование: 10 окт 2017
    fixxxer и ivanov77 нравится это.