Я вот искал себе в команду фронтендера — опросил десяток знакомых на предмет "а как их собеседовать?". Семеро мне сказали — "ищи того, кто хорошо шарит в JS, остальное фигня".
Ну вот знаете, всё ведь от человека зависит в конце концов. Можно и на голом JS запилить, а можно взять что-то чтобы поднять скорость.
Мне вот, короче, кажется что перед тем как использовать фреймворки — стоит какое-то время позапиливать сайтики на голом JS и HTML. Просто чтобы иметь представления о проблемах, которые фреймворки решают.
Согласен, должен. Однако в вашей декомпозиции есть минус куда более фатальный: протекающая абстракция.
Вы хотите обставить всё так что мол на сервере OrdersService и на клиенте OrdersService с теми же методами и работают они одинаково, но эта абстракция течёт по той причине что сериализовывать stateful-объекты вы не можете и если таковой возвращает серверный OrdersService, то при попытке вызвать этот метод на клиенте произойдёт ошибка. Уже не говоря о том, что надо помечать все модели DataContract-атрибутами и писать портянку WCF-конфигурации в конфиге, иначе чуда не случится. Плюс разный контракт ошибок: если нет связи с сервером то выкинется соответствующий эксепшон в то время как на сервере такового не может произойти по определению.
Вы как хотите, я (как уже было сказано) не претендую на создание единственно-верного-пути, но выступаю решительно против протекающих абстракций, полагая их несоизмеримо большим злом нежели неудобства кодогенерации.
на сервере: несколько каналов (база данных там, или что у вас), несколько сервисов для работы с ними.
на клиенте: один канал (Server), один аспект для WCF, но я бы сделал RPC-аспект (думаю над ним), одним из рантаймов которого был бы WCF (который уже мало кто использует).
На сервере пишем OrderService как показано в статье. Для клиента можем генерить обвязки к ним тем же самым tt, которые пускают запросы к серверу через аспект (чтобы работал перехват данных — см. следующую статью). Вероятно вместо tt можно использовать типы (надо смотреть на конкретику).
Клиентское приложение будет получать свой собственный OrderService, который сгенерен для него по образу и подобию серверного.
Что такое в вашей терминологии «клиентская логика»?
Вот это вот.
И, кстати, зависимость на сборку с сервером (или же промежуточную сборку с интерфейсами, необходимую и клиенту и серверу) нужна как раз в вашей схеме.
Да, логику с клиентской (а не серверной) логикой, содержащий набор сервисов, работающих с сервером как с каналом через WCF — придётся подключать к клиентскому приложению.
Послушайте, LINQ в Tecture — это сугубо opt-in ORM-аспекта. Вам не обязательно его использовать, но если используете то рантайм должен поддерживать его целиком. В данном случае я использую рантайм, основанный на EF для этого. Если бы у меня его не было — то я бы не смог запустить приложение.
В остальном — я ещё раз подчеркну что LINQ (а точнее его expression) прежде всего — это про конструирование запроса и хранение информации о нём. Не о выполнении. Выполнение LINQ-запроса легко делегируется другой части системы, что в ORM-аспекте и сделано.
Кстати, вы не задумывались сделать их instance методами?
А в чём профит? Я вижу в такой декомпозиции два возможных профита:
моки. В Tecture не нужны (см. следующую статью);
возможность изменить реализацию ОДНОГО КОНКРЕТНОГО запроса. Такая необходимость происходит крайне редко, но и в Tecture она возможна — LINQ-запросу можно задать маркер (скажем через тот же метод .Describe) и уже на уровне рантайма вставить хук — мол — если пытаются выполнить такой-то запрос — сделать вот так-то.
Просто если следовать этой логике, то и методы в духе Where, Select, Join в LINQ так же стоило бы сделать instance-методами, но MS так не сделали по одной простой причине: смысл такой декомпозиции стремится к нулю при гигантских накладных расходах.
Методические рекомендации к использованию вашей архитектуры требует вынесения всех запросов в слой query или допустимо фигачить мимо этого слоя?
Нет "слоя" query. Слоёв по сути три — приложение, аспекты и рантайм. Запросы и команды — это разделение как бы "по другой оси координат". Т.е. есть часть системы, где пишут, а есть — где читают. By design у вас просто нет возможности нафигачить "мимо слоя". То есть можно, но это довольно сложно и вряд ли пользователь будет нарочно извращаться чтобы это сделать.
var b = new TectureBuilder();
b.WithChannel<Server>(x=> { x.UseServer("127.0.0.1", 8080); });
var tecture = b.Build();
tecture.Let<OrdersService>().AddLine();
Проблема-то достаточно старая, решение такое лежит на поверхности, и тоже уже давно есть реализации
А можно ссылку? А то что-то мне не по глазам.
Так же подчеркну: я не делаю что-то принципиально новое и уникальное: просто раскладываю по полочкам старое и существующее.
To().Add(new Order()) отвалится
Он не может отвалиться потому что, простите, ничего не делает кроме создания команды и заталкивания её в очередь.
В ходе процедуры накатки этой очереди, возможностей отменить изменения представляется куда больше. Над этой архитектурной возможностью и политиками отката изменений из очереди я сейчас как раз думаю. Но если у вас есть по крайней мере список изменений (если угодно — лог кросс-системной транзакции), то откатить её становится значительно проще чем при любой другой реализации. Поправьте меня если я ошибаюсь.
Более того — уже в этой версии фреймворка можно реализовать простенький адаптер транзакций и если все системы, с которыми вы работаете — транзакционные, то при ошибке записи в любую из них произойдёт откат транзакций для всех. Это то, что уже работает. Как раз на случай если этот механизм не годится — я думаю над добавлением возможности покомандного откатывания, но пока сомневаюсь что это нужно.
Сложность-то лежит как раз в разработке каналов-адаптеров
Я и не отрицаю. Задача состояла в том, чтобы чётко разделить реализацию каналов-адаптеров и реализацию бизнес-логики. Не вижу ни одной причины, по которой можно сказать что эта задача не решена.
Если вы декомпозируете работу с внешними каналами по моей схеме — у вас сразу появляется чёткое понимание что надо сделать: реализовать раннеры для таких-то команд, реализовать методы таких-то аспектов для запросов. Хоть в джиру готовые задачи закатывай.
То есть можно работать по крайней мере в понятных примитивах, а не нырять в неизвестность при встрече с каждой внешней системой.
В клиентском приложении всё так же — у вас есть один канал (сервер) + по вкусу локальный кэш — второй канал. Не вижу противоречий. WCF/SOAP — это уже вкусовщина на уровне рантайма в моей терминологии.
Всю первую часть предыдущей статьи я рассказывал, что канал может абстрагировать любую внешнюю систему. Странно, что вы увидели только БД. Тем более странно, что вы увидели только одну БД, когда я говорю что в качестве рантайма используется EF.Core, который поддерживает как минимум три базы.
И вся логика начинается с фразы From<Db>
С From<Db> начинаются только запросы и только к базе данных. Только к той, которую вы привязали к каналу Db.
это просто иная разновидность EF
Мимо. Обёртка для DirectSQL, кстати, есть.
очереди СМЭВ
Делаете аспект СМЭВ, подключаете его к любому каналу и погнали.
В остальном — см. вот этот комментарий и мой ответ на него.
Ещё есть using без скоупа с последних версий C#. Я вот в тестах использую такой. В общем есть много способов обыграть, аж глаза разбегаются. И это главное. :)
Я думал о том, чтобы добавить такую опцию, но забегался и забыл. Сделать такую обёртку нетрудно руками.
Но это в любом случае opt-in, потому что имея BeginTrace/EndTrace проще вычленять хитрые куски логики, когда например начало в контроллере, а конец — внутри сервиса до первого сэйва.
Я всё делаю из предположения что логики у вас будет в любом случае больше, чем каналов и аспектов, так что на практике эта ситуации видится мне зело маловероятной.
Или потянут?
Я сделаю всё от меня зависящее чтобы потянули. Сейчас немного разгребусь с текучкой и напишу детальные пошаговые инструкции-чеклисты по написанию аспектов и рантаймов. Мне сложно оценить сложность такой задачи — как по мне она довольно лёгкая и творческая. Плюс аспекты вполне могут кочевать из проекта в проект — почему нет?
В общем, мне сложно представить как именно это будет работать вживую, но вроде проблем быть не должно — структура система всё ещё разборная, а значит базовая гибкость будет.
В имейте в виду только что в него попадают все действия с каналами. То есть совсем все. Будет у вас кэш — факт записи в него тоже свалится в трейс. Хотя в целом трейс вполне можно отфильтровать руками и сделать свой Explain, включив туда только то, что интересно.
Насколько покрывают корнер кейсы
То, что я делаю называется data-driven testing или table-driven testing. Такие тесты не покрывают совершенно все corner case-ы — вы должны сделать это сами, но в смысле не руками, а подбить правильные тестовые данные и вводные, чтобы у вас проверялись все ветки. Ну то есть условно регистрируйте в своей админке юзера с одними правами — пробуете из теста залогиниться и удалить файл, захватываете занные. Заводите юзера с другими правами — пробуете то же самое ещё раз, захватываете данные.
На реальных проектах вам наверняка с этим поможет QA. То есть корнер-кейсы за вас не откроют, но уже открытые помогут оформить.
идеальный стэйктрейс вызова
Ну всё же не стоит использовать его как стектрейс — он тяжеловат будет в рантайме. Трейс — штука чисто на этапе разработки и дебага. Но я подумаю над тем, чтобы сделать облегченную версию. Слишком уж мне нравится, как этот автор пишет :)
Я вот искал себе в команду фронтендера — опросил десяток знакомых на предмет "а как их собеседовать?". Семеро мне сказали — "ищи того, кто хорошо шарит в JS, остальное фигня".
Ну вот знаете, всё ведь от человека зависит в конце концов. Можно и на голом JS запилить, а можно взять что-то чтобы поднять скорость.
Мне вот, короче, кажется что перед тем как использовать фреймворки — стоит какое-то время позапиливать сайтики на голом JS и HTML. Просто чтобы иметь представления о проблемах, которые фреймворки решают.
Я на чистом TS написал целый фреймворк для датагридов с 0 зависимостей. Что я сделал не так? :)
Согласен, должен. Однако в вашей декомпозиции есть минус куда более фатальный: протекающая абстракция.
Вы хотите обставить всё так что мол на сервере OrdersService и на клиенте OrdersService с теми же методами и работают они одинаково, но эта абстракция течёт по той причине что сериализовывать stateful-объекты вы не можете и если таковой возвращает серверный OrdersService, то при попытке вызвать этот метод на клиенте произойдёт ошибка. Уже не говоря о том, что надо помечать все модели DataContract-атрибутами и писать портянку WCF-конфигурации в конфиге, иначе чуда не случится. Плюс разный контракт ошибок: если нет связи с сервером то выкинется соответствующий эксепшон в то время как на сервере такового не может произойти по определению.
Вы как хотите, я (как уже было сказано) не претендую на создание единственно-верного-пути, но выступаю решительно против протекающих абстракций, полагая их несоизмеримо большим злом нежели неудобства кодогенерации.
В этой ситуации делается так:
На сервере пишем OrderService как показано в статье. Для клиента можем генерить обвязки к ним тем же самым tt, которые пускают запросы к серверу через аспект (чтобы работал перехват данных — см. следующую статью). Вероятно вместо tt можно использовать типы (надо смотреть на конкретику).
Клиентское приложение будет получать свой собственный OrderService, который сгенерен для него по образу и подобию серверного.
Вот это вот.
И, кстати, зависимость на сборку с сервером (или же промежуточную сборку с интерфейсами, необходимую и клиенту и серверу) нужна как раз в вашей схеме.
Да, логику с клиентской (а не серверной) логикой, содержащий набор сервисов, работающих с сервером как с каналом через WCF — придётся подключать к клиентскому приложению.
Послушайте, LINQ в Tecture — это сугубо opt-in ORM-аспекта. Вам не обязательно его использовать, но если используете то рантайм должен поддерживать его целиком. В данном случае я использую рантайм, основанный на EF для этого. Если бы у меня его не было — то я бы не смог запустить приложение.
В остальном — я ещё раз подчеркну что LINQ (а точнее его expression) прежде всего — это про конструирование запроса и хранение информации о нём. Не о выполнении. Выполнение LINQ-запроса легко делегируется другой части системы, что в ORM-аспекте и сделано.
А в чём профит? Я вижу в такой декомпозиции два возможных профита:
.Describe
) и уже на уровне рантайма вставить хук — мол — если пытаются выполнить такой-то запрос — сделать вот так-то.Просто если следовать этой логике, то и методы в духе
Where
,Select
,Join
в LINQ так же стоило бы сделать instance-методами, но MS так не сделали по одной простой причине: смысл такой декомпозиции стремится к нулю при гигантских накладных расходах.Нет "слоя" query. Слоёв по сути три — приложение, аспекты и рантайм. Запросы и команды — это разделение как бы "по другой оси координат". Т.е. есть часть системы, где пишут, а есть — где читают. By design у вас просто нет возможности нафигачить "мимо слоя". То есть можно, но это довольно сложно и вряд ли пользователь будет нарочно извращаться чтобы это сделать.
Нет. В
Server
и его аспекты вы оборачиваете всю коммуникацию с сервером поверх WCF или что там у вас.Будет что-то в духе
А можно ссылку? А то что-то мне не по глазам.
Так же подчеркну: я не делаю что-то принципиально новое и уникальное: просто раскладываю по полочкам старое и существующее.
Он не может отвалиться потому что, простите, ничего не делает кроме создания команды и заталкивания её в очередь.
В ходе процедуры накатки этой очереди, возможностей отменить изменения представляется куда больше. Над этой архитектурной возможностью и политиками отката изменений из очереди я сейчас как раз думаю. Но если у вас есть по крайней мере список изменений (если угодно — лог кросс-системной транзакции), то откатить её становится значительно проще чем при любой другой реализации. Поправьте меня если я ошибаюсь.
Более того — уже в этой версии фреймворка можно реализовать простенький адаптер транзакций и если все системы, с которыми вы работаете — транзакционные, то при ошибке записи в любую из них произойдёт откат транзакций для всех. Это то, что уже работает. Как раз на случай если этот механизм не годится — я думаю над добавлением возможности покомандного откатывания, но пока сомневаюсь что это нужно.
Я и не отрицаю. Задача состояла в том, чтобы чётко разделить реализацию каналов-адаптеров и реализацию бизнес-логики. Не вижу ни одной причины, по которой можно сказать что эта задача не решена.
Если вы декомпозируете работу с внешними каналами по моей схеме — у вас сразу появляется чёткое понимание что надо сделать: реализовать раннеры для таких-то команд, реализовать методы таких-то аспектов для запросов. Хоть в джиру готовые задачи закатывай.
То есть можно работать по крайней мере в понятных примитивах, а не нырять в неизвестность при встрече с каждой внешней системой.
В клиентском приложении всё так же — у вас есть один канал (сервер) + по вкусу локальный кэш — второй канал. Не вижу противоречий. WCF/SOAP — это уже вкусовщина на уровне рантайма в моей терминологии.
Вроде натягивается и даже не как сова на глобус.
Мне этот подход чем-то де-факто напомнил… FoxPro на NoSQL. Прикольно.
Всю первую часть предыдущей статьи я рассказывал, что канал может абстрагировать любую внешнюю систему. Странно, что вы увидели только БД. Тем более странно, что вы увидели только одну БД, когда я говорю что в качестве рантайма используется EF.Core, который поддерживает как минимум три базы.
С
From<Db>
начинаются только запросы и только к базе данных. Только к той, которую вы привязали к каналуDb
.Мимо. Обёртка для DirectSQL, кстати, есть.
Делаете аспект СМЭВ, подключаете его к любому каналу и погнали.
В остальном — см. вот этот комментарий и мой ответ на него.
Ещё есть
using
без скоупа с последних версий C#. Я вот в тестах использую такой. В общем есть много способов обыграть, аж глаза разбегаются. И это главное. :)Спасибо.
.Annotate
— extension-метод для команды (как и.Describe
для запроса) — вы можете без труда ввести свою систему именования прямо поверх них.Можно использовать
#ifdef
Я думал о том, чтобы добавить такую опцию, но забегался и забыл. Сделать такую обёртку нетрудно руками.
Но это в любом случае opt-in, потому что имея
BeginTrace
/EndTrace
проще вычленять хитрые куски логики, когда например начало в контроллере, а конец — внутри сервиса до первого сэйва.Я всё делаю из предположения что логики у вас будет в любом случае больше, чем каналов и аспектов, так что на практике эта ситуации видится мне зело маловероятной.
Я сделаю всё от меня зависящее чтобы потянули. Сейчас немного разгребусь с текучкой и напишу детальные пошаговые инструкции-чеклисты по написанию аспектов и рантаймов. Мне сложно оценить сложность такой задачи — как по мне она довольно лёгкая и творческая. Плюс аспекты вполне могут кочевать из проекта в проект — почему нет?
В общем, мне сложно представить как именно это будет работать вживую, но вроде проблем быть не должно — структура система всё ещё разборная, а значит базовая гибкость будет.
С коллегой :)
Спасибо за отзыв.
В имейте в виду только что в него попадают все действия с каналами. То есть совсем все. Будет у вас кэш — факт записи в него тоже свалится в трейс. Хотя в целом трейс вполне можно отфильтровать руками и сделать свой Explain, включив туда только то, что интересно.
То, что я делаю называется data-driven testing или table-driven testing. Такие тесты не покрывают совершенно все corner case-ы — вы должны сделать это сами, но в смысле не руками, а подбить правильные тестовые данные и вводные, чтобы у вас проверялись все ветки. Ну то есть условно регистрируйте в своей админке юзера с одними правами — пробуете из теста залогиниться и удалить файл, захватываете занные. Заводите юзера с другими правами — пробуете то же самое ещё раз, захватываете данные.
На реальных проектах вам наверняка с этим поможет QA. То есть корнер-кейсы за вас не откроют, но уже открытые помогут оформить.
Ну всё же не стоит использовать его как стектрейс — он тяжеловат будет в рантайме. Трейс — штука чисто на этапе разработки и дебага. Но я подумаю над тем, чтобы сделать облегченную версию. Слишком уж мне нравится, как этот автор пишет :)
Сделаю и добавлю отдельно. Просто я ж гифки записывал, там немного "декорации" есть :)
Всё так, да.