Pull to refresh

Comments 56

Извините, ни в коем случае не негатив, но под заголовком предполагал описание именно enterprise архитектуры, а нашел вариант реализации продукта.
Проблемы продуктов в enterprise чуток шире.
Далеко не эксперт, но вы верно заметили что в любой enterprise (напр. крупные банки, типа JPMC, WellsFargo) внутри хаос из продуктов 3х фирм и внутренних продуктов.
Как следствие, enterprise архитектура продуктов должна решать задачи enterprise уровня.
Желательна концепция архитектуры, принимающая во внимание и зоопарк, как неизбежность, и задачи бизнеса и проблемы реализации (неимоверно затянутые сроки, низкий уровень понимания big picture) и снижение порога вхождения для эффективного добавления продуктов и принудительная стандартизация для исключения нового хаоса и масса других мелочей, для описания которых комментарий неуместен.
Ребята из Jet иногда делятся интересными решениями.

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


Что для обычных проектов "ааа! мы подключаем интеграцию ещё с одной системой спасите!", то для проектов на Tecture — "о, архитект завёл новый канал".

Как по мне, вы предлагаете велосипед. Проблема-то достаточно старая, решение такое лежит на поверхности, и тоже уже давно есть реализации (правда, как и принято в приличном энтерпрайзе, либо с конскими ценами, либо на Java, либо с конскими ценами на Java), работают в принципе похожим образом, и кроме того, включают дополнительные необходимые обработчики грабель, на которые вы пока ещё не наступили, но обязательно наступите. Например, поддержку транзакций, когда ваш To().Add(new Order()) отвалится где-то в недрах Db, и надо синхронно откатить всё то, что наделали перед этой операцией на другом конце этого вашего «канала» и всех ему предшествующих.
Сложность-то лежит как раз в разработке каналов-адаптеров для всего корпоративного зоопарка.
Проблема-то достаточно старая, решение такое лежит на поверхности, и тоже уже давно есть реализации

А можно ссылку? А то что-то мне не по глазам.
Так же подчеркну: я не делаю что-то принципиально новое и уникальное: просто раскладываю по полочкам старое и существующее.


To().Add(new Order()) отвалится

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


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


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


Сложность-то лежит как раз в разработке каналов-адаптеров

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


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


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

public interface IQueryFor<T>  
    IQueryable<T> All { get; } 
    IQueryable<U> Joined<U>(); 


Так не понятно, внутри взаимодействие идет через EF?

В зависимости от того, какой рантайм подключается. В настоящий момент меня хватило на то, чтобы сделать один рантайм — для EF.Core. Но можно написать другой, где конструировать запросы будет что-то другое. Я планирую ещё сделать рантйм на EF6, но это будет разработка чисто в демонстрационных целях, что вот, мол, можно из одной точки приложения переключиться между EF6 и EF.Core и результат не изменится. Tecture как раз об этом.

Я тут надысь узнал про существование Belgrade ORM. Вот можно его попробовать воткнуть :) Но конструктор запросов — одна из самых сложных частей EF и если делать его с нуля, то я даже не знаю с какого боку кусать эту задачу.

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

Мы рождены чтоб сказку сделать былью :)


Но да, бывает грустненько.

Меня раздражает традиционная архитектура бизнес-приложений

+1


// Этот ById приклеится ко всем IQueryFor, где T содержит int-овый Id

Красиво и здраво всё выглядит. Даже немного жаль что там где сейчасас на C# пишу ничего не решаю, а то бы попробовал.


Читал по диагонали и потом с трудом нашёл ссылку на репозиторий. Было бы здорово как то выделить и поместить или в самом начале или в самом конце. Можно даже отдельным заголовоком. Тут не стоит скромничать имхо.

В Tecture есть свой мини-IoC на типах.

Любопытства ради, а что вы понимаете под "IoC" в данном контексте?

Я подразумеваю любой примитив, позволяющий сопоставить в run-time тип некого компонента с его экземпляром, абстрагирующий пользователя от управления временем жизни этого экземпляра. Как-то так.


Штука, которой можно сказать "дай мне печеньку", не заботясь о том, откуда печенька берётся, кто её создал и на чём её мне привезут.

То есть только управление временем жизни?

Да. Видимо мне стоило использовать термин DI вместо IoC, но как-то так повелось что под IoC-контейнерами я (и не только я) понимаю вполне конкретную методологию.


Я вообще не силён в терминах и думаю что из статьи это довольно очевидно :)

И время жизни у вас только одно? Какое, кстати?

Я исхожу из


Лайфтаймы <...> всё равно во всех проектах одинаковые и прибиты гвоздями к лайфтайму подключений к базе (и остальным внешним системам).

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


Если брать на примере web-проекта, то смерть всего там в основном происходит после завершения обслуживания запроса.

Если брать на примере web-проекта, то смерть всего там в основном происходит после завершения обслуживания запроса.

Ну вот у меня тут под боком веб-проект (вполне себе enterprise), в котором лайфтаймов как минимум два: на все время жизни приложения и на время жизни запроса.

Вопрос в том, какие каналы у вас живут и почему так долго. Вангую что где-то у вас сидит SignalR или что-то подобное.


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


То есть инстанс, создаваемый TectureBuilder-ом можно воспринимать как модуль — пакет сервисов с одинаковым временем жизни. Просто вы не каждому ProductsService прописываете явно .InSingletonScope/.InstancePerRequest, а делаете это один раз, скопом для всего ITecture.

Вангую что где-то у вас сидит SignalR или что-то подобное.

Нет.


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

… и как они друг с другом взаимодействовать будут? Потому что сейчас я могу из per-request-сервиса обратиться к singleton, и у меня все хорошо.

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


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


Либо вообще не засовывать в Tecture ту часть, которая статическая (полагаю она меньше) и общаться с ней вне сервисов Tecture.


Но кейс интересный, учту.

Это основной кейс в asp.net. Конфигурация — singleton, а контроллеры и db context чаще всего per request. Синглетон-сервисов целая куча, не меньшая куча per-request сервисов.

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


Но если хочется — можно например сделать канал/аспект с настройками. Вай нот? В дизайн всё ещё вписывается :)

Кстати из того же asp.net mvc можно взять концепцию processing pipeline.

Туда органично впишется authorization или data validation.

Чтобы был processing pipeline — надо чтобы было что процессить. А у нас команды и запросы :)

Currently there are 2 aspects available for Tecture: ORM and DirectSql.
Вместо Belgrade ORM лучше добавь поддержку REST API вызовов. Это покроет очень значительную часть потребностей потенциальной базы пользователей проекта.

Сделать будет легче чем ORM, а людей которые её захотят — сильно больше.

Ещё не увидел интеграцию с авторизацией, интерпрайзы это сильно любят. Например возьмём разделение на обычного пользователя и админов. Если на уровне «тулинга», можно было бы декларировать что вот это только для админов, поэтому сломайся когда попробуешь засунуть в сервис для обычных пользователей. Думаю что на уровне generic constraints, можно что-то такое придумать. Опять же, даже если это будет какое-то простейшее решение, которое работает в runtime вместо compile-time, то всё равно будет плюс в глазах пользователей. Ещё для подобных вещей можно притащить в проект .NET Compiler Platform Analyzers, там сильно гибко можно обработать правила и при компиляции отвалиться если не удовлетворяет критериям.

Ну и конечно, было бы совсем хорошо, если бы существовал некий roadmap по поэтапной миграции существующих приложений.

Например с чистого EF на вашу надстройку.

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

Согласен с вами, работы именно столько. Не уверен что стану её делать.

А как работать с транзакционными каналами? У меня несколько каналов и они должны отработать в одной транзакции.

При создании корневого инстанса есть возможность подпихнуть свой Transaction Manager, создающий транзакции на разных этапах процесса и на выбранных вами каналах. Не очень удобно — придётся обернуть канальные транзакции в обёртки Tecture и отнаследоваться от Transaction Manager-а. Но сама возможность присутствует.

Higher-kinded type, тип высшего рода/порядка. Своего рода функция на уровне типов.


Примером HKT является дженерик: IEnumerable<int> — это обычный тип (тип рода *), а IEnumerable<> — это уже HKT (тип рода * -> *).


Однако, под присутствием HKT в языке понимается не возможность создать простой дженерик, а возможность определить на уровне типов произвольную функцию, и даже передать её куда-нибудь. Пример из С++:


template <typename T> struct foo_traits;
template <typename T> using foo = typename foo_traits<T>::foo;

template <> struct foo_traits<void> {
    using foo = bar;
}
template <> struct foo_traits<int> {
    using foo = baz;
}

Higher Kinded Types. Когда тип-параметры считаются полноценной частью языка, их можно собирать в массивы, фильтровать, группировать. В C# есть where-constraint-ы, но их мощности маловато. Дискуссию о том, что ожидают от них в C# можно почитать, например тут. Там же по ссылке приводят меткий термин: generics on generics.


Аналогию можно провести с higher kinded functions. В C# есть их поддержка через делегаты. С их помощью методы можно запихивать в переменные, собирать их в список, итерироваться по ним, передавать аргументами. Без них методы были бы просто методами. Вот хочется такой же гибкости, но на тип-параметрах.

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

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


Например, бизнес код у автора работает с linq напрямую или посредством слоя "запросов" которые находятся в extension методах. В отличии от репозитория слой запросов нельзя подменить на другой (он в статических методах) в результате прикладной код зависит от linq.


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

Всё так, но не совсем.


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


LINQ — часть стандартной поставки .NET и это просто способ строить запросы. А вот способ ИСПОЛНЯТЬ эти запросы явно задаётся отдельно и его можно переключать.


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


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


Вот как-то так, только подход я называю аспект.

Ну я про это и говорю. Условно, если у вас порт (в терминах паттерна "hexagonal architecture") на уровне linq — то вы должны реализовать весь linq если надо присоединиться к чему-то другому. Если у вас репозиторий то там вы собираете только те запросы, которые реально используются вашим приложением — соответственно другая реализация должна реализовать только их.


В Tecture с одной стороны вы собираете используемое подмножество запросов, но в неудобной для подмены формы — вроде статических методов. Кстати, вы не задумывались сделать их instance методами? Типа не extension метод который вызывает запрос, а extension-метод, который инстанциирует репозиторий, который вызывает запрос или как-то еще.


Методические рекомендации к использованию вашей архитектуры требует вынесения всех запросов в слой query или допустимо фигачить мимо этого слоя?

linq

Послушайте, 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 у вас просто нет возможности нафигачить "мимо слоя". То есть можно, но это довольно сложно и вряд ли пользователь будет нарочно извращаться чтобы это сделать.

Выполнение LINQ-запроса легко делегируется другой части системы, что в ORM-аспекте и сделано.

Тут я имею ввиду уровень абстракции на котором определен интерфейс канала (КМК это то же самое что и "порт" в рамках hexagonal architecture)


А в чём профит?

Я встречал два варианта:


  1. Допустим у вас есть какой-то модуль, который может хранить свои данные и в SQL и в плоских файлах. Если сделать "слой query" легко подменяемым (фактически — репозиторий) то можно легко заменять его реализации другими полностью — там где был сложный LINQ с джоинами будет, например вызитывание какого-нибудь смещения в файле. Чтобы по подменить реализации не придется поддерживать linq поверх этих файлов.


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



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


Нет "слоя" query.

Наверное, я использовал неправильный термин. Имелся ввиду "Query extensions"


Query extensions can be placed everywhere. Their intention is to replace read abstractions (Repositories) in software design.

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

Для трёхзвенных десктопных приложений не очень подходит.
Там нужно выделить интерфейсы в отдельную сборку, на сервере — своя реализация, на клиенте — прокси-реализация через WCF/SOAP

В клиентском приложении всё так же — у вас есть один канал (сервер) + по вкусу локальный кэш — второй канал. Не вижу противоречий. WCF/SOAP — это уже вкусовщина на уровне рантайма в моей терминологии.


Вроде натягивается и даже не как сова на глобус.

Так как с клиента вызывать сервисы, не абстрактные Add/Update, а в терминах бизнес-логики (OrdersService.AddLine)? Если сигнатура Orders.AddLine существует только в сборке-реализации сервиса.

Будет что-то в духе


var b = new TectureBuilder();
b.WithChannel<Server>(x=> { x.UseServer("127.0.0.1", 8080); });
var tecture = b.Build();
tecture.Let<OrdersService>().AddLine();
Получается, что мы на клиента тащим реализацию OrdersService (пользователям доступен код, которого у них быть не должно), а также все зависимости — EF, Hibernate (если есть), драйверы БД (mysql/postgress), которые фактически не будут запускаться, а нужны просто потому, что подключены к DLL с OrdersService?

Нет. В Server и его аспекты вы оборачиваете всю коммуникацию с сервером поверх WCF или что там у вас.

Если в коде клиента есть строчка
tecture.Let<OrdersService>().AddLine();
, значит и сборка, содержащая класс OrdersService, подключена в клиента.

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

Я не понимаю ваш ответ.

В классическом 3-звенном «интерпрайзе» есть интерфейс IOrderService с методом AddLine, и 3 его реализации: 1) OrderService, который выполнет всю работу, на сервере, в транзакции; 2) OrderService_WcfServer, который упомянут в *.svc-файле и потому вызывается IIS-ом на входящем запросе, и делегирующий всю работу первому сервису, и 3) OrderService_WcfClient, который делает wcf-вызовы к серверу. Классы OrderService_WcfServer и OrderService_WcfClient генерируются по интерфейсу автоматически, например, tt-файлами (как альтернативный вариант — какой-нибудь Castle DynamicProxy).

Клиентское приложение так сконфигурировано, что по IOrderService получает OrderService_WcfClient. Какая-нибудь консольная патчилка или тесты будут по IOrderService получать настоящий OrderService.

Что такое в вашей терминологии «клиентская логика»? Это ещё один класс OrderService, отличный от серверного OrderService? Его будет писать «архитектор»? Но ведь методы и параметры 100500 раз меняются по мере развития приложения. Типичное ТЗ: «добавьте в AddLine ещё один параметр Notes». Переписывать серверный, затем клиентский OrderService?

В этой ситуации делается так:


  • на сервере: несколько каналов (база данных там, или что у вас), несколько сервисов для работы с ними.
  • на клиенте: один канал (Server), один аспект для WCF, но я бы сделал RPC-аспект (думаю над ним), одним из рантаймов которого был бы WCF (который уже мало кто использует).

На сервере пишем OrderService как показано в статье. Для клиента можем генерить обвязки к ним тем же самым tt, которые пускают запросы к серверу через аспект (чтобы работал перехват данных — см. следующую статью). Вероятно вместо tt можно использовать типы (надо смотреть на конкретику).


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


Что такое в вашей терминологии «клиентская логика»?

Вот это вот.


И, кстати, зависимость на сборку с сервером (или же промежуточную сборку с интерфейсами, необходимую и клиенту и серверу) нужна как раз в вашей схеме.

И, кстати, зависимость на сборку с сервером (или же промежуточную сборку с интерфейсами, необходимую и клиенту и серверу) нужна как раз в вашей схеме.
Верно, это один из принципов (IoC) в SOLID: зависимости не от реализаций, а от абстракций. Абстракции выносятся в отдельную сборку.

Клиентское приложение будет получать свой собственный OrderService, который сгенерен для него по образу и подобию серверного.
Теперь понятно. Но тут я вижу тот минус, что код должен знать, какой ему нужен OrderService: серверный или клиентский, они разные. Если вдруг какой-то сервис нужно перенести с клиента на сервер, нужно будет править сам сервис, а не конфигурацию DI.

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


Вы хотите обставить всё так что мол на сервере OrdersService и на клиенте OrdersService с теми же методами и работают они одинаково, но эта абстракция течёт по той причине что сериализовывать stateful-объекты вы не можете и если таковой возвращает серверный OrdersService, то при попытке вызвать этот метод на клиенте произойдёт ошибка. Уже не говоря о том, что надо помечать все модели DataContract-атрибутами и писать портянку WCF-конфигурации в конфиге, иначе чуда не случится. Плюс разный контракт ошибок: если нет связи с сервером то выкинется соответствующий эксепшон в то время как на сервере такового не может произойти по определению.


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

Вы указали на проблемы так, как будто в вашем подходе тех же самых проблем не будет. Это проблемы в принципе трёхзвенки, и Tecture их не решает.

1) Модели помечать атрибутами, портянки конфигурации (кстати, в app.config писать не обязательно — можно кодом нагенерить по списку классов/интерфейсов и зарегистрировать). Ну да, если требование — WCF, это нужно, так работает технология.

2) Сервисы не могут возвращать stateful-объекты, в частности, entities. Это решается вынесением абстракций. Если в сборке-интерфейсе не упоминаются entities, и только она подключена к клиенту, то на клиент entities никак не попадут.
Но вы же предложили использовать автогенерацию клиентского OrdersService (и все dto к нему тоже дублировать авто-генерацией?). Авто-генерация тупо сделает копии, не разбираясь, что за объект — stateful/stateless.

Интересный подход. У нас более "развязанная" архитектура (а логирование и прочие штуки можно приделывать к брокеру, через который общаются все модули), а модули, в принципе, могут быть на разных ЯП… У вас больше ориентированно на то, чтобы дать разработчикам рамки, за которые им не рекомендуется выходить, а у нас на то, чтобы разработчик конкретного модуля мог, при желании, сделать в нём почти всё, что угодно (лишь бы "контракт по API" не нарушался).


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

Откройте свой рабочий проект, найдите то, что у вас называется «бизнес-логика»: это просто код, который сам ничего не делает, а только говорит что делать внешним по отношению к вашей системам.


А кто же все тогда делает? Редакторы баз данных не в счёт :)

Их не надо регистрировать ни в каком IoC-е


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

И сидеть потом в ожидании runtime exception.


Это вероятно, если использовать IoC как Service Locator как любят в ASP.NET: IServiceProvider или ISupportRequiredService. Попробуйте использовать подход «composition root».

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


Я думаю, что «сервисы прячутся за интерфейсы» еще и для того, чтобы детали (реализация) зависели только от абстракций. А «моки» не нужны если вы не пишите Unit тесты или используете что-то типа Microsoft Fakes.

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


Для этого можно (и нужно) использовать интерфейсы.

В-четвёртых: тулинги. Я не нашёл подходящего названия для этого механизма, поэтому называю его тулинг.


Можно назвать «зависимости» :)

Тулинги явно описывают что в этом сервисе делается, а чего в нём точно не делается. Степень детализации этой информации зависит от аспекта. Вот про сервис из примера выше точно можно сказать что Order-ы он не удаляет, а OrderLine-ы не меняет (только создаёт).


Так же и зависимости в конструкторе (если это конечно не Service Locator) скажут вам что в этом сервисе делается, тут еще “interface-segregation principle” поможет.

Если попробовать написать в этом сервисе, скажем To_Db_().Delete(order) — компиляция упадёт с ошибкой, как бы говоря нам: «хэй, чувак, это наш двор и ордеры тут не удаляют


В случае с абстрактными зависимостями в конструкторе у вас не получится написать «.Delete(order)» если нет абстракции для этого, тут и до компилятора дело не дойдет.

PS: Вопросы есть почти по каждому тезису
Sign up to leave a comment.

Articles