• Вопросы для собеседования бэкенд-разработчика
    +1

    Я на большинство вопросов «хотите обсудить эту тему» ответил «нет»:) Видимо, меня не возьмут.

  • Кто такой программист?
    –1

    Вы не один такой. Мне 29, я тоже окончил физ-мат школу и виду как снижается качество образования. Готовят в основном «обслуживающий персонал». Может это и неплохо, но все чаще ощущаю себя героем романа Олдоса Хаксли.

  • CQRS. Факты и заблуждения
    0
    Кстати, не хотите подлелиться опытом в DDD в комментариях здесь? Я как раз затронул тему «прикладного» DDD в рамках ООП-парадигмы.
  • Domain Driven Design на практике
    0
    cartCalculator.Calculate(cart), кэп!:) Калькулятор считает ценую корзины, потому что скидки могут применять как позиции заказа, так и заказу в целом. Бывает «3 по цене 2», «купи на 3000 рублей и получи в подарок шнурки для галошь» и просто накопительные скидки для постоянных клиентов.
  • CQRS. Факты и заблуждения
    +1
    F# в вопросе типов сильнее C#. Например, union'а часто не хватает для моделирования, приходится multiple dispatch городить. Опять же какое ООП имеется в виду? То, что в C++ или в SmallTalk?
  • Domain Driven Design на практике
    0
    На одном из проектов есть такая задача. У нас отдельно Product.BasePrice и есть CartCalculator. Внутри калькулятора — еще другие зависимости, которые расчитывают цену в зависимости от лицензии, роялти, накопительной скидки и других бизнес-правил.
  • Domain Driven Design на практике
    0
    Можете раскрыть свой вопрос более подробно?
  • Domain Driven Design на практике
    0
    Спасибо за вопросы.
    Обоснуйте пожалуйста, почему метод Accept принадлежит сущности Company. Ведь аккредитация выдается Ведомством. А то получается что компания сама себя куда хочешь может аккредетировать… Ну или как минимум в этот метод должен передаваться Policy ведомства или экземпляр объекта Аккредитация, с данными кто выдал и почему.

    Именно в данном приложении ведомство было одно, а аккредитация проходила автоматическом режиме при закрытии квартала при соблюдении определенных условий. Сущность в проекте называется по другому и там довольно увесистый агрегат. Возможно более естественно смотрелись бы названия методов BecomeAccepted и BecomeDeclined, чтобы субъект и объект действия не путались.

    Почему метод DangerouslyChangeInnAndKpp вообще содержит в себе эту приставку? Если предметной области это нормальное явление — это бессмысленно. Может стоило тогда ввести другую сущность?

    Смена ИНН и КПП — это перерегистрация. Т.е. в реальном мире нельзя просто так взять и сменить. Однако, предполагалось, что в подаваемых сведениях могут быть ошибки и к системе были предъявлены требования дать возможность эти ошибки исправлять.
    Название DangerouslyChangeInnAndKpp навеяно React'ом. Видимо, зря я удалил аннотации, в которых эта логика объяснялась.

    Я не знаком с .Net вообще, но вы упоминаете «луковую» и «чистую» архитектуры, а потом в домене используете public class Specs. Разве это не кусок вашего фреймворка только что просочился в домен? Т.о. вы нарушаете направление связей.

    В .NET на уровне платформы встроены «деревья выражений» (Expression Trees). Например:
    Expression<Func<Company, bool>> acceptedSpec = 
         x => x.State = CompanyState.Accepted;
    

    Expression<Func<Company, bool>> — довольно многословная конструкция. Spec<T> — более читаемая и сразу специализирована для сигнатуры T -> bool. Плюс добавлена перегрузка операторов && и ||. Спецификация по определению представляет правила бизнес-логики, т.е. является частью домена.

    Необязательно создавать спецификации внутри класса сущности, можно положить их рядом. Мне просто удобно использовать такой синтаксис: Company.Specs.Accepted. Все бизнес-правила фильтрации агрегата Company под рукой и intellisense поможет их найти. Можно использовать и другие правила, например отдельный статический класс. Тогда будет так: CompanySpecs.Accepted.
  • Domain Driven Design на практике
    0
    Промахнулся и ответил вам ниже отдельным комментарием.
  • Domain Driven Design на практике
    0
    Непонятно только только, почему при использовании DDD нельзя описывать бизнес логику в сервисах

    Почему же нельзя? Сервисам посвящен целый параграф. Другой вопрос, что есть смысл хранить не всю логику. Обратите внимание на метод Decline. Он четко показывает: чтобы отклонить заявку необходимо добавить непустой комментарий с пояснением, почему компания не может быть аккредитована. Представьте, у нас в домене три бизнес-сценария, где мы должны отклонять аккредитацию. Где гарантия, что все три метода будет реализовывать один разработчик? Даже если так, он может забыть правило про обязательный комментарий. В варианте с отдельным методом это невозможно.

    Кроме того в реальном приложении при отклонении необходимо было хранить историю заявок, в том числе с указанием менеджера, работающего с юр.лицом. При переходе из одного статуса в другое необходимо было менять 2-3 разных поля по определенным правилам. Объектная модель с поведением здорово помогает с пониманием «что здесь происходит и почему».

    Более подробно этот вопрос раскрывает Дино Эспозито. Ближе к концу доклада он приводит следующий пример. Допустим, в поддержку приходит баг. Пользователь объясняет, что он совершил некоторое действие, но ему не были начислены бонусные балы (или типа того). Если в коде программист видит «репозитории» и изменение каких-то свойств ему потребуется приложить дополнительные усилия, чтобы интерпретировать слова пользователя и сопоставить с кодом. А если он видит в коде нечто вроде:
    if(user.IsVip)
    {
       user.AddBonus(100);
    }
    

    Он может сразу уточнить статус пользователя. Может быть, про бонусные баллы он услышал от друга, а про VIP-статус позабыл. Может быть у нас баг и пользователю не был присвоен VIP-статус, хотя должен был. В любом случае, диалог получится куда более предметным.
  • Domain Driven Design на практике
    0
    Спасибо за комментарий. Ошибся при редактировании кода. ToList там вообще не скомпилируется, потому что ByInnAndKpp выполняет FirstOrDefault. Добавил пример метода ByInn и отредактировал статью.
  • CQRS. Факты и заблуждения
    0
    Тогда я не понимаю, чего вы хотите. Все возможные варианты не устраивают:)
  • CQRS. Факты и заблуждения
    0
    Но сути это не меняет. Нужно бросить исключение чтоб прекратить выполнение и поднять сообщение(я) об ошибке наверх в контроллер. Я не в восторге от такого подхода.

    В моем примере используется возвращаемый тип Result, а не исключения. Подробно подход описан здесь.
  • CQRS. Факты и заблуждения
    0
    А ссылочкой не поделитесь?
  • CQRS. Факты и заблуждения
    0
    А этот вариант вам не подходит? При необходимости валидацию расширяете просто доменными правилами.
  • CQRS. Факты и заблуждения
    0
    Дописал. Все сходятся, что это неудобно. Мы пробовали компоновать QueryHandler'ы. Получается нечитаемо и многословно.
  • CQRS. Факты и заблуждения
    0
    На основании, что предметная модель не предоставляет естественного Id. Guid гарантирует уникальность за счет математики (хотя и подвержен коллизиям), Id — за счет СУБД. У Эванса, на сколько я помню, про выбор Id написано четко: если есть Id в домене — берите из домена. Нет — положитесь на технические срадства.

    кто гарантирует, что со следующим обновлением БД-движка или его заменой останутся те же самые идентификаторы?

    Приведите реальный пример обновления СУБД, чтобы слетели первичные ключи?

    Guid'ы совсем не бесплатные. Если есть возможность использовать автоинкремент в БД зачем усложнять себе жизнь?
  • CQRS. Факты и заблуждения
    0
    Один интерфейс решает проблему с двумя декораторами, которую я решил с помощью кастинга к делегатам. Это плюс.

    Субъективно кажется, что имея IRequestHandler<SomeCommand> и IRequestHandler<SomeQuery> проще запутаться и случайно добавить мутацию в IRequestHandler<SomeQuery>, чем в случае двух интерфейсов: IRequestHandler<SomeQuery>, а в IQueryHandler<SomeQuery>. Это минус.

    Библиотека выглядит приятно. Если у вас нет проблем с соблюдением нейминага и код-ревью не в web-морде, а в IDE (чтобы можно было по файлам пробежаться, посмотреть, проверить наличие мутаций), решение вполне имеет право на жизнь.
  • CQRS. Факты и заблуждения
    0
    Я имел в виду, что доменная модель смоделирована так: сначала есть «корзина», а в момент оформления появляется «заказ». Заказ создает наша система и сообщает его Id клиенту. Вы знаете альтернативу автогенерируемым БД Id и Guid для сущностей, для которых нет естественных Id (типа ИНН, КПП, паспортных данных и т.д.)?
  • CQRS. Факты и заблуждения
    0
    Все сделали на IRequest без сервисного слоя?
  • CQRS. Факты и заблуждения
    0
    Допустимо, но тогда это не CQRS так как доступ к данным организован через один и тот же объект для двух разных типов операций.

    Мы довольно долго так думали, пока не нашли QueryableExtension'ы. Кейсы, где нужно возвращать сущности как-то после этого кончились.
  • CQRS. Факты и заблуждения
    +1
    Сейчас я в обработчиках использую EF контекст напрямую и при тестировании подменяю хранение на InMemoryStorage (у нас ef core) и вроде всё хорошо, но как-то не по себе.

    А что вас не устраивает в таком подходе? DbContext уже реализует репозиторий, зачем городить абстракцию поверх. Можно отвязаться от EF и инжектировать IQueryable<T> через фабрику: dbContext.Set<T>(), но тогда EF не будет кешировать обращения по Id (по-умолчанию кешируются только вызовы метода Find). Это тоже можно исправить, но работа с контекстом — это вообще тема отдельной статьи.

    На практике абстракция от ORM создает больше проблем. Если можно говорить о какой-то заменяемости между EF и NHibernate, то остальные, даже если поддерживают LINQ требуют значительного переписывания кода для замены. Так что первая ложь — ORM абстрагирует вас от БД и вы сможете через 5 лет разработки переехать с Оракла на Постргес. Вторая — IQueryable абстрагирует вас от деталей реализации.

  • CQRS. Факты и заблуждения
    0
    И второй вопрос — что скажете на счёт MediatR в качестве инфраструктуры для CQRS. Там правда всего один интерфейс, но проблема деления на команды и запросы решается нэймингом.

    MediatR реализует идею separation of concerns, только вместо декораторов использует Behaviors. Даже используется «фишка» с пустым дженерик-интерфейсом, чтобы указать возвращаемый тип:

    public class Ping : IRequest<string> { }

    В данном контексте я не вижу разницы: использовать SimpleInjector с декораторами или MediatR. Есть смысл проверить производительность и выбрать, что работает быстрее. SimpleInjector компилирует деревья выражений, а MediatR использует механизм фабрик.
  • CQRS. Факты и заблуждения
    0
    Вы имеете в виду дорого писать маппинги для Select? Если да, посмотрите в сторону Mapster Queryable Extensions. Вы же в любом случае в JSON сериализуете не сущность, а какие-то агрегированные данные. Зачем тащить из базы ненужные поля, если можно выбрать только то, что нужно?
  • CQRS. Факты и заблуждения
    0
    Единственное, что мне до сей поры очень плохо ясно, это куда во всей этой схеме пихать валидацию?

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

    1. некоторые сущности могут быть вычитана по два раза
    2. транзакционностью придется управлять за границей CQRS-стека

    Тут был поднят вопрос без ответа на него: что делать с автосгенерированными БД ID? Я полагаю, тут истинно верного решения нет?

    Если у вас синхронный CQRS без шины — возвращайте Id из команды / хендлера и не мучайтесь. Если с шиной, то у вас либо должен быть механизм доставки доменного события «заказ создан» до пользователя, например web sockets. Либо отказ от автогенерируемых Id — создаем Guid'ы на клиенте.
  • CQRS. Факты и заблуждения
    0
    А если на начальном этапе разработки в качестве хранилища взять тот же EF Core, и оперировать его контекстом, как в командах, так и в запросах. А потом, по мере роста нагрузок и усложнения логики — проводить постепенный рефакторинг, устраняя узкие места (с использованием того же Dapper или вообще ADO).

    Я в абзаце «Возвращать из QueryHandler сущности или DTO» примерно это и предлагаю. Что вас смутило?:)
  • Java с ассемблерными вставками
    0
    .NET реализация сможет это делать только на win-платформе с запуском от администратора?
  • Про ошибки и исключения
    0
    Вы все правильно поняли. Этот Result — калька с F# с поправкой на реалии C#. Union заменил на T,Failure. Метод Return, чтобы обязать проверить оба варианта, потому что компилятор C# не предупреждает, если не все случаи обработаны в pattern matching. Приходится выкручиваться. LINQ-синтаксис — замена computation expressions.

    Не всегда можно добавить в стек ещё один ЯП, зачастую по организационным причинам. Зато можно какие-то инструменты портировать, хотя они и могут смотреться чужеродно. Сигнатуры SelectMany для IEnunerable такие же страшные. Думаете их кто-то видит и вообще задумывается о них? Многие просто используют готовые инструменты и не вникают. Мне вообще кажется, что скоро мы войдём в эпоху, когда прикладные программисты не будут понимать как Машина выполняет код. Будем писать только DSL.
  • Про ошибки и исключения
    0
    «Проблему остановки» кажется же еще не решили:) Я как-то даже не стал уточнять.
  • Про ошибки и исключения
    0
    ошибки – объекты первого класса

    Вы вырвали из контекста. Полная фраза такая:

    Является ли любая ошибка «исключительной ситуацией»? Если вы когда-нибудь сталкивались с бухгалтерским или налоговым учетом, то наверняка знаете, что существует специальный термин «корректировка». Он означает, что в прошлом отчетном периоде были поданы неверные сведения и их необходимо исправить. То есть в сфере учета, без которой бизнес не может существовать в принципе, ошибки – объекты первого класса. Для них введены специальные термины. Можно ли назвать их исключительными ситуациями? Нет. Это нормальное поведение. Люди ошибаются.

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

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

    Knight capital потеряла $440.000.000 за 30 минут из-за ошибки. Да она была не в программе, а из-за человеческого фактора при выкладке. Кто знает, если бы в их ПО была система самодиагностики оно бы аварийно завершилось и компания понесла бы убытки, но не обанкротилась. Один из наших клиентов занимается разработкой медицинских тренажеров. Стоимость ошибок в их отрасли — человеческие жизни. «Львиная доля» — понятие весьма субъективное. Если в сфере, где вы работает нужно, чтобы программа «делала вид, что как-то работает», это не значит, что критерии успешности других людей совпадают с вашими.

    Сложность основного алгоритма в общем случае — существенная. Сложность обработки ошибок даже в самых запутанных случаях — акцидентная по Бруксу.

    Если бы эта сложнасть была «акцидентная», Брукс не предлагал бы тратить половину (Карл!) времени на системное тестирование и отладку. Да не все это время уйдет на исправлние именно ошибок. Очень много «съест» работа с изменениями требований. Ноги этой проблемы все-равно растут из нежелания разбираться с «побочными» путями выполнения программы: тут не учли, здесь забыли, тут доработочка. Сдвинем ка релиз на пол года.

  • Про ошибки и исключения
    +1
    Вы их не игнорируете. Если не использовать Return вы не вынете значение из Value, значит придется обработать и успешное завершение и ошибку.
  • Про ошибки и исключения
    0
    Что конкретно вы считаете «мифом»?
  • Еще немного о валидации в ASP.NET
    0
    А вот эти аргументы можете прокомментировать? Считаете, что ничего страшного: exceptions for control flow?
  • Еще немного о валидации в ASP.NET
    0
    Мне — не лучше. Предпочитаю возвращать осмысленные коды ответов. Возвращение осмысленных кодов имеет преимущества по usability, соответствию стандартам и простоте отладки.
  • Еще немного о валидации в ASP.NET
    0
    UserNotFound, ProductNotFound, OrderCantBePaid, ProductOutOfStock — это типы исключений или возвращаемых из CommandHandler значений?
  • Еще немного о валидации в ASP.NET
    0
    Кстати, интересная тема: коды ответов. Если мы запрашиваем GET: /category/5 и такой категории нет принято возвращать 404 или 410. А вот если POST: { categoryId: 5, someData: ...} я бы возвращал 422, чтобы четко показать: такой метод существует, но шлешь ты мне хрень.

    Семантика валидации БЛ одинаковая: нет такой сущности. А вот код ответа — разный. Если вы возвращаете результат валидации из бизнес-слоя, скажем NotFoundResult, дополнительно обрабатываете в контроллере, зная контекст запроса?
  • Еще немного о валидации в ASP.NET
    +1
    Стоит заметить что ваше решение плохо работает когда надо прикрутить транзакции. А имя вашего класса MoveProductParam как бы подразумевает что транзакция бы не помешала.

    Вы имеете в виду случай, когда валидация проходит, а за оставшиеся 100мс кто-то успевает удалить продукт / категорию с таким id? Если да, то эта тема отдельной большой статьи.

    Правильно заметили выше — намешаны слои. Если уж так хочеться — делайте авто-валидацию или модел-байдинг по ид из БД уже в бизнес слое не завязываясь на веб АПИ. Тут тоже можно прикрутить свой велосипед через рефлекшн и атрибуты.

    Да прикрутил уже. Бывают простые случаи, когда нужно сделать много CRUD'а и не хочится делать DTO ради двух-трех полей, которые мапятся на foreign-ключи. Хотя, может их проще сразу гнать на Update в базу через Dapper. В любом случае, вариант с атрибутом скорее экспериментальный и не пертендует на истину в последней инстанции.
  • Еще немного о валидации в ASP.NET
    +2
    Вы прыгаете с UI сразу в Persistence слой, минуя бизнес логику.

    Ну и что? Какая бизнес-логика в наличие или отсутствии в БД записи? Строго говоря, откуда взялись эти id? Либо у нас ошибка и мы передаем в UI неактуальные данные, либо запрос сфабриковали. Pro100Oleh ниже приводит гораздо более убедительные аргументы и указывает на потенциальные проблемы. Кроме транзакционности добавляются потенциальные лишние запроы к БД. Вот эти проблемы действительно стоят внимания.

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

    Это не так. Если у вас синхронный CRUD, но вы вместо контроллера написали запросы в квейри / командах ничего не изменилось, просто называется иначе. Тот CQRS, что асинхронный обычно требует принципиально иного подхода к проектированию системы. Грег Янг вообще не рекомендует использовать CQRS как top level architecture.
  • Еще немного о валидации в ASP.NET
    0
    Тут смешаны слои. Слой UI, слой бизнес-логики и слой хранения данных. На уровне контроллера нельз провалидировать ничего, кроме формата запроса. Наличие всех обязательных полей, соответствие типам и форматам (положительные целые/UUID-строки для идентификаторов, валидный email, etc).
    Валидация наличия категорий и товаров — это правило бизнес-логики и место ему в бизнес-слое.

    Здесь вы правы. Но если кроме валидации изменить модел-байндинг, то все получается логично: раз в контроллер приходят не DTO, а сразу Entity, то и валидацию следует произвести там же. Конкретно в данном случае я вижу опасность не в перекрытии слоев, а в не стандартности решения. Ну и ошибки придется искать в двух местах.

    Биндинг моделей к параметрам контролера прибивает вас гвоздями к фреймворку

    Не прибивает. Func<Type, object, bool> и Func<Type, object, object> соответствуют следующему php-коду:
    function(Type $type, object obj): bool {
       //...
    }
    
    function(Type $type, object obj): obj {
       //...
    }
    

    Вместо них можно инжектировать сервисы / репозитории / что угодно. Для примера я использовал просто делегат (функцию). Кстати, dbContext уже реализует репозиторий, поэтому на практике эта абстракция в .NET смысловой нагрузки не несет: IQueryable — протекающая абстракция by design, потому что все выражения может разобрать только in-memory реализация, которая убивает весь смысл интерфейса.

    Биндинг моделей к параметрам контролера прибивает вас гвоздями к фреймворку

    Не прибивает. В популярных ORM на .NET достаточно давно используются POCO. Т.е. — сущности — это просто классы. Они не зависят от фреймворка.

    зы. Ну и от dbContext коробит. А что если приложение должно уметь работать вообще без бд? Или использовать редис как хранилище? Получается мы снова жестко прибиты к EF.

    Обратите внимание, dbContext не инжектится в атрибут. Я работаю с делегатом. Конкретная релизация настривается в IOC-контейнере.
  • Еще немного о валидации в ASP.NET
    0
    Func<Type, object, bool> — ужасный тип для сервиса. Надо либо напрямую обращаться к DbContext в атрибуте — либо заводить именованный делегат или интерфейс для сервиса.

    Не факт, что вы не захотите делать это через Dapper или еще как-то. Завист от проекта.
    Func<Type, object, bool> — пример. Какую зависимость использовать — вам решать.

    Байндер такой у меня тоже завалялся. Он сильно старый с reflection'ом и его еще допилить нужно. Только в виде идеи:
        public class EntityModelBinder : DefaultModelBinder
        {
            public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
            {
                var model = base.BindModel(controllerContext, bindingContext);
                if (model == null || !(model is EntityBase)) return null;
    
                var props = ((EntityBase) model).GetDependentProperties();
    
                var dbContext = DependencyResolver.Current.GetService<DbContext>();
                foreach (var propertyInfo in props)
                {
                    var id = controllerContext.HttpContext.Request[propertyInfo.Name];
                    if (string.IsNullOrEmpty(id)) continue;
                    
                    var fkValue = dbContext.Set(propertyInfo.PropertyType).Find(int.Parse(id));
                    if (fkValue == null) continue;
    
                    propertyInfo.SetValue(model, fkValue, null);
                    if (bindingContext.ModelState[propertyInfo.Name].Errors.Any())
                    {
                        bindingContext.ModelState[propertyInfo.Name].Errors.Clear();
                    }
                }
    
                return model;
            }
        }