Search
Write a publication
Pull to refresh

Comments 60

А какое веселье будет, когда транзакция подождёт недоступного почтового сервера, поставив колом всю систему из-за какого-нибудь уведомления, которое пользователь потом отправит в корзину, не читая…

Все конечно так, но если Вам придет письмо с таким содержимым, то я уверен, в штаны наложите знатно:
Привет, $username$! С тебя списали 100500 рублей за то-то, то-то, то-то.
Все конечно так, но если Вам придет письмо с таким содержимым, то я уверен, в штаны наложите знатно:
Привет, $username$! С тебя списали 100500 рублей за то-то, то-то, то-то.

Надеюсь, со мной не случится ни того, ни другого :)

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

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

Правильный способ: завершить транзакцию, в рамках транзакции в БД создаётся событие отправки уведомления. Эти события периодически выгребаются и отправляются в очередь. Может получиться два сообщения, если крашнулось во время транзакции. Из очереди рассыльщик выгребает сообщения, формирует и отправляет письма. Письмо может уйти дважды. Письмо нужно обогащать данными пользователя и самого события. В общем сложно, дорого и опять без гарантий.

Давят сроки. Они всегда давят. На чём будет настаивать архитектор? Что выберет разработчик, его тимлид и начальник тимлида? Что получится в итоге?

Лично я, конечно, за правильный способ. А по поводу сроков, начальников и обогащения...


  • Часто для писем нужна информация, которая не нужна коду, записывающему в БД. Так что письмо придётся обогащать данными в большинстве случаев — так лучше это сделать позже, не замедляя более приоритетные операции.
  • Сроки давят по-разному. Иногда можно объяснить, что лучше сделать правильно (особенно, если показать, в чём будет польза). Иногда — нет, не спорю. Тогда есть простой способ.
  • Понятно, что задача не самая однозначная. Но вы же согласитесь, что рассчитывать на постоянную доступность и быстрый отклик почтового сервера несколько оптимистично?
Правильный способ:

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

Такой вариант тоже подходит, но когда факт отправки почты вообще не важен. Либо я не совсем понял предложенное решение.


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

Такой вариант тоже подходит, но когда факт отправки почты вообще не важен. Либо я не совсем понял предложенное решение.

Да все просто — факт того, что вам внешний сервис ответил "ок", не означает, что все на самом деле ок. Следовательно, даже в случае такого ответа вам надо предусматривать негативный сценарий.


В 90% случаев (вроде "письма с подтверждением") гарантированной отправки не требуется — тогда и проверять просто ничего не нужно и делать ничего не нужно. Дернули внешний апи — и все, конец, если сервис я ответил "неок", то можно считать, что случился негативный сценарий, который уже обработан.


Если гарантия требуется — то это должно быть реализовано в слое, который непосредственно взаимодействует с внешним апи (там будет и какая-то retry-логика, и логи ошибок взаимодействия, и запись задач в бд и т.д.).


В бизнес-логике в любом случае ничего не будет, будет просто вызов sendMesage либо sendMessageGuaranteed, без каких-либо явных транзакций или проверок.


В таком контексте вопрос "пихать отправку в транзакцию или нет" — он в принципе как бы и не стоит даже.

Теперь чуть лучше понял, что имеете в виду. Понятно, что "проценты" у всех разные, как и подходы, впрочем.


Ещё бы уточнить, как обеспечивается целостность без транзакций (когда реально несколько операций с БД и надо обязательно либо всё сделать и отправить письмо, либо ничего не сделать и не отправлять) — саги?


UPD: На всякий случай поясню, я не говорю про то, что всё всегда должно быть в транзакции. Но вот зарегистрировать факт необходимости вызова внешнего сервиса для наших задач очень удобно как раз в транзакции, а потом уже да — и повторы и прочее конечно не дело бизнес-логики — у нас за это отдельный PersistentInvoker отвечает. Конечно, есть подход, когда решение о "персистентности" вызова принимает не вызывающий код (а оркестрируется где-то сбоку) и он тоже имееет право на жизнь, просто у нас не прижился.

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

Amazon тоже юзаем для некоторых проектов, а вот SQS или SES — тут больше force в курсе. Так или иначе, всё что нужно — хэндлится :)


Но не все заказчики хотят/могут использовать Amazon, так что делаем по-разному.

Прочтение письма никто не гарантирует. Его можно проверять или заголовком, и почтовый клиент сам письмо отправит (обычно отключают все). Или трекинговым кодом. Но загрузка внешних изображений может быть отключена в целях безопасности или проксироваться (как делают всякие гуглы и яндексы). А когда они решат попросить картинку — в целом не очень известно.
Баунсы и комплейнты приходят с аццкой задержкой, которую можно использовать для отключения доставки на этот ящик (пользователь не хочет), но не как гарантию доставки/недоставки.

Чего это вы так сразу. Для меня очевиден :)


В Tecture тестируется тот факт, что вы отправили команду для отправки email-а. А как на это отреагировал email-сервер — не тестируется (ну или может захватиться один раз, при изначальном прогоне с живыми системами).


То есть вы послали команду, проверили что она реально работает — и забетонировали факт отправки команды именно с такими параметрами в тест. Остальное — не вашей бизнес-логики дело.

Правильный способ:… В общем сложно, дорого и опять без гарантий.

На первый взгляд это так, но не совсем.


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


Другой аспект этой проблемы заключается в том, что — не почтой единой! Особенно в случае микросервисной архитектуры. Я к тому, что если прямо сейчас единственная задача, которую этот сервис должен выполнить, условно говоря, в рамках транзакции в БД, это отправка почты — то через пару месяцев таких задач будет воз и маленькая тележка: отправка события в nats/кафку/кролика/что там у вас, дёргание вебхука стороннего сервиса для синхронизации состояния или уведомления о чём-то и т.п. Поэтому использование "правильного способа" с самого начала, пусть даже пока надо только письма отправлять — в целом верное решение для многих проектов.

чтобы наш сервис отправил письмо по SMTP, и здесь гарантия всё-таки есть.

А зачем нам эта гарантия? Задача же не в том, чтобы отправить письмо, а в том, чтобы пользователь его прочитал. Отправлено оно или нет — при этом совершенно не важно. Как и в том случае, если пользователь его не прочитал — не важно совершенно, по каким именно причинам это произошло.

Затем, что наша область ответственности ограничена тем, что мы контролируем. Юзер может решить не читать письмо даже если оно у него перед глазами — это вне нашего контроля в принципе. Всё, что мы можем — гарантировать что от нас письмо ушло. И если мы не в состоянии это реализовать — это баг в нашем коде (если, конечно, бизнес изначально не ставил задачу "попытаться отправить письмо, но если не отправится, то и фиг с ним").

Всё, что мы можем — гарантировать что от нас письмо ушло.

Зачем? Кому это нужно и для чего?
Если вы что-то можете, то это не значит, что следует это что-то по факту делать.

Всё просто. :) Бизнес попросил реализовать отправку писем при определённом событии. Если моя реализация будет в большинстве случаев отправлять письмо, но иногда, при неудачном стечении обстоятельств, не отправлять — это баг (если, конечно, бизнес не включил такое поведение в постановку задачи).


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

Бизнес попросил реализовать отправку писем при определённом событии.

Бизнесу-то это зачем? оО
В таком виде это просто плохо поставленная задача, надо поработать над постановкой :)


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

Конечно, хотел бы! Меня совершенно не волнует, как и что мне выплатили, меня волнует — что я по итогу получил. И я хочу, чтобы вариант неполучения мной зп, раз уж он возможен в рамках обычного рабочего flow, был корректно обработан в рамках имеющихся бизнес-процессов, а не "мы деньги отослали, идите нахер".

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


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


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

было бы неплохо как-то обрабатывать почтовые "отлупы"

Ну, на самом деле я об этом задумываюсь при отправке — это выражается в том, чтобы выяснить у бизнеса/админов адрес для envelope и не забывать указывать его при отправке. Это не совсем "обрабатывать отлупы", скорее "сделать обработку отлупов возможной в принципе".


P.S. И делаю я это чисто ради того, чтобы не искать впустую баг в коде отправке из-за "я не получил письмо при регистрации!!111", когда дело в отлупе и это можно легко проверить просто заглянув в ящик, куда эти отлупы сыпятся.

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

Вот именно. Так никто не делает.


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

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


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

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

Т.е. если оператор опечатался в email или если админ неправильно настроил почтовый сервер или провайдер почтового сервиса потребовал другой протокол работы действия один и те же? Исправляем адрес базе?

В общем случае использовать другой канал связи

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

Вкачусь в дискуссию.


Тут, кмк, надо разделять возникающие ошибки не две категории: логическая (ваш код не маякнул SMTP-серверу отправить письмо) и инфраструктурная (у SMTP-сервера кончились почтовые голуби и он ваше письмо не отправил).


В Tecture я позволяю юниттестить логические ошибки: получать тест-прувен гарантии что бизнес-логика точно затриггерила отправку емейла. Остальное — инфраструктурные ошибки. И чинятся они не в коде (а например в конфиге SMTP-сервера).


Я ставил себе целью как раз отделить логическое от инфраструктурного. И получилось.

Про правильный способ отправки сообщений по SMTP — мой взгляд со своей колокольни: я последнее время работал, в основном, администратором сервера электронной почты (MS Exchange).
Так вот, постоянная ошибка программистов прикладных систем (и постоянные их претензии по этому поводу) — в том, что они ожидают, что сервер SMTP всегда готов принять их сообщение. На самом деле это не так. И протокол SMTP этого не требует: в нем есть коды ответа (в диапазоне 400-499), означающие временную недоступность сервиса: сервер не может принять сообщение сейчас, но может это сделать позже, поэтому отправку сообщения надо будет повторить. В частности, у MS Exchange переход в такое состояние — это часть штатной реакции на перегрузку (back pressure), а временная перегрузка в почтовой системе бывает и по вполне уважительным причинам: например, служба внутреннего PR послала сообщения о какой-нибудь акции всем сотрудникам, и прямо сейчас сервер занят доставкой тысяч сообщений в п/я, буквально через минуту он это закончит и освободится, но если повторной отправки посланного в этот момент сообщения из программы не будет, то это сообщение будет потеряно.
Поэтому любой сервер и большинство клиентов SMTP и организуют очередь на отправку (у клиента это обычно специальная папка, типа «Исходящие») и делают попытки повторной отправки из очереди, если они получили ответ о временной недоступности сервиса. Но вот некоторые самодельные программы-клиенты SMTP, (почему-то) так не делают: они пытаются отправлять только один раз.
В Windows Server есть, конечно, обходной путь: поднять локальный сервер SMTP (он там входит в состав дистрибутива) и делать отправку через него, лучше — через папку Pickup. Но, вообще-то, за этим сервером SMTP тоже нужно приглядывать — отказы там тоже возможны. То есть это — лишняя точка отказа, которая требует мониторинга.
Поэтому я двумя руками за такой правильный способ, который гарантирует повторную отправку сообщений при временной недоступности сервиса SMTP из самой программы. Иначе способ отправки будет не совсем соответствовать протоколу, и тогда возможны неприятности.
В случае, если необходимость отправки письма возникает в рамках транзакции в БД, разумно, на мой взгляд, организовать очередь отправки в специальной таблице этой БД (а если отправка важна — то и сделать добавление в эту таблицу частью транзакции), а отправку организовать отдельным процессом, читающим эту очередь и формирующим сообщение.
Ну или, на худой (или не очень) конец, воспользоваться облачным сервисом, который на 99,99% гарантирует отправку сообщений
многие из них слишком далеки от SQL. Но не все. Поэтому, вместо того, чтобы «терпеть, пока O/RM удаляет 3000 объектов» или придумывать ещё один, найдите тот, который вас устроит.

EFCore.BulkExtensions содержит расширения для массовых операций в ef core.
EFCore.BulkExtensions содержит расширения для массовых операций в ef core.

Спасибо, буду иметь в виду, что они вообще есть. Но я немного про другое, вот пример:

using LinqToDB;

using (var db = new DbNorthwind())
{
  db.Product
    .Where(p => p.UnitsInStock == 0)
    .Set(p => p.Discontinued, true)
    .Update();
}


P.S. Если подскажете такое же для EF, буду благодарен. Сам всё равно останусь верен LINQ to DB, но есть коллеги, которые используют EF. Да, bulk insert в LINQ to DB тоже есть.
Там же есть пример:
context.Items.Where(a => a.ItemId <= 500).BatchUpdate(a => new Item { Quantity = a.Quantity + 100 });

Для вашего случая будет что-то вроде:

db.Product
    .Where(p => p.UnitsInStock == 0)
    .BatchUpdate(new Product{ Discontinued= true } /*ну или принудительно задать ограничение: , updateColumns*/);
О, спасибо. Просто непривычные названия и синтаксис, проглядел.
Дополнил статью этой информацией.

P.S. LINQ to DB нравится не только массовыми операциями.
Хорошо быть CTO или хотя бы тимлидом — ему можно позволить себе наслаждать разными архитектурами.
А простому разработчику-гребцу работающему «в команде» (а особенно — в кровавом энтерпрайзе) сия роскошь обычно недоступна: его удел — выкатывание фич и правка багов в рамках кем-то когда-то выбранной архитектуры, да ещё и в соответствии с кем-то когда-то принятым руководством по оформлению кода (AKA style guide).
Единственно, что может греть душу простого гребца — это дорасти до тимлида.

Чертовски сложный вопрос. У меня есть что сказать на этот счёт, но надо подумать на свежую голову, как это нормально сформулировать.

В хороших командах все её члены могут инициировать изменение архитектуры или стайл-гайдов. А тимлиду/CTO нужно обосновать свои игры перед CTO/CEO. Или, хотя бы перед самим собой не в терминах "красиво", "правильно" и т. п., а в терминах "выгодно"

Design by comitee — это само по себе хреново. Но чтобы предлагать изменения архитектуры — надо обладать квалификацией. И если квалификация команды ниже чем тимлида/CTO, но при этом все активно предлагают и "каждый должен быть услышан" — то с проектом случается некоторого рода эта… жопа.

"Предложение услышано" не значит "предложение принято" :)

Я сам сталкивался с фактом, когда твоё действительно дельное предложение тонет в пяти других, суть которых сводится к "да пох ваще, сделаем как можно тупее". А потом наступает фаза "ну я же говорил".


Бесит. Но я держусь огурцом.

Здесь нужно задать два вопроса:
1. А в скольких процентах случаев фаза «ну я же говорил» в итоге так и не наступила после этих решений?
2. Если такой процент ничтожно мал, то не кажется ли вам, что у вас в команде нездоровая атмосфера и ее надо просто сменить?

Disclaimer: в то, что сейчас напишу, я не планирую вкладывать ни пафос, ни нытьё. Это может со стороны выглядеть так, но если объяснять без примеров из личной жизни — будет неубедительно и, опять же, пафосно. Также я понимаю, что у всех разная жизненная ситуация и мои слова могут выглядеть как "мыши, станьте ежиками" (с)… Но разум мой ограничен и универсального совета дать не могу.


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


Приоритеты и формулировка успеха. Согласен с Канеманом, что Успех = Труд x Талант x Везение (жаль, точных коэффициентов никто не скажет). Другой вопрос, что сформулировать, что такое успех, мы должны сами (и формулировка со временем может меняться). "Успехов", естественно, может быть несколько (локальные/глобальные, работа/личная жизнь).


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


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


Давайте посмотрим на последствия такой расстановки приоритетов:


  1. Работа у меня почти всегда была интересной, но не слишком уж высокооплачиваемой. А на первой работе вообще застрял лет на пять на почти нищенской для программиста зарплате, но там было чертовски интересно (как следствие — ипотека на первую квартиру, когда работал уже на 4-ой работе, была ментальным адом, потому что не люблю кредиты в принципе).
  2. Работа выбиралась исходя из приоритетов, поэтому обходил стороной банки и крупные компании.
  3. Я разведён, сын живёт со мной, дочка — с бывшей женой. Правда, мы с сыном считаем, что в этом больше заслуга характера жены, но надо быть честным — проблемы в отношениях исчезающе редко связаны только с одним их участником.
  4. Сейчас надо бы купить для нас с сыном хотя бы двухкомнатную квартиру, но это будет тот ещё квест, потому что в нужном районе в основном либо хрущёвки, либо "косящие под элитные" дома, но доплатить миллион-другой за это я не готов (на всякий случай — я живу в Ярославле). Но до этого ещё и не дошло, потому что надо ещё доделать ремонт в однушке...
  5. Из-за смещения приоритета денег чуть выше, приходится заниматься управленческой работой (пропорции где-то 50/50 но часто бывают периоды смещения то в одну, то в другую сторону). Привет, "многозадачность". Причём, управление — это не какое-то уютное тимлидство, но это уже совсем другая история...

Идеи о том, как сделать работу интересной


Пишу просто как сделал бы я в разных ситуациях, подойдёт, наверное, не всем.
И да, обращение, естественно, не к mvv-rus (он просто зацепил меня сложным вопросом), а к любому читателю.


  1. Если основной приоритет деньги, в зависимости от скиллов — искать работу в стартапе или в чём-то гуглоподобном. Или просто на собеседованиях стараться максимально узнать больше о будущих задачах и обстановке. Если скиллов пока не хватает — прокачивать скиллы.
  2. Если деньги — не основное (или они борются за первое место с интересными задачами) — найдите компанию (или команду в большой компании), где и задачи будут интересны для вас и вы сможете влиять на происходящее. Если непонятно как — прокачивайте скиллы (в том числе и софт-скиллы). Вообще, в любой непонятной ситуации — прокачивайте скиллы :)
  3. Если на данный момент вам кажется, что задачи совсем неинтересные и вы ни на что не влияете… Попробуйте найти в задачах что-то интересное, а в коллегах — интересных собеседников (возможно, получится влиять на архитектуру не являясь архитектором). Не получится — вернитесь к началу списка или подумайте, не пора ли сменить приоритеты.
  4. Высыпайтесь. Да, вот так внезапно. Не всегда это может получиться (но тоже вопросов приоритетов, в том числе).

Как это работает в моей команде


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


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


В нашей команде (она, кстати, офигительная) у всех есть некоторая степень свободы в решении текущих задач, пока они не затрагивают платформу. Есть ревью, поэтому откровенной жести не случается. В моём понимании, если не давать разработчику принимать вообще никаких самостоятельных решений — он не будет развиваться и это плохо для всех.


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


А вот если задача либо как-то затрагивает платформу, либо из-за неё серьёзные проблемы на проекте — тогда собирается 2-3 человека и один из них, обычно, либо я, либо force (иногда — оба). Чаще всего мы с ним на одной волне, но бывает, что не сходимся во мнениях. В большинстве случаев, это касается незначительных деталей (и тогда решение за тем, кто больше в контексте задачи). Если всё серьёзнее, то такие варианты:


  1. Зовём кого-то из опытных разработчиков (Миша, Саша — не нашёл вас на Хабре), чтобы помогли рассудить.
  2. Есть некоторые области, в которых кто-то разбирается очевидно лучше в силу опыта (ну, скажем, я в метаданных и генерации кода, а force — в сетевом взаимодействии и многопоточности). Тут всё просто.
  3. В крайне редких случаях я пользуюсь правом вето. Навскидку, пару раз за 10 лет вроде было. Это не потому что мы такие лапочки и живём душа в душу. Просто два умных человека, которые понимают, что идеальные решения — редкость и лучше принять хоть какое-то. А ещё, оба в курсе, что есть целый букет когнитивных искажений и иногда ты можешь быть совсем не прав, даже если уверен, что прав.

P.S. Извиняюсь за некоторый сумбур, не выспался...

Спасибо, коллега, за фидбек. Я даже как-то не ожидал.


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


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


Кстати, бардак этот почему-то получается похожий даже на международном уровне. У эстонцев, у американцев, у европейцев, у белорусов и даже у евреев одно и то же, одно и то же.


А ещё вы подсветили важную фигню, которую надо учитывать при проектировании систем, которые могут из маленького хеллоуворлда внезапно бомбануть в кастом-билт ERP: управление сложностью. И это не сводится к тупому "делать меньше классов". Это так же затрагивает управление информационной сложностью проекта — требования, грануляция, время сборки и тестирования.


Вот к этой байде я и пытаюсь адресоваться в своём фреймворке в частности. Типа а как нам писать проект 15 лет и не сойти с ума? Нужны какие-то императивы, хорошо если формальные. Ну хотя бы оговоренные устно (конвеншоны). Я над этим и пытаюсь работать, но сами понимаете — в двух словах о таком не напишешь. А даже если напишешь, то читать никто не будет.


Захватывать мир со своей архитектурой я не собираюсь. Первую привычку, которую отбивают 10 лет опыта за плечами — веру в серебряные пули. Я ставлю целью сделать удобный инструмент для себя лично, чтобы жопа не полыхала от каждого проекта. Ну и люблю C# и его систему типов до кровавых соплей, поэтому хочу показать как с помощью его фич можно городить что-то интереснее фасадов-бриджей-репозиториев. Разумеется это не "инструмент-который-всех-нас-спасёт", но если хотя бы пяток систем он убережёт от типичной индустриальной безблагодатности — я буду считать себя победителем.


Теперь по техническим моментам:


Про IoC

В IoC-ах все наступают на проблему грануляции, про которую вы пишете как "ну просто не делать слишком маленькие сервисы и слишком большие сервисы". Вот то, о чём я говорю — нет никакого понимания. Маленький — это сколько? И чего? Методов, сущностей, операций там… я не знаю. В итоге никто ничего не понимает и система сама скатывается в снежный ком. Предлагаешь архитектурные услуги — слышишь "ой нам не нужен архитектор, у нас ажайл". Тьфу. В Tecture я пытаюсь ФОРМАЛЬНО (на компайл-тайм проверках) работать с этим аспектом сложности. Хорошо ли, плохо ли — вот расскажу как именно это происходит в следующей статье — там и подискутируем с превеликим удовольствием. А пока что — ну то понятно, что если грамотно использовать IoC, то с ним нет проблем. Но кто его умеет грамотно использовать? Вы, я, ещё десяток-другой человек? А надо чтобы масштабировалось, понимаете...


зачем прямой доступ к БД

Нужен. И дело не только в батч апдейтах/инсертах. Я как-то даже на митапе дотнетчиков в Нске рассказывал что ORM по сути абстрагирует базу данных, простите, списком. Ну не убожество ли? А где удаление через CTE, а где денормализация, а где индекс-хэлперы? А ORM выставляет наружу интерфейс листа. Та ну тьфу! Короче, нужен SQL в базу, поэтому в Tecture он легализован соответствующей фичей, делается просто, безопасно и тестируется так же. Да да, я люблю подход "не можешь победить — возглавь".


А что не так с тестами на БД?

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


За сим откланяюсь. Ещё раз спасибо за фидбек.

Постараюсь ответить кратко, потому что двух статей за неделю я не осилю.
К тому же — да, что-то лучше обсуждать после второй статьи.


Но кто его умеет грамотно использовать? Вы, я, ещё десяток-другой человек?

Да ладно, всё значительно лучше. Просто негатив более ярко окрашен эмоциями, а когда всё в порядке — быстро забывается.


А где удаление через CTE, а где денормализация, а где индекс-хэлперы?

Может я везучий, а может упрямый, но как-то получалось обходиться без CTE. У нас в команде единодушное (вроде бы) мнение, что если понадобилось CTE, значит что-то не так…
Или как раз денормализация нужна. Кстати, не совсем понял про проблемы с денормализацией, но, это, наверное, тоже лучше после второй статьи обсудить.


Долго и дорого. Очень рад если в вашей компании могут позволить поднимать виртуалку на тесты и ждать сутки пока они прогонятся.

В принципе, создание БД на нескольких выделенных серверах работает достаточно быстро, поэтому до поднятия виртуалок не доросли ещё — для некоторых проектов просто используются два TeamCity — один на Linux, другой — на Windows.


Основные сценарии пока проверяются за несколько десятков минут (как я уже говорил — тесты производительности выполняются отдельно и пореже).


За сим откланяюсь. Ещё раз спасибо за фидбек.

Спасибо, и вам тоже. Тем более, за такой развёрнутый.

Последний раз, когда я увидел CTE в проекте, я заменил его на два джойна. Так что у меня также мнение, если вы начали их использовать, то скорее всего что-то не так :)
Просто если разработчики начинают использовать CTE, то им всё видится гвоздём.
А где удаление через CTE, а где денормализация, а где индекс-хэлперы? А ORM выставляет наружу интерфейс листа

В linq2db CTE есть, остальное не понял ;)
При использовании linq2db я уже и не помню когда не мог создать правильный SQL через LINQ.


Вам в конечном итоге придется решать BulkCopy, временные таблицы. Все это — время.

Угу. В Tecture прелесть в том, что BulkCopy можно легализовать в рамках отдельно взятого проекта и покрыть тестами все места с ним. Ну и моя разработка не отменяет linq2db ни коим образом. Можно его втащить спокойно. Займусь этим. Когда-нибудь.

OlegAxenow, спасибо за теплые слова о нашем детище linq2db. Команда с каких-то пор интернациональная и им тоже будет приятно.


Мой ник @sdanyliv на github если что.

Кстати, а почему вы (и вы лично и другие участники команды) о LINQ to DB на Хабре не пишете? Интересно же (и уверен, что не только мне).

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

А вы начните с провокационного описания какое именно говно царит в индустрии вокруг :)

Начните хотя бы просто с обзорной статьи и чем вы отличаетесь от Entity Framework.
Я про вас узнал на какой-то конференции, когда был рассказ про BLToolkit, а потом попробовал и затянуло. Думаю, многие просто не знают про то, что есть такой офигенный ORM.

Я подумаю, только это не так сразу.


Что задеть предлагаете? Я то слежу за EF Core репозиторием, только не использую. Вот и могу много болячек не рассмотреть. Если есть идеи, будет полезно узнать.

Я тоже не сильно слежу за EF, так что может они что-то и реализовали уже. Да и в linq2db мы используем не все возможности. В качестве идей:
1. linq2db — лёгкий ORM, который делает то, что сказано без всякой магии с трекингом изменений и волшебным сохранением непойми чего, непойми когда (context.SaveChanges())
2. Простые и понятные Insert/Update/Delete. Булки, которые могут работать по-разному в зависимости от провайдера
3. Интуитивные массовые операции (тот же апдейт по критерию)
4. Простые Poco классы для таблиц
5. Быстрый селект (некоторые используют Dapper для селекта, потому что EF медленный, тут не надо).
6. Понятные запросы в базу данных — т.е. они соответствуют тому, что написано, EF любит монстрячить жёсткости с вложенными селектами
7. Всякие оптимизации, типа CROSS APPLY и прочих, если потребовалось
8. Всякие хелперы, типа .LeftJoin, чтобы не писать станадартную linq магию с DefaultIfEmpty
9. Оптимизации под версии баз данных (не знаю, есть ли это у EF)
Помнится у меня были проблемы с fetch/include + paging + order одновременно. И в nhibernate (и с linq и с query over) и в ef. В ef вообще include дико медленный, больше условно 5 штук ужасно тормозит само построение запроса.

А еще хинты нельзя было применить, типа TABLOCK/TABLOCKX/XLOCK (в каждой базе свои)

И работа с иерархическими таблицами, благо в Oracle 10 были недокументированные функции, а в 11 их вывели в паблик, в MS SQL это было через CTE. Но в любом случае ни одна ORM все это добро не поддерживала, писал руками.

Что из этого ваш умеет? Не нашел в доках быстрым взглядом.

SqlServer хинты есть


var query = db.Patients.With(“NOLOCK”);

Но я планирую все это пересмотреть и закончить это раз и навсегда.
Если есть замечания или предложения пожалуйста коментируйте этот issue https://github.com/linq2db/linq2db/issues/2452


Какие там в функции в Oracle… Невозможно за всем уследить.

CONNECT BY

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


Помнится у меня были проблемы с fetch/include + paging + order одновременно. И в nhibernate (и с linq и с query over) и в ef. В ef вообще include дико медленный, больше условно 5 штук ужасно тормозит само построение запроса.

Эти детские болезни полечены

Предлагаю чуть другую точку зрения: интересная статья на Хабре => повышение интереса к продукту => потенциальные новые участники в команде => помощь, в том числе и с документацией ;)


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

Sign up to leave a comment.

Articles