• Микросервисы (Microservices)
    0
    Поправил.
  • Как правильно работать с исключениями в DDD
    0
    Валидация в одном месте — application services layer. Но она либо делегируется доменным объектам (через вызов их CanDo методов), либо нет (к примеру проверка имейла юзера на уникальность идет напрямую к базе или к репозиторию).
  • Как правильно работать с исключениями в DDD
    +4
    Хорошая статья. Пара комментариев/дополнений с моей стороны:

    1. Очень хороший поинт насчет того, что с конвейерами подход с резалтами не работает — слишком много нагромождений (at least in C#), и это явно не то как авторы asp.net видели для себя обработку ошибок из middleware. Поэтому сценарий с декораторами — исключение из общего правила. При этом единственное, я не встречал других сценариев где такая работа с exceptions была бы оправдана.

    2. По поводу типов исключений. Я не думаю что стоит создавать более 1 кастомного исключения (если только это не special case scenario as in #1 above). В статье говорится о преоразовании ItemNotFoundException в 404. Это как раз классический случай, где надо преобразовывать отсутствие объекта (Single/First) в return value и работать дальше уже с ним.

    К примеру, может быть use case когда запись по данному Id обязана находиться в базе и если ее нет — это является исключительной ситуацией. В других кейсах это может не быть исключительной ситуацией. И если всегда бросать ItemNotFound, то нельзя будет отличить исключительную ситуацию (500) от не-исключительной (404). Здесь немного более подробно на эту тему: enterprisecraftsmanship.com/2017/01/31/rest-api-response-codes-400-vs-500

    Рекомендую всегда возвращать Maybe из репозиториев/гейтвеев и потом уже решать кидать исключение или возвращать 404.

    3.
    Но не менее справедливо, что в императивных языках (к которым относится C#) повсеместное использование Result приводит к плохо читаемому коду, засыпанному конструкциями языка настолько, что с трудом можно разглядеть исходный сценарий.
    Код получается более verbose, да, но он при этом становится наоборот, более читаемым благодаря явной логике ветвений. Опять же, по аналогии с goto: можно переписать метод с кучей if-ов на использование goto и тогда код будет плоский, без indentations. Только читать его станет намного сложнее.

    4. И еще один отличный поинт про Application Service, валидацию, и инкапсуляцию в доменный слой. Я обычно делегирую все (возможнные) проверки слою домена через паттерн Do/CanDo. Получается примерно так:

    class DomainClass
    {
      public Result CanTransfer()
      {
         return Result.Ok();
      }
      
      public void Transfer()
      {
        Guard.Require(CanTransfer().IsSuccess); // кидает исключение в случае false
        
        /* ... */
      }
    }
    
  • Как правильно работать с исключениями в DDD
    +3
    State machine-ы делать на F# — одно удовольствие. Рекомендую этот курс Марка Симана если еще не смотрели: app.pluralsight.com/library/courses/fsharp-type-driven-development/table-of-contents
  • Functional C#: Primitive obsession (одержимость примитивами)
    +1
    Да, но не обязательно отдельными методами, property setters тоже подойдут.
  • Functional C#: Primitive obsession (одержимость примитивами)
    +1
    Здесь пример того как я обычно это делаю: github.com/vkhorikov/FuntionalPrinciplesCsharp/blob/master/New/CustomerManagement.Logic/Model/Email.cs
  • Отзыв на книгу Growing Object-Oriented Software, Guided by Tests
    +1
    Если «набросать структуру», то очень легко ошибиться
    Стурктура как раз-таки должна быть легковесной (отсюда слово «набросать»), чтобы ее можно было легко менять. Итерировать дизайн нужно в любом случае, разница в том, есть ли у вас при этом тесты и если есть — насколько хрупкие они.
    Тесты с моками далеко не всегда оказываются такими уж хрупкими
    Тесты с моками не хрупки только когда они заменяют собой external systems (bus, БД и т.д). Если они мочат внутренности доменной модели — они завязываются на детали имплементации и значит становятся хрупкими. Сторонники mockist подхода (как минимум те, кого я встречал, включая авторов GOOS книги) не делают такого разделения и как правило мочат всё подряд — и внешние системы и внутренности самой доменной модели.
  • Отзыв на книгу Growing Object-Oriented Software, Guided by Tests
    0
    Классика — это всегда bottom-up, Кент Бек и ко не писали про моки в оригинале, лондонская школа выработалась позже. Сочетать то, что описано в книге с классикой кстати возможно, но не так как вы описали (и это соответственно не будет полноценным top-down). Можно начать с набросков доменной модели (без тестов), затем после того как структура более-менее понятна — написать первый end-to-end тест и прокладывать себе путь к его исполнению путем классического bottom-up. Получится эдакий двух-уровневый TDD (как описано в книге), но без моков и без преждевременного распределения ответственностей. Проблема в top-down подходе в том, что если вы неверно выделелили эти ответственности, то отрефакторить их довольно сложно, т.к. из-за моков тесты становятся завязаны на детали имплементации.

    вы не против разработки с моками? Вы просто призываете их удалить после того, как «реализация готова»?
    Это я к тому, что если вы большой приверженец подхода сверзу-вниз, то это тоже не повод оставлять моки в конечной имплементации.
  • Отзыв на книгу Growing Object-Oriented Software, Guided by Tests
    0
    Чем руководствовались авторы сказать трудно. В книге кстати заметно как они испытывают трудности с циклами, т.к. возникают проблемы при «собирании» всех взаимодействующих классов воедино в composition root.

    возможно, компоненты действительно должны взаимодействовать «в обе стороны», а вы просто прячете это за возможностями какого-нибудь фреймворка
    Как я уже упомянул, код проекта довольно несложен, никаких фреймворков за исключением UI не используется. Должны или нет взаимодействовать в обе стороны — на мой вгляд неверная постановка вопроса. Нужно смотреть на то, можно ли сделать так, чтобы они работали только в одну сторону. Если можно — значит так и нужно делать.
  • Отзыв на книгу Growing Object-Oriented Software, Guided by Tests
    0
    Действительно ли ваша система проще? Мне пока вы не смогли это продемонстрировать.
    Мне кажется, это довольно легко увидеть глядя на диаграммы + код. В альтернативной версии в два раза меньше классов и интерфейсов (при этом код внутри самих классов либо такой же по размеру, либо меньше), плюс он не имеет недостатков, описанных в статье, таких как наличие циклических зависимостей.

    Фукнциональность проекта идентична оригиналу, как по части самого кода так и по части тестов его покрывающих, фреймворки (кроме UI) не используются.

    Это моки заставили авторов наделать столько классов (что сомнительно) или (что более вероятно) они принимали во внимание какие-то аспекты, которые вы по каким-то причинам откинули?
    Опять же, это легко проверить посмотрев на код.
  • Отзыв на книгу Growing Object-Oriented Software, Guided by Tests
    0
    Вопрос по больше части сводится к top-down vs bottom-up подходу к разработке. Mockist подход действительно помогает при top-down, т.к. позволяет «мочить» несущественные детали. Я бы не сказал, что один из подходов позволяет решать задачи проектирования лучше другого. Лично я больше тяготею к классическому bottom-up, но также понимаю людей, которые предпочитают top-down.

    При разработке top-down без моков действительно никак. Но при этом эти моки не обязательно оставлять после того как реализация готова. Такие тесты можно отрефакторить и заменить тестами без моков, что я собственно и сделал в статье.
  • 3 способа использовать оператор?.. неправильно в C# 6
    0
    Причем здесь слой доступа к данным вообще?
  • 3 способа использовать оператор?.. неправильно в C# 6
    0
    >Как это нет, когда по вашей ссылке:
    Если вы про это: " preventing unauthorized parties' direct", то это о сокрытии информации, а не о соблюдении инвариантов.

    >что по вашей логике есть нарушение инкапсуляции
    Да, верно, внутри GetAdminAddress — такие же правила что и в DoSomething()

    >Хотя геттер — тоже метод, но он даёт мне возможность выбирать любые варианты
    Опять же — тут дело в наличии бизнес логики и в том, где она находится

    >Потом, я захотел найти не-администратора с зарплатой до 100 рублей и фамилией Нафтазаров, и выяснить его стаж, а не адрес. Ещё один метод писать?
    Да, либо еще один метод, либо параметр в существующий, в зависимости от ситуации
  • 3 способа использовать оператор?.. неправильно в C# 6
    0
    Дело не в технических отличиях метода и проперти, дело в наличии бизнес-логики и в том, где она хранится.
  • 3 способа использовать оператор?.. неправильно в C# 6
    0
    Нет, инкапсуляция — это объединение данных с логикой + сокрытие информации от клиентов: en.wikipedia.org/wiki/Encapsulation_(computer_programming). Защита внутреннего состояние объекта — это соблюдение инвариантов и больше в сторону контрактного программирования.

    >Правильно?
    Поясните вопрос, не уверен что понял.

    >(1)Если у клиента Customer открыты Employees, что нам мешает их опрашивать?
    Ничего до тех пор пока логика опроса не базируется полностью на данных из Customer. К примеру опрос с целью сохранения части кастомеров во внешней коллекции по каким-то признакам — не является нарушением инкапсуляции. Пример выше:
    var boss = customer.Employees.Max(x => x.Salary).FirstOrDefault();
    — является

    >(2)Что, если класс Customer писали не мы, а другая компания?
    Если доступа к классу нет, то тут уж ничего не поделаешь. Это тем не менее также будет или не будет являться нарушением в зависимости от (1)

    >Что, если надо будет получить не адрес, а телефон? Не у админа, а у Семён Семёныча?
    Нужно больше данных, непонятно что конкретно вы имеете ввиду
  • 3 способа использовать оператор?.. неправильно в C# 6
    0
    Потому что оно находится внутри класса Customer.

    Разверну ответ. Для многих подобный код считается нормой:
    var boss = customer.Employees.Max(x => x.Salary).FirstOrDefault();
    


    Тем не менее, это также является нарушением инкапсуляции, т.к. здесь логика по определению «босса» отвязана от данных, с которыми эта логика работает. Правильным с т.з. принципов инкапсуляции решением будет вынести эту логику в класс Customer.
  • 3 способа использовать оператор?.. неправильно в C# 6
    0
    >Здесь уже спрашивали, но я спрошу ещё раз, раз речь про инкапсуляцию: как она нарушается?
    Инкапсуляция нарушается тем, что метод DoSomething делает суждения полностью базируясь на внутренностях класса Customer. Это по сути определение инкапсуляции: если данные полностью принадлежат одному классу, то методы по работе с этими данными должны также быть в этом классе. Второй пример показывает восстановление инкапсуляции — логика по получению адреса админа перенесена в Customer.
  • 3 способа использовать оператор?.. неправильно в C# 6
    0
    > устраняя необходимость в проверках на нал
    Имеется ввиду что второй случай — это и инкапсуляция, и отсутвие проверок на null в самом методе, первый случай — только отсуствие проверок на null.

    По поводу того, что внутри GetAdminAddress будет такие примерно такой же код:
    return Employees
            ?.SingleOrDefault(x => x.IsAdmin)?.Address?.ToString();
    

    В данном случае речь не о том, что оператор нельзя использовать (использовать его можно), а о том, что он скрывает проблемы в дизайне первоначального метода. Т.е. первый случай его использования нарушает принципы инкапсуляции, второй — нет, т.к. Customer обращается ко своим внутренним членам.
  • Типы CQRS
    0
    Самый первый вариант — отойти от CQS в данном конкретном случае и таки вернуть объект вместе с результатом выполнения команды.
    Второй вариант — как предложили выше — генерировать Ids на стороне клиента. В таком случае клиент сможет делать запросы по этому Id с использованием queries
  • Типы CQRS
    +2
    LazyLoad нельзя использовать, чтобы избежать этого?

    LazyLoad приводит к ошибкам доступа в случае если к коллекции идет обращение когда сессия уже закрыта. Получить на клиенте количество ордеров таким образом не получится
  • DTO vs POCO vs Value Object
    0
    Как правило, Value Object-ы (так же как и Entities) не используются для передачи данных между приложениями. В целом, в простых случаях (как например в вашем) Value Object-ы действительно можно использовать как DTO, но опять же — это до тех пор, пока в них не скопилось достаточно логики.

    Хорошей практикой считается изолировать домен приложения. На практике это означает, что доменные классы (Entities and Value Objects) не должны знать о том, как они сериализуются. В более-менее сложных случаях этого трудно достичь если сериализовывать их напрямую.
  • DTO vs POCO vs Value Object
    +2
    Мнение, которое вы высказали, и послужило причиной написания этой статьи.
    Value Object — иммутабельные объекты
    Это верно.
    Они как бы строительные кирпичики для Entity
    Это тоже верно.
    Никакой логики в них быть не должно
    Вот это уже неверно. Value Object-ы вполне себе могут содержать логику. Более того, Эванс и ко рекомендуют помещать доменную логику именно в Value Object-ы там где это возможно, т.к. работать с ними проще из-за их неизменяемости. Хороший пример Value Object-а — DateTime в .NET. Неизменямый тип данных с большим количеством логики внутри.
    POCO (POJO, PODS) — это принцип организации данных
    POCO — это в первую очередь принцип, который говорит о том, что при моделировании предметной области следует использовать настолько простые классы, насколько возможно. Это понятие никак не связано с DTO (кроме того, что DTO тоже не следует наследовать от тяжеловесных компонент).
  • DTO vs POCO vs Value Object
    0
    На самом деле

    Что-то не вижу как сказанное вами противоречит тому, что написано в статье
  • Functional C#: Non-nullable reference types (ненулевые ссылочные типы)
    0
    Еще хотел добавить. Пожалуй главный плюс подхода с Maybe для меня в том, что он улучшает читаемость кода благодаря тому, что мы разделяем нулевые и ненулевые типы.
  • Functional C#: Non-nullable reference types (ненулевые ссылочные типы)
    0
    1. Согласен

    Проверяются все входящие и выходящие значения. Т.е. в вашем примере NRE будет тут:

    var customer = CustomerService.GetCustomer(); // будет NRE
    Console.WriteLine(customer.Name);

  • Functional C#: Non-nullable reference types (ненулевые ссылочные типы)
    0
    Не совсем так. Проверка на нал действительно идет в ран-тайме, но мы получаем ошибку компилятора в случае если используем нулевую ссылку там где подразумевалась ненулевая.
    Code annotations от решарпера мне не нравятся тем, что
    1) Это всего лишь warning
    2) Используется opt-in схема. Т.е. все типы по умолчанию нулевые. По-хорошему нам нужно обратное поведение — сделать все типы по умолчанию ненулевыми и затем opt-out в случае если какой-то из них нулевой.

    Но вообще Code annotations и Code Contracts — тоже вполне себе хорошая альтернатива
  • Functional C#: Primitive obsession (одержимость примитивами)
    +1
    Использовать исключения для валидации — плохая практика. Тут более подробно на эту тему: habrahabr.ru/post/263685
  • Functional C#: Primitive obsession (одержимость примитивами)
    +1
    Запретить не получится. Но я бы поспорил с посылкой этого утверждения. Программисты-коллеги — друзья, а не враги, цель нового класса EmailStrong — не запретить им что-то делать, а подсказать, направить на правильный путь. Ну и плюс общение между коллегами должны быть intensive, чтобы все были в курсе нововведений.
  • Functional C#: Primitive obsession (одержимость примитивами)
    0
    А что мешает использовать методы со старыми сигнатурами?

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

    … и теперь сломался ранее работавший маппинг.

    Маппинг куда? Если речь про ORM, то они умеют памить на protected setter-ы. В EF это посложнее сделать, в NH — попроще.
  • Functional C#: Primitive obsession (одержимость примитивами)
    0
    Понять — по сигнатуре метода. Т.е. какие-то методы будут работать только с новыми имейлами, для других — мы оставляем как было.

    лучше все-таки использовать сеттеры, а не методы

    Здесь как раз пригодится сделать небольшие методы-сеттеры для обозначения новых инвариантов класса.
  • Functional C#: Primitive obsession (одержимость примитивами)
    0
    особенно учитывая, что где-то присваивать можно (мало ли, мы данные из системы в систему гоним). Опять-таки, в систему типов этот запрет присвоения уже не обернешь, снова боль.

    Обернуть можно. Где-то метод принимает старый Email, а где-то — только новый EmailStrong, который является наследником старого Email.
  • Functional C#: Primitive obsession (одержимость примитивами)
    0
    Еще раз посмотрел на код. Да, вы правы.
    Старался сделать примеры как можно проще, не хотел накручивать какую-то логику поверх обычного изменения имейла и нэйма.
  • Functional C#: Immutability
    0
    По поводу ORM — обычная практика в таких случаях создавать интернал коллекцию для ORM и внешнюю read-only для клиентов класса
  • Functional C#: Primitive obsession (одержимость примитивами)
    0
    Good points.

    1) По поводу ручного биндинга — вы правы, здесь теряется часть встроенного функционала, который есть в ASP.NET. Это вопрос взвешивания «за» и «против». Для меня плюсы более выразительной доменной модели перевешивают минусы необходимости писать подобный код вручную. Для более простых проектов вполне можно отказаться от этого подхода и делать по старинке.

    Но при этом для других свойств (скажем, City и Country) такой проверки нет, и теперь программисту нужно помнить про два разных механизма. Не ужас, но неудобно.

    Если у City и Country есть какие-то более-менее сложные инварианты, то их тоже стоит обернуть в классы-обертки.

    2) По поводу невалидных данных в БД — отличный point. Есть два распространенных подхода к проблеме. Первый — вместе с ужесточением инвариантов писать скрипты для миграции данных в БД, чтобы они соответствовали новым инвариантам. Второй — создавать отдельный класс, к примеру EmailStrong для хранения имейлов, инварианты в которых были ужесточены и не давать присваивать кастомерам объекты старого Email, только нового. Со временем, когда БД придет в соответствие с новыми требованиями, старый Email удаляется, новый EmailStrong переименовывется в Email. Второй вариант сложнее, я как правило пользуюсь первым.
  • Functional C#: Primitive obsession (одержимость примитивами)
    0
    Отдельные методы-сеттеры сделаны для большей выразительности. Всю ту же логику можно написать и в сеттере свойства, безусловно, разницы в коде при этом не будет.
  • Functional C#: Immutability
    0
    С ридонли полями примерно так же как и с private set пропертями в плане читаемости. Т.е. для того чтобы понять является ли класс изменяемым нам все равно нужно смотреть внутрь этого класса, по сигнатуре это непонятно. Тут бы помогло ключевое слово immutable.

    >P.S. Почему статья не оформлена как перевод?
    Я думаю свои статьи не очень правильно оформлять переводом, хотя тут не уверен насчет правил хабра.
  • Functional C#: Immutability
    0
    Вы правы. Пост писался еще до 6го шарпа
  • Code contracts vs валидация входящих данных
    0
    Да, под предусловиями я имею ввиду именно предусловия контрактов. Я выделяю предусловия (и оставляюю за скобками инварианты и постусловия), т.к. предусловия обычно путают с валидацией приходящих извне данных, с постусловиями и инвариантами в этом плане проще.

    Конкретно эту книгу Мейера я не читал, но вообще первоисточник моих знаних о контрактах именно Мейер, конкретно эта его книга: www.amazon.com/Object-Oriented-Software-Construction-CD-ROM-Edition/dp/0136291554
  • Code contracts vs валидация входящих данных
    0
    Это часть публичного API одного из классов доменной модели.
  • Code contracts vs валидация входящих данных
    +1
    Верно ли, что результат невыполнения контракта — всегда исключение, а при валидации ситуация обрабатывается внутри метода?

    Верно. Причем исключения эти не должны отлавливаться в коде (разве что для логирования ошибки), приложению в этом случае нужно дать упасть (fail-fast прицнип). Более подробно на тему исключений: habrahabr.ru/post/263685

    Если внутри GetById проверить OrganizationId > 0, это будет контракт или валидация?

    В целом ничего не мешает обозначить это как контракт, но с точки зрения best practices контрактами лучше всего помечать значимые с точки зрения модеолирования условия. К примеру то, что юзера нельзя удалить если он уже удален — значимое условие и дает разработчику важную информацию о доменной модели приложения. Еще пример:

    public class Organization : Entity
    {
        public void ProvisionUser(User user, Subscription subscription)
        {
            Contracts.Require(UsersInternal.Contains(user));
            Contracts.Require(SubscriptionsInternal.Contains(subscription));
            Contracts.Require(subscription.SeatsRemaining > 0);
            Contracts.Require(subscription.IsProvisionable);
            Contracts.Require(!user.IsProvisionedTo(subscription));
    
            subscription.AddSeat(user);
            user.AddSeat(subscription);
        }
    }
    

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

    Заметил, что я не ответил на этот вопрос:
    Чем технически различаются контракты и валидация?

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