Pull to refresh

Comments 108

Из статьи и исходников не вполне понятно:

  1. Какая целевая аудитория у проекта?
  2. Какой класс задач решается?
  3. На сколько лучше/быстрее/дешевле будут пилиться фичи и в каких единицах измеряется улучшение?
  4. Чем лучше этого
  5. И главное… Как же все описанные проблемы исправились

Спрашивали — отвечаем:


  1. Разработчики на C# (уровня мидл и выше), архитекты, тимлидеры
  2. Решается задача системного дизайна типичного бизнес-приложения
  3. Я предоставлю эту информацию как только вы дадите мне достаточно денег на проведение полноценного PoC-тестирования на нескольких проектах и командах :)
  4. Тем, что к web-у не имеет никакого отношения.
  5. Для вас русским языком написано: "статья рассказывающая про реализацию пока что находится в редактуре"
3. Ну так не годится. Зачем мне давать денег на исследования, если есть продукты-субституты? Вы же попросили подискутировать. Я судя по ответам на 1 и 2 — ваша ЦА. Вам выгодно, чтобы ЦА интересовалась продуктом, контрибьютила может даже. Иначе зачем публиковать наработки?
4. Таки прям никакого
3. Сожалею, у меня нет богатых родственников, готовых меня кормить полгода пока я пишу огромную документацию, у меня нет компании, готовой выделять команду под разработку и обкатку моих проектов, я делаю всё сам. Поэтому боюсь что у вас нет опций кроме как ждать. Ну или клонировать репозиторий, потыкать код и оценить границы применимости используя свою собственную квалификацию.

4. Вы открыли плейграунд-проект, который используется для обкатки фреймвока с группой добровольцев. Этот проект иллюстрирует возможность применения фреймворка для web-приложений. После обкатки этот кусок уйдёт в отдельный репозиторий.
Окей, понял, мы видимо плохо начали. Вот развёрнутый ответ:

3. Я ориентируюсь на два критерия: количество фич в условную единицу времени — в неделю там, в месяц или сколько там длится спринт… И на стабильность/верифицируемость получающегося решения в долгосрочной перспективе — предотвращение багов и сокращение регрессии.

Повышение количества фич достигается базово путём сокращения количества кода, необходимого для реализации требуемой функциональности и раннего устранения багов посредством некоторого числа compile-time проверок благодаря активному использованию системы типов C#. Конкретно же это достигается путём упрощения структуры приложения для разработчика, сведения к минимуму количества нерелевантных абстракций при сохранении необходимого уровня контроля, гибкости и создания удобных точек расширения во всех местах, в которых расширение может потребоваться. Таким образом, задача создания инфраструктурного кода с разработчика снимается настолько, насколько это возможно, позволяя сконцентрироваться непосредственно на бизнес-логике.

Простота структуры обуславливается разделением всего приложения на команды и запросы: эту концепцию я позаимствовал из CQRS, только адаптировал под внутреннее использование в приложении. Команды складываются в очередь исполнения, которая начинает раскручиваться при вызове `Save`, а запросы производятся непосредственно. За счёт правильного абстрагирования тулинга запросов у источников данных — отпадает необходимость строить леса абстракций для запросов (e.g. репозитории) в пользу написания оных в форм-факторе метода расширения для источника данных (своеобразная условно-чистая функция). Методы запросов могут быть generic (даже с констрейнтами): по задумке они должны цепляться к «запросному» концу канала благодаря — опять же — системе типов C#. Вот так я убираю примерно половину абстракций и IoC-записей у системы — путём вынесения запросной функциональности в бесконтекстное пространство. Идея диктуется самим LINQ в C# (методы .Where, .Any и т.п.), которую я немного модифицировал, обыграв абстракцию для запроса (хорошо наблюдается на примере ORM-фичи).

При том Tecture не пытается инкапсулировать в себе весь окружающий мир, реализовать свой собственный ORM, или там очередь сообщений, whatever. Он — просто удобная абстрактная прокладка между бизнес-логикой и внешним миром, которая ни коим образом не запрещает вам использовать фичи любого существующего фреймворка, если вдруг у вас возникнет такая необходимость. Точек расширения оставлено достаточно, чтобы вы без разрыва головы могли сами впаять поддержку того, что считаете нужным. Внутри Tecture этот оборот называется Feature, но я подумываю переименовать его в «Aspect», потому как звучит понятнее. O/CP во всей красе, как его вижу я.

Вот я сделал поддержку ORM и реализацию к ней на EF.Core. Не стану скрывать что основные абстракции и внешний дизайн EF.Core мне импонируют, поэтому при разработке ORM-фичи я смотрел именно на него, плюс немного на Dapper и маленький ORM от ServiceStack. SqlStroke — моя авторская задумка, так же хорошо зарекомендовавшая себя в ряде проектов. Этим не ограничивается, можно запилить что-нибудь ещё — очереди, емейлы, файлы… На любой вкус и цвет.

Повышение стабильности решения достигается посредством генерации data-driven регрессионных unit-тестов с автоматическим захватом тестовых данных. Фактически, стоимость создания и обслуживания подобных тестов при использовании фрейморка — нулевая. По задумке это должно побудить разработчиков писать больше meaningful-юнит тестов, которые прекрасно будут встраиваться в code coverege и live coverage-тулы вроде NCrunch, что позволяет в удобной форме сохранять метаинформацию о работоспособности бизнес-логики на конкретных кейсах, а портянки тестов при необходимости можно перегенерировать.

О конкретных цифрах говорить пока рано: некий прототип этого решения уже использовался на живом проекте для одного заказчика и показал себя весьма эффективно. Из нашего кода исчезло 90% репозиториев, но появились длинные бесконтекстные extension-методы, с которыми, тем не менее, было достаточно легко работать просто потому что они располагались целиком в отдельном файле и были названы правильно. Это всё, что я могу сказать на эту тему на текущий момент. Именно с этой целью в репозитории есть Playground-проект, дабы все желающие могли попробовать фреймворк «в деле». Это общая песочница, в которой нет никаких ограничений на чистоту кода и корректность используемых подходов, ибо как целью ставится — поставить фреймворк в неудобное положение. Сейчас на этот проект по мере наличия свободного времени и желания смотрят мои коллеги — я добавил их как контрибьюторов, но в виду добровольности всего происходящего время выделяется по остаточному принципу, поэтому быстрых результатов ждать не стоит. Глупо на этой страдии требовать полного отчёта с цифрами, сравнения с другими фреймворками, детальной документации и упаси б-же записей выступления с конференций, например — просто потому что бюджета на проект нет никакого, разрабатывается он моими силами.

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

4. Нет нет и ещё раз нет. К web-у проект не имеет СОВЕРШЕННО никакого отношения. Ни к web, ни к микросервисам, ни к чему. Он может использоваться и в web-приложениях, и в консольных, и в десктопных (хотя в основном задумывался для backend-части приложений), но в дизайне решения не предусмотрено эксплицитно никаких специальных для этого инструментов. Зависимостей у ядра проекта — 0. Он не требует от вас установки yarn, node, использования ASP.NET MVC и вообще ничего за собой не тянет, потому как не пытается быть комбайном-для-всего, коим является, скажем, Spring. Целью ставилось создать минимально-необходимый набор абстракций, от которого будет удобно плясать и попытаться поплясать в каком-то конкретном направлении — например в направлении работы с БД и оценить возможность использования разработки, скажем, в web-е.

Playground-проект, на который вы смотрите — это попытка понять насколько дорого выходит прикрутить Tecture к шаблонному ASP.NET MVC-приложению. Насколько удобно использовать его оттуда, составить общее понимание о том, как меняется внешний вид кода и его логика, да и просто чтобы попробовать сделать что-либо законченное-энд-юзерское а не ковыряться в абстракциях до посинения. Playground-проект, само собой не является частью поставки решения и создан в исследовательских и калибровочных целях. Сторонний же наблюдатель может пощупать его руками, дабы оценить на практике преимущества и недостатки. У меня стали просить ссылку на «пощупать» — я её и дал. Однако сейчас я понимаю что без контекста она не то чтобы сильно полезна.

Дайте угадаю… потому что каркас, предусматривающий решение описанных проблем написан на C#? :)

Следующей статьёй будет детальное демо. Playground-проект можно поглядеть тут

Глянул playground. Хм, пока что это очень похоже на какую-то обертку над ORM, вроде EF, но думаю, я что-то не так понял, отвык от c# уже пару лет как. Подпишусь, жду демку. Спасибо!

Заходите если вдруг что.

Покритиковать можно?
Мне не понравилось. Очень много абстракций.
Я тоже недавно переосмысливал как выстраивать архитектуру. И сделал бы иначе:
  • _tecture.BeginTrace(); — это должен быть middleware, что бы ручками не прописывать это в каждом методе каждого контроллера. И хорошо бы тут использовать уже готовые реализации opentracing.
  • похоже что все контроллеры кладутся в папку Controllers, аналогично с dto — как насчёт подхода «одна фича — одна папка»? Внутри папки Nomenclature будет контроллер и его Dto. И сервисы.
  • Еще можно не делать абстракций над Orm. Заложиться на то, что у нас всегда efcore. Он тоже умеет Raw SQL Queries. В коде работать напрямую с DbContext. Для тестирования — sqlite in-memory. Но вообще вот так я бы не стал писать
    To<Db>().SqlStroke<ResourceSupplyItem>(x => $"DELETE {x.Alias()} FROM {x} WHERE {x.ResourceSupplyId == id}");

  • Чего я не увидел — это как жить с микросервисами. Все retry должны быть безопасными, а значит нужна идемпонентность вызовов между микросервисами.
  • Вы в неявном виде полагаетесь на распределенные транзакции от MSDTC? Не уверен что это подойдет многим.

Вы декларируете «хочу так, чтобы сделать правильно было просто». Но разобраться в кишках этого Reinforced.Tecture непросто.
ИМХО.
Вы декларируете «хочу так, чтобы сделать правильно было просто». Но разобраться в кишках этого Reinforced.Tecture непросто.

А зачем вы разбираетесь в его кишках?


_tecture.BeginTrace();

Этот вызов делает не то, что вам кажется.


Заложиться на то, что у нас всегда efcore

Если я захочу сделать каркас, на который невозможно будет мигрировать ни одно существующее приложение — так и сделаю :)


Но вообще вот так я бы не стал писать

Почему?


Чего я не увидел — это как жить с микросервисами

Я что-то не увидел, где я сказал про микросервисы. Может вы приведёте цитату?


Вы в неявном виде полагаетесь на распределенные транзакции от MSDTC

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

А зачем вы разбираетесь в его кишках?

А как понять как оно должно использоваться?
Я что-то не увидел, где я сказал про микросервисы. Может вы приведёте цитату?

Так и я о том что про микросервисы ничего нет.
Так и я о том что про микросервисы ничего нет.

А почему здесь должно быть что-либо про микросервисы? Статья и фреймворк — про архитектуру приложения, а не про деплоймент.


А как понять как оно должно использоваться?

Давайте я вам расскажу до выхода статьи, буквально скопипастив кусок :)


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


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


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

Извиняюсь, нооо…
Если метод делает не то, что кажется — возможно, как минимум с именованием что-то не так? И поскольку нет никакой документации и объяснений (про статью в редактуре давайте забудем, ее здесь нет, когда будет — неизвестно) стоило бы сказать, в чем именно человек ошибся, а не отправлять его шерстить исходник очередного "прорывного продукта"?


Ровно такой же вопрос про те же микросервисы. В статье есть запрос на фидбек, в комментах есть тот самый фидбек и достаточно таки агрессивный ответ. А ведь человек банально поинтересовался о том, что болит.


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

Ну а если статья и правда на редактуре — что делать? :)


Ровно такой же вопрос про те же микросервисы

Но написанное (что в статье, что в коде) не имеет ровным счётом никакого отношения к микросервисам. Я не заявляю Tecture как фреймворк для работы с микросервисами — это всего лишь каркас для организации логики. Откуда микросервисы, почему микросервисы, зачем микросервисы? :)


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

Человек сам пошёл смотреть исходники кишок проекта. Я ещё и удивился зачем он это делает — там много не самого очевидного кода и куча internal-штук. Если их прочитать — то понимания не прибавится. Только и всего. Решительно no offence.

Откуда микросервисы, почему микросервисы, зачем микросервисы?
Часто приходится с ними жить, что влияет на выбор инструментов.
UFO just landed and posted this here
Мы такое обычно делаем с использованием очереди сообщений, дабы не грузить базу данных лишний раз.
UFO just landed and posted this here
Да, но не в смысле Transaction Scope, а чуть более хитрым механизмом. Обычно до моего прихода на проект — емейлы отправляют прямо из треда веб-запроса. Я конечно потом исправляю на очередь, но бизнес не то чтобы сильно хочет в это вкладываться пока тупить всё не начинает.

И да, я видел проекты где отсылаются именно миллиарды сообщений в день :)
UFO just landed and posted this here
Зависит от очереди. В случае с MSMQ например очередь вполне попадает в Transaction Scope. В случае с реббитом — ну приходится внутри приложения пилить механизмы для отложенной записи.
UFO just landed and posted this here
Я это знаю. Равно как и знаю что TransactionScope — это прокси-объект для общения с MSDTC. Вы это хотели сказать?
UFO just landed and posted this here
Я вам и ответил: зависит от используемой в проекте очереди сообщений. Не все они поддерживают MSDTC.

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

UFO just landed and posted this here

Это типо nestjs что ли, только для с#?

Нет. nestjs для C# называется ASP.NET MVC

Но структура проекта в playgrounde такая же

Потому что это структура стандартного ASP.NET MVC-проекта. Смотреть надо в папку Domains например.

Ок. Подожду тогда Вашу статью с детальным разбором

UFO just landed and posted this here
Посмотрел сорцы — идея показалась интересной. Жду продолжения!

Спасибо на добром слове. Продолжение делается, но на всё рук не хватает.

В мире шарпа я вообще ничего не знаю, но ради интереса посмотрел код и… останусь я пожалуй в мире джавы и дальше, где все просто и нет у меня боли когда использую Spring или Quarkus или Guice.


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


Вот такой класс ошибки это нормально? Где информация типа код ошибки, sql вызвавший ошибку и тд.


У нас принято использовать liquibase или flyway и мне больно видеть вот такое


Метод в сервисе public IAddition<MeasurementUnit> CreateMeasurementUnit(string name, string shortName) — а может стоит использовать домен модели или ДТО какой то? Вы расказываете как все плохо сейчас, но вот такой код приводит что CreateMeasurementUnit("Name #1", "Name #1") непонятно что он принимает просто глядя на метод, легко перепутать аргументы.


Вот этот код


if (Aux.IsTracingNeeded)
            {
                if (!Aux.IsEvaluationNeeded)
                {
                    Aux.Query(hash, "test data", "ORM Addition PK retrieval");
                }
                else
                {
                    Aux.Query(hash, result, "ORM Addition PK retrieval");
                }
            }

что он вообще делает в Features/Reinforced.Tecture.Features.Orm.Query.cs, как это относится к методу, или SOLID это тоже плохо? А что будет если у меня пойдет мультипотоковость и я параллельно буду операции делать ?


Для меня код на Хаскеле понятней чем ваш на шарпе. И я точно бы не захотел чтобы мне пришлось работать с таким велосипедом.

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


По классу ошибки — очевидно, он ещё в разработке, как и сам инструмент


По миграциям — это во-первых, автогенеренный код, во-вторых, стандартный подход для EF Core, в-тертьих этот код лежит в плейграунде — который как бы существует для демонстрации фреймворка — который не включает в себя обязанность обработки миграций


По трейсингу в queries — тут согласен, надо бы вынести в отдельный сервис. С другой стороны, в любом коде, который я когда либо видел что-то куда-то надо бы вынести

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

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


он ещё в разработке, как и сам инструмент

проблема в том что я смотря на код и примеры в плейграунде не вижу легкости и желания работать с этим инструментом. Вот пример спринг дата с еще накрученым кешом сверху. Любой программист поймет что здесь к чему и проще уже некуда.
А у вас то вылазят статик методы а значит все гвоздями прибито, или как вкорячить Редис для персистент стораджа?
Что будет если я два раза добавлю where тут


public IQueryFor<TEntity> That(Expression<Func<TEntity, bool>> @where)
        {
            _thatExpressions.Add(@where);
            return this;
        }

или вот здесь зачем приведение к типу? это уже bad smell для меня.


internal T Key<T>(IAddition<IPrimaryKey<T>> keyedAddition)
        {
            var a = (Add)keyedAddition;
...

Такой код вообще подразумевает что речи о конкурентных запросах не может идти тк толку от проверки на уникальность в коде нет.


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

Простите, я правильно понимаю что вы пишете на Java, но пытаетесь проревьюить код на C#?

Не только на Джава, но к Шапру имею отдаленное представление. Зачем глубоко понимать Шарп если вот здесь явно видно что вы идете в базу проверяете нет ли такого measurement unit а потом сохраняете. И это неверно тк вы не сможете гарантировать что после того как вы проверили данные другая транзакция не вставит такой же юнит.

Так это же опять плейграунд. Сам фреймворк вполне позволяет реализовать это по-другому

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


public void RenameMeasurementUnit(int id, string name, string shortName)
        {
            var unit = From<Db>().Get<MeasurementUnit>().ByIdRequired(id);
            unit.Name = name;
            unit.ShortName = shortName;
            To<Db>().Update(unit, x => x.Name, x => x.ShortName);
        }

где ДТО, почему вместо них просто строки?
как добавить кеш и ее инвалидацию?
как сделать оптимистик локинг?
почему вы считаете что это From<Db>().Get<MeasurementUnit>().ByIdRequired(id) гораздо лучше и проще чем measurementUnitRepository.getById(id). Потому я считаю что Сервис класс не должен знать про никакой <Db> это не его забота как получают и сохраняют MeasurementUnit а забота репозитория для этого обекта. Потому что однажды вы захотите вынести какую нить сущность в Монго например и вот мы начинаем менять по всем сервисам From<Db> на From<Mongo>


Если статья называется Я десять лет страдал от ужасных архитектур в C# приложениях — и вот нашел, как их исправить то код должен быть чтобы комар носа не подточил.

где ДТО

Если вы видите принципиальную разницу, то я вам его с радостью сделаю. Сути происходящего это не изменит.


как сделать оптимистик локинг?

Рантайм для этого кода — EF.Core. Оптимистик локинг — стандартная фича EF, настраивается в DbContext-е с указанием ConcurrencyToken для сущности, что полностью абстрагировано от бизнес-логики и код Tecture не может и не должен влиять на это обстоятельство. Я полагаю, документация по EF расскажет об этом лучше и подробнее чем я.


почему вы считаете что это From<Db>().Get<MeasurementUnit>().ByIdRequired(id) горазда лучше и проще чем measurementUnitRepository.getById(id)

Потому что в первом варианте используется generic extension метод, а не метод инстанса. ByIdRequired определён один раз для всего приложения и всех сущностей. Для того, чтобы сделать такой запрос мне не надо создавать экземпляр measurementUnitRepository и регистрировать его в контейнере — ровно то, о чём я писал в статье когда говорил про длинные портянки IoC. Конечной целью этого приёма является сокращение количества записей в IoC-контейнере и упрощение инстанцирования служебных штук необходимых для запроса в БД. Tecture обеспечивает таким вызовам должные возможности для мокинга на этапе тестирования, а функциональная природа происходящего позволяет нам сократить количество абстракций до одной (собственно, функция).

Почему вместо них что?

поправил :) — почему вместо них просто строки.


Я полагаю, документация по EF расскажет об этом лучше и подробнее чем я.

спасибо за разъяснение.


ровно то, о чём я писал в статье когда говорил про длинные портянки IoC.

Тем самым вы приколачиваете свои сервис классы к конкретной имплементации сторадж для конкретного ентити. И как только у вас возникнет нужда один (или все) перевести с <Db> на <Mongo> вам придется править весь проект включая тесты которые по хорошему для тестирования бизнес логики и не должны меняться.

Тем самым вы приколачиваете свои сервис классы к конкретной имплементации сторадж для конкретного ентити

Нет, сторадж — Generic-класс.


как только у вас возникнет нужда один (или все) перевести с <Db> на <Mongo>

Ладно, мне не сложно рассказать.


Db — это канал. Если вы посмотрите что это — то увидите что канал — это интерфейс без реализаций. Он экстендит generic-интерфейс CommandQueryChannel<,>, который определяет используемые при работе с базой примочки (мне сегодня сказали, что слово "аспект" поставит многое на свои места в этом объяснении).


В Playground-проекте определён пока единственный канал — канал работы с базой данных. В подключённых примочках у него — ORM и SqlStroke. Это значит что при вызове From<Db> в списке доступных методов у вас окажутся ORM-овские Add/Update/Delete и SqlStroke (они подсасываются к промежуточному ковариантному интерфейсу как extension-ы). Так же список доступной функциональности канала регулируется типа-параметрами сервиса, где по задумке вы должны эксплицитно указать изменения каких сущностей производит сервис, в котором расположен данный метод: уберите оттуда MeasurementUnit — и код перестанет собираться.


Я так понимаю, что ваш вопрос состоял в том, что случится при переходе к Mongodb. Отвечаю: скорее всего у вас поменяется набор плюшек у канала, потому что Mongo не является реляционной БД и работать с ней надо другими средствами. Однако же, если вам удастся придумать удачную абстракцию, за которой вы спрячете и объектную и реляционную БД, то мои поздравления (у меня не получилось) — вам останется просто переключить рантайм с EF на Mongo.


К слову — Db это просто название канала. Оно может быть любым, хоть Barrel. Поведение канала определяется только набором поддерживаемых фич, что технически выражается в эстенде CommandQueryChannel<,> с разными тип-параметрами. Так же есть возможность заэкстендить только CommandChannel<> или только QueryChannel<>, оставив от аспекта только возможность записи или чтения соответственно.


Как-то так, если кратко.

Ладно, мне не сложно рассказать.

Первая часть вашего комментария отличная. Спасибо


Я так понимаю, что ваш вопрос состоял в том, что случится при переходе к Mongodb. Отвечаю: скорее всего у вас поменяется набор плюшек у канала, потому что Mongo не является реляционной БД и работать с ней надо другими средствами.

Вот поэтому у нас и есть объект репозиторий который скрывает имплементацию и все сервисы с ним работающие не знают ничего про его внутренности. А у вас получается что архитектура намертво сбитая где изменения одного слоя автоматически означают изменения в других слоях. Что в моем понимании не есть идеальная архитектура.


И на моей практике надо было делать как полную миграцию с Монги на Постгрес так частичную — часть мы пишем в Постгрес, а часть в Редис. И это было не больно, когда нормально сделаные слои и IoC.

И на моей практике надо было делать даже не полную миграцию с Монги на Постгрес а частичную — часть мы пишем в Постгрес, а часть в Редис.

А здесь это просто не нужно. Повторяю: ByIdRequired — это статический extension-метод. Он посасывается в требуемым инстансам по типу, пользуясь соответствующей языковой фичей C#.


Если часть приложения нужна на постгре, другая — на редисе, то вы просто добавляете ещё один канал для редиса, настраиваете ему необходимые плюшки и один раз реализуете Get<> и ByIdRequired для него одним Extension-методом, подпихивая туда свою абстракцию — и вуаля, у вас уже часть приложения на редисе, часть на постгре. Никаких проблем здесь нет, все изменения займут, я полагаю, строчек 20 кода.

Смотрите у вас код в сервисе вот такой From<Db>().Get<MeasurementUnit>().ByIdRequired(id) и Db может использоваться для работы еще с какими то ентити. Те мы жестко зафиксировали канал в коде. И теперь перенеся Unit ентити в Редис канал(который мы реализовали) мы получаем что все ентити по прежнему достаются из Db а за Юнитом надо идти уже по другому From<Redis>().Get<MeasurementUnit>().ByIdRequired(id)


Верно я понимаю?


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


А что делать если ентити надо писать и в Редис и ДБ? А читать из Редиса сперва а только потом из ДБ?

Верно я понимаю?

Верно, это один из вариантов решения.


Второй вариант состоит в том, чтобы добавить к Db возможности редиса и переопределить (где угодно) Get и ByIdRequired как Get<T>(this Db channel, ...) и уже внутри него различать что — в базу, что — в редис.


Можно сделать IStoredInRedis и сделать Get<T>(this Read<Db> channel,...) where T : IStoredInRedis, где задействовать Redis. Аналогично завести IStoredInDb и переформулировать Get<T> в том числе и для where T : IStoredInRedis, IStoredInDb. Это так, первое что в голову пришло.


Запись осуществляется через команды. Аналогичная система и с Write<Db> — его возвращает From<Db>. Можно порешать на уровне экстеншонов, можно порешать на уровне аспекта (у меня аспекты называются "фичи"). Но если вы умеете проектировать и реализовывать аспекты для Tecture, то вы вообще всё что угодно можете сделать вплоть до DSL-я пресонально для своей бизнес-логики (другой вопрос стоит ли оно того).


В остальном — типы. Очень много на типах и очень много работы переложено на компилятор.


И да, я на всякий случай явно говорю: если вам хочется, то вы можете повторить repository pattern на Tecture. Но вот Tecture на repository pattern-ах повторить уже невозможно.

Второй вариант состоит в том, чтобы добавить к Db возможности редиса и переопределить (где угодно) Get и ByIdRequired как Get(this Db channel, ...) и уже внутри него различать что — в базу, что — в редис.

Это уже не чистая архитектура.
Также у вас интерфейс начнет со временем разбегаться тк редиса захочется использовать чисто его плюшки.

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

Так вот на мой взгляд это все усложнит код вместо тупого и прямого использование репозиториев которые вы можете отнаследовать от какого то базового который дает стандартные методы get/save/…
Посмотрите на Спринг Дата Репозиторий там вообще кода может не быть даже SQL:


public interface OrderRepository extends PagingAndSortingRepository<Order, Long> {
    List<Order> findByCustomer(Customer customer);
}

и все — у вас есть для сущности Ордер весь стандартный набор операций плюс кастомный поиск по заказчику и все это гибко без статических методов и выдумывания что делать если надо изменить имплементацию метода.

Ну да, если забыть, что здесь все построено на рефлексии, которая генерит запрос на основании имени метода, то подход и правда выглядит неплохо

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

Так вот на мой взгляд это все усложнит код вместо тупого и прямого использование репозиториев которые вы можете отнаследовать от какого то базового который дает стандартные методы get/save/…

Тут это похожим образом работает. На самом деле, FromDb — это и есть своего рода репозиторий. Просто вместо обычных методов вы делаете на нем экстеншн-методы. По сути, вы можете для каждого репозитория написать свой Db, просто зависимости потом разрешаете не через IoC а через типы, либо подставляя в From конкретный Db, либо используя типовой параметр с ограничением по интерфейсу и указывая где-то на call-site уже конкретный тип (своего рода аналог внедрения по конструктору получится).

UFO just landed and posted this here
Потому я считаю что Сервис класс не должен знать про никакой Db

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


гораздо лучше и проще чем measurementUnitRepository.getById(id).

Тем, что композится. measurementUnitRepository.getById(id) можно использовать только в одном месте, в другом месте — придется писать свой новый measurementUnitRepository2.getById(id) или measurementUnitRepository.getById2(id). Get и ByIdRequired можно написать один раз и навсегда. Ну это известная проблема репозиторий-паттерна в принципе.


и вот мы начинаем менять по всем сервисам FromDb на FromMongo

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

Коллеги, я прошу прощения за столь простодушное вторжение в вашу беседу, но по моему скромному мнению проверка на дубль по имени в коде на C# при однозанчном наличии полноценной SQL СУБД в качестве хранилища — это неправильно в принципе.
Потому что такую вот простую проверку можно поручить СУБД, причем безо всяких хранимых процедур: достаточно просто добавить ограничение UNIQUE на поле. И после этого можно быть увереным, что никакой джун никогда это ограничение не сломает (разве что в DDL залезет).

Само собой! Однако же это не повод не делать просто SELECT EXISTS... в коде. Всяко информативнее и быстрее, нежели ждать пока прилетит Exception от базы. А база подстрахует констрейнтом в крайнем случае.

Это называется double check pattern :) Где проверка на условие осуществляется во lock free среде, а потом, если ограничение не сработало, тогда жестко на заблоченных объектах (в данном случае UNIQUE constaint на колонке таблицы).
Всяко информативнее и быстрее, нежели ждать пока прилетит Exception от базы.

Не согласен. Ведь если вы обнаружите нарушение уникальности с помощь SELECT EXISTS, то как вы будете извещать вызывающих из верних слоев, что они передали недопустимые данные? Подозреваю — что как обычно, вбросите исключение: throw new ЧтоТоТам(). В таком случае ничто не мешает поймать исключение из СУБД и вбросить вместо него то же самое исключение, которые было бы вброшено после SELECT EXISTS.
Вообще, мое мнение, что если делать все максимально универсально, «по уму» (не берусь судить, хватит ли у вас на это ресурсов), то ограничение следовало бы оформлять в виде атрибута для поля данных в классе модели. А уже реализация, в зависимости от наличия «плюшек канала», должна решить, как это организовать. Если это СУБД с ограниченями целостности на уровне DDL — через эти ограничения в коде создании таблицы. Если ограничений целостности нет, но есть (и достаточно дешевы) транзакции — то через предварительную проверку (вроде SELECT EXISTS) в рамках той же транзакции, что и вставка, если нет и этого — то через пост-проверку и корректирующее действие (DELETE, к примеру). Заодно в атрибуте можно через параметр его конструктора указать, насколько строго это ограничение следует проверять, если хороших средств для проверки нет.
Но ещё раз повторю, что такие вещи требует времени и сил для реализации, которых может не быть. Так что если вы не сочтете необходимымым реализовывать это (вообще или хотя бы «в следующей версии»), то это будет понято.
Когда надо сделать бизнес сценарий то можно взять или spock или cucumber или не заморачиваться и просто JUnit и вынести каждый бизнес сценарий в отдельный класс и разбить по тест методам на которые навесить аннотации очередности.

Что в целом ничем не лучше пресловутой портянки


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

Метод в сервисе public IAddition>MeasurementUnit> CreateMeasurementUnit(string name, string shortName) — а может стоит использовать домен модели или ДТО какой то? Вы расказываете как все плохо сейчас, но вот такой код приводит что CreateMeasurementUnit(«Name #1», «Name #1») непонятно что он принимает просто глядя на метод, легко перепутать аргументы.


Я бы в таком случае ограничился просто именованными параметрами, благо в C# они есть.
CreateMeasurementUnit(name:«Name #1», shortName:«Name #1») — и сразу понятно, что метод принимает, и уже параметры трудно перепутать.

А потом добавится еще три новых поля и вы пойдете переписывать все слои. Вместо того чтобы добавить эти параметры в ДТО и изменения затронут только те части где работы с этими новыми параметрами нужна.

Если у полей нет значений по умолчанию — то вам в любом случае придётся переписывать все слои, хоть с DTO, хоть без него.

Зачем мне переписывать логику сервиса и его АПИ если ДТО будет приходить такой же.
Везде по коду там где не надо использование этих новых полей ничего меняться не будет, так же не будет меняться АПИ.
А добавление столбцов в живую систему всегда подразумевает что null это нормальное значение или делается миграция и к существующим данным в базе добавляется нужная информация.

А зачем в таком случае вообще что-то переписывать — хоть параметры добавлять в метод, хоть поля в DTO — если вновь добавленные поля в БД никак не влияют на работу существующих сервисов?
В этом ведь и была изначальная идея СУБД, то, ради чего её вообще изобрели в древние времена — ради преимущества перед обычным файлом данных: преимущество СУБД было именно в том, что добавление новых полей данных в неё никак не влияло на работоспособность существующих программ, которые эти поля не используют.

В целом очень хорошо указан набор проблем, с которым мы сталкиваемся практически на ежедневной основе.
Чего в статье не хватило. Обычно после перечисления проблем ожидаешь увидеть хотя бы грубыми мазками или рамочно подход к решению упомянутых проблем.
Насколько я понял, автор планирует раскрыть тему в дальнейшем, получилась неудачная разбивка материала.
В целом же за направление исследований — большой плюс, это уже заявка на computer science.
Единственное, хотелось бы, чтобы всё не вылилось в классический xkcd standards.

Статья понравилась. Вот только не увидел в ней описание проблемы необходимости погружения разработчика в предметную область заказчика. Поясню. У моего знакомого начальство периодически требует, чтобы он составил техзадание на свой продукт, который давно и успешно эксплуатируется на предприятии. Он обычно футболит такие запросы, но когда сильно достают, то тогда отправляет в качестве техзадания Налоговый кодекс РФ. И начальство затыкается после этого на пару лет, так как понимает, что людей, способных перевести текст Налогового кодекса в нечто похожее на техзадание, просто нет, а даже если бы они и были, то разработка такого ТЗ всегда бы запаздывала по установленным государством срокам внедрения тех или иных налоговых новаций.

То есть, будучи чистым разработчиком, он не только выступает в качестве архитектора, но также и постановщика задач самому себе, работающего на основе постоянно меняющихся нормативных документов в сфере бухгалтерского учета и налогообложения. Результатом такого подхода является продукт, функциональность которого не могут даже в первом приближении воспроизвести наиболее раскрученные продукты фирм 1С или SAP.

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

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

Неожиданное совпадение: как раз об этом я и хотел поговорить в третьей статье — о налаживании общего языка между бизнесом и разработкой. Потому что эту проблему я так же пытался решить добавив человеко-читаемые трейсы происходящего и методы .Annotate/.Describe. На их использование уходит несравнимо меньше времени, чем на написание осмысленного комментария, но этот текст впоследствии будет использован в человеко-читаемом выводе трассировки, динамически меняющимся в зависимости от вводных данных.


Так вот. И если туда вставлять прям куски из ТЗ — то в итоге можно добиться того, что бизнес-логика сама о себе рассказывает свой бизнес-сценарий. Что произошло при таких-то и таких-то вводных. И этот текст по задумке может быть читаем как разработчиками, так и ребятами от бизнеса, что (опять же по задумке) может положительно повлиять на нахождение общего языка между теми и другими. Но тут уже я на техническом уровне ничего гарантировать не могу :)


В остальном: да, я понимаю и принимаю ваш аргумент, однако я постарался заложить extenstion point-ы в систему, через которые её можно адаптировать под конкретику бизнеса. Этот подход будет предусматривать написание не самого понятного и простого архитектурного кода, однако всё ещё не rocket science — любой толковый сениор должен справиться.


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

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

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

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

Схема хороша, но я вижу в ней изъян что она всё же опирается на одного конкретного и очень незаменимого человека. Bus factor слишком низкий, поэтому предположу что для стабилизации решения этой проблемы всё же придётся честно обслуживать базу знаний по проекту (включая теоретически-вводные статьи), всячески "шарить нолидж" (чему Tecture содействует by design) и особенно работать над процессами онбординга новых сотрудников (вплоть до включения в программу онбординга лекции по экономике).


Но кабы всеми проектами хорошо управляли — так глядишь и половины описанных мною проблем не возникало бы. Иначе говоря, ваши слова — да б-гу в уши :)

Согласен, что такой незаменимый человек является узким местом, которого любой бизнес в здравом уме всегда будет пытаться избегать. Для решения этой проблемы есть идея создания профессиональной ассоциации экономистов-плановиков, аналоги которых существуют в других профессиях. Со своими стандартами, периодической переподготовкой и аттестацией, членством в ассоциации, классными чинами, этическим кодексом, корпоративной солидарностью и т.п. Ну и конечно же со своим профессиональным программным обеспечением. Чтобы бизнес был спокоен, что если накосячит один из членов сообщества, то его всегда подстрахуют его коллеги.

Слушайте, как у вас всё классно в экономике организовано. В нашей бы индустрии так.


Круто.

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

Эээ это точно про юнит тестирование? Или какие-нибудь интеграционные тесты?

А тут ещё и O/RM поощряет такие кренделя, оставляя доступ напрямую к базе

Да и потом — я не хочу тестировать сервер баз данных!

А вот если не фигачить запросы к базе напрямую, то сторадж прекрасно мокапится и юнит можно легко обтестить со всех сторон с минимальными усилиями.
Эээ это точно про юнит тестирование? Или какие-нибудь интеграционные тесты?

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


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


В Tecture же я отдаю предпочтение data-driven (оно же table-driven) тестированию, как наиболее близкого бизнесу и лёгкому в обслуживании (при правильном тулинге). За счёт правильного дизайна Tecture, к месту впаянных перехватчиков данных и генератору кода валидации стоимость производства и запуска такого теста снижается чуть ли не до нуля. Таким образом я планирую решить проблему с тестированием.


А вот если не фигачить запросы к базе напрямую

Вашими бы устами… Я придерживаюсь стратегии "не можешь победить — легализуй". Поэтому добавил возможность для работы с прямым SQL-ем, только немного облагородил её по периметру. Можно сказать что весь SQL, которые уходит в базу посредством соответствующей фичи Tecture — подвергается осмотру, анализу, валидации от типичных ошибок. Словом, это становится не маленький подленький SQL в две строчки, который вызывается из дальнего угла системы, а полноценный и вдумчивый SQL, который просто найти и отследить.


Но в целом вся идея моей разработки крутится вокруг возможности полностью отрезать приложение от внешнего мира через довольно простые по сути абстракции. Но писать в 2020м что ты придумал очередную абстракцию от внешнего мира — как-то… Вторично, чтоли :)

Спасибо Павел, ссылку отправь в ЛС

Выше ребята накидали ссылок на файлы, через них можно в репозиторий попасть.
Ход и направление мысли одобряю.
Мне лично интересно это направление со стороны микросервисов. Там последние несколько лет решаются (с переменным успехом) все проблемы которые выше самого сервиса, подразумевая, что все что внутри — это не проболема — оно же микро. Однако на практике я вижу, что люди такое городят внутри микросервиса, что очень хочется выдать им что-то вот вроде твоей штуки и принудить их к единому стандарту в котором все четко и понятно.
Очень смешно, кстати, получается, если транзакция в БД по каким-то причинам упала, а e-mail ушёл. Транзакцию-то можно откатить, а вот письмо уже обратно не всосёшь.

Кстати, этой штуке подвержены и вроде как сильно крупные компании. vk.com например.
Для понимания, что происходит. Я использую отдельную дебетовую карту для всяких мелких постоянных подписок/платежей/etc. Закидываю туда пару тысяч в месяц и она там медленно рассасывается.

В этом месяце, как то запарился и пропустил обнуление баланса карты, а спустя пару дней обнаружил на телефоне(он не основной и не рабочий, просто валяется в столе, для уведомлений или подтверждений) пачку уведомлений об отказе оплаты, но в самом вк меня ждала вот такая картина:
Поток успешных операций
image

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

А потом наступает клиринг...

Ох уж эти разработчики, что делают хорошо, так это выдумывают всякие костыли и неудачные решения. DI контейнер это не проблема, проблема в том что абсолютно все зависимости начинают через него инжектить, так страшен new что ли. Вторая проблема это величественные классы — Service, Provider, *Processor и тд. В которые пихают опять же абсолютно все что кроется за звёздочкой. Третье — никакой логики в модели… Выделение интерфейса абсолютно для всего, по мне так это нужно на стыке модулей, не более. обычно граф зависимостей выглядит как… люблю ещё вещи выносят в экстенше класс, типа что бы по солид было)) или универсальную обёртку для транзакций выдумывают
И куча всего ещё, самое главное по моему мнению — условному программисту тупо скучно, он считает что может строить ракеты, но почему то делает сайты

так страшен new что ли.

Ага, а потом, если необходимо будет еще один параметр в конструктор добавить, придется весь граф зависимостей обходить или еще хуже ServiceLocator городить. Знаем, плавали.

никакой логики в модели

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

Или передать зависимость параметром метода.

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

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

Путь другой — автоматическая генерация. Но это путь математики, логики, долгих поисков, на что бизнес никогда денег не даст. Туда тянут некоторые ФП-шники, интуитивно признавая пользу некоторых базовых попыток что-то наваять в этой области, но эти базовые попытки ограничены университетами и поддерживаются студентами да энтузиазмом какого-нибудь профессора, но не бизнесом. Поэтому результат выглядит жалко и убого. Но именно его нужно развивать, и в первую очередь — в императиве. А то-ж иначе когда-то ФП-шники смогут абсолютно заслуженно сказать — императив отстой! Ведь без развития он и правда таким станет.
В любой компании есть человек, который любит придумывать такие абстракции над абстракциями и еще обижается когда их мало кто использует.

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

Если взять C# то тут:
1. БД должна быть полностью описана миграциями (FluentMigrator)
2. Уровень доступа к БД должен быть покрыт компонентными тестами которые выполняются в Docker (поднимается минимальная рабочая инфраструктура для сервиса)
3. Должны быть интеграционные тесты выполняющие определенные сценарии, также исполняемые в Docker, при этом тесты должны быть внутри этого же решения.
4. Для каждого контроллера делать фасад. Никакой логики в контроллере. В простейшем случае фасад же и будет репозиторием.
5. Никогда не закладывать в начале создания сервиса overhead на репозитории, внутренние сервисы и прочее когда в них нет надобности. В 99% случаев микросервисов достаточно контроллера, фасада и уровня ORM.
6. Давать именам осмысленные названия чтобы проект не состоял из кучи бессмысленных producer,consumer,context и прочего вырвиглаза который не нужно инжектить. Его можно просто создать в том сервисе, который логически за всё это отвечает, а не в инициализации контейнера.
7. Шаблон репозиторий на высокой нагрузке приводит к тому, что код на нём сложно оптимизировать. Например, в процессе оптимизации просто приходится напрямую в базу ходить, чтобы сделать JOIN на 4 таблицы вместо того чтобы данные извлекать по очереди. Просто потому что через репозиторий это сделать невозможно не вынося на верх Query(...) который просто окажется абстракцией над решением ORM

1. БД должна быть полностью описана миграциями (FluentMigrator)

Почему так сразу FluentMigrator? Есть стандартные миграции EF или SqlServer database project, если мы используем MS SQL server.

Никакой логики в контроллере.

Очень спорное утверждение, например, мне нужно в зависимости от того, что вернулось на мой запрос, отвечать соответствующим кодом ошибки HTTP и тянуть транспортные нюансы в фасад — моветон. У меня контроллер должен отвечать за транспорт, а все остальное — можно и инкапсулировать.
При развертывании в Kubernates, вам нужен init-контейнер, который приготавливает базу.
При развертывании локально вам нужно чтоб миграции выполнились при старте сервиса. Не всегда используется EF, я, например, использую пару NHibernate и Dapper в зависимости от потребностей

Что касается кодов ошибок, таких кодов не так много.
200 — стандартный ответ
204 — null (реализовано .NET)
На все остальные кидается Exception, который выставляет код ошибки.

Я лично за подход 200 — возвращается всегда когда API правильно отработало запрос, 500 — когда произошла ошибка. Ведь сервисы еще и мониторить надо
Шаблон репозиторий

Мысль про фасады — очень правильная.
Репозиторий — это вообще зло, потому что реюзается. Когда GetUser внутри начинает возвращать все поля юзера, ещё и огромное дерево за ним. Ведь GetUser кому-то ещё пригодился.
Репозиторий прячет DbContext, который содержит в себе список Tracked-сущностей и очень хорошо, когда можно этим управлять. Кстати, именно потому в EF Core 5 добавили DbContextFactory, чтобы народ не прокдидывал один DbContext по всем сервисам в скопе.

Чтобы писать просто, надо меньше реюзать:
«а давайте сущность прямо из view прокидывать в ORM» — звучит красиво, но на практике лучше сдалать кучу классов для каждого View и DTO для каждого запроса, а потом мэппить одно на другое auto-mapper'ом.
Кодегеренация — это очень круто, именно она позволяет сделать мэппинг максимально быстрым.
Автомэппер тоже отдельное зло. Я помню как сделал protected set в просто set и Mapster сломался. Учитывая что правка была оценена в 1 час, пришлось сделать public метод, который эту логику реализовывает. Поэтому автомепперы это тоже зло.
Более того часто люди не понимают как это работает — в итоге получаем что у нас есть три enum в разных проектах, которые в друг друга мапятся вместо одно enum.
А задачи по добавлению одного поля в ответ отнимают половину рабочего дня. А если там еще интеграционные тесты на каком-нибудь питоне, то задача по добавлению одного поля может и за 2 дня в мастер не вмерджиться.
Кодогенерацию еще и запускать надо. В новом .NET 5 она уже нормальная станет
UFO just landed and posted this here
Я второй десяток лет участвую в разработке приложений для бизнеса на .NET и каждый раз вижу одни и те же проблемы

Прежде чем начинать разговор об архитектуре, и поиска истины между «правильная архитектура, но медленно или тяп-ляп, но быстро?». Нужно разобраться, о каких проектах идет речь.

Хотелось бы поинтересоваться у автора, какое количество моделей было в ваших проектах? Конечно, количество моделей не отражает на прямую сложность логики, но, практика показывает, что проект с 20-30 моделями это одно, а с 200-300 — совсем другое.
какое количество моделей было в ваших проектах

Разные проекты были. От 10 до 500 моделей. Где-то на 150 UoW с репозиториями уже начинает загибаться. Я пытаюсь построить архитектуру, которая не будет ломаться на больших проектах с огромными доменами. Странно что я явно не упомянул это в статье.

Статья хорошая, но она размазывает внимание по симптомам, а болезнь у всего этого одна — ГЛУПОСТЬ. Джуны, мальчики с горящими глазами, линуксоиды, вчерашние управдомы, переквалифицирующиеся в погромизды — вот это вот всё сильно портит элитный мир ИТ.
Раньше, чтобы только коснуться заветных клавиш, ты должен был быть большим дядькой с научными работами, которому позволили просчитать процент одарённых кузнечиков на большой, гудящей машине. Сегодня каждый индус, слезший с пальмы и прошедший недельные курсы С++ — погромизд, мать его!
Отсюда и все эти «популярные»(hype) технологии и архитектуры.

Принцип разработки очень прост: если ты видишь шуруп — бери отвёртку, если гвоздь — то молоток. Только и всего! Юные лопухи ровно наоборот — тащат в проект всё, о чём слышали, потому что начитались статей от других восторженных лопухов, которые не написали ни одного реального проекта.
IoC, тесты, контейнеры, фабрики, паттерны… этого мусора в сети как говна за баней. Только применять это с умом и к месту могут лишь опытные сеньоры. Если ты не сеньор — даже не смей касаться этой новомодной кучи хайпа! Когда тебе понадобится какая-то технология, ты сам это почувствуешь. А не почувствуешь — значит ещё не дорос.
Если ты не сеньор — даже не смей касаться этой новомодной кучи хайпа!

Джуны в своих личных проектах могут трогать что захотят.
А если на работе/бою джуны тащят то, что не надо, а синьйоры и тимлиды им позволяют, то всё ли правильно с «Элитарностью в ИТ» у этих синьйоров?
Каждый раз такие комменты сдвигают ответственность за современное ИТ на джунов и войтишников (войти в айти), но забывают, что само «Элитное ИТ общество» ими управляет.
Отдельное спасибо за перечисление откуда берётся бардак, открыто подсвечивать проблемы это важно.

— Это IoC!


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

Конечно, без DI-контейнеров жилось бы хуже (наверняка). Где грань между рациональным использованием автоматической инъекции зависимостей и неуправляемым мессивом из интерфейсов — мне неизвестно, пока что.

Будем ждать ссылку на репозиторий с версией готовой к открытому релизу. :)
Начал писать комментарий. Он всё разрастался… и вот что получилось: habr.com/ru/post/518182

TL&DR; есть спорные моменты, но в целом статья интересная. Чем больше вариантов архитектур — тем лучше.
Я десять лет страдал от ужасных архитектур в C# приложениях — и вот нашел, как их исправить

То есть предполагается «серебряная пуля», которая поможет с архитектурой для любых C# приложений? Звучит эпично. Все ли приложения (или подсистемы) на C# используют базы данных (и транзакции)? Может быть речь идет о каком-то подмножестве приложений?
Это IoC! Но что лежит в наших контейнерах?

А что там должно лежать? В чем проблема то? Ну да, на самом деле, там должно лежать все, кроме DTO, на мой взгляд. Иначе будет много созданий определенных экземпляров new и вызовов статических методов (как в вашем примере). На этот код вы потом не сможете написать unit тесты при всем своем желании. Кстати, в вашем примере я не нашел unit (модульных) тестов совсем.
На всё вышеупомянутое содержимое контейнера надо написать заглушки и вбить тестовые данные (руками).

Суть unit теста – тестирование юнита (класса или метода и т.п.) в изоляции (и в этом нам поможет DI, раз уж вы небрежно упомянули SOLID). Следование SRP предполагает, что зависимостей не будет много, так как моки нужно определить только для этих зависимостей, следование ISP позволит быстро понять что нужно замокать.
В итоге на один честный тест одного метода бизнес-логики у вас уходит хорошо если день времени.

На unitтест из 3 – 20 строк кода уходит целый день? Может быть вы путаете unit тест с чем-то еще?
Опытные разработчики, которым надо релизить по фиче в неделю и закрывать по 5 тасков в день этой чушью категорически маяться не хотят — и в этом бизнес их поддерживает. Овчинка выделки не стоит.

Предполагаю, что ваши опытные разработчики не знают, что такое unit тест.
Так вот, хочется впаять автоматизированное тестирование в этот процесс так, чтобы после приёмки, когда все — от QA до бизнеса сказали «да, это работает правильно», настрогать тестов, не меняя функциональность и кинуть в общую кучу, дабы прогонялись каждый билд. Можно даже какой-нибудь code coverage замутить. Меняешь логику — видишь что упало. Ну круто же!

То есть предполагается что после приемки все разработчики вдруг вспомнят все детали реализации, все хитрые случаи, все исключительные ситуации, все сценарии использования и все как один бросятся «строгать» тесты? А тем временем новые фичи будут ждать реализации.
А вот TDD — на фиг если честно. Невозможно загодя написать тесты для фичи, функциональность которой толком не известна до момента её сдачи. Поэтому ориентироваться на TDD я не стану.

Невозможно или может быть у вас не получается?

Жду вторую часть и надеюсь, что она сделает жизнь разработчиков C# проще.
Sign up to leave a comment.

Articles

Change theme settings