Форум программистов, компьютерный форум, киберфорум
C#: ASP.NET Core
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.55/29: Рейтинг темы: голосов - 29, средняя оценка - 4.55
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759

God object?

14.07.2021, 15:07. Показов 5920. Ответов 8
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Есть всем известный анти-паттерн 'god object'.

И есть типовые примеры реализации stateless-сервисов, например, для размещения логики API на уровень ниже контроллеров.

И получается какой-нибудь:
C#
1
2
3
4
5
6
7
8
public class InvoiceService
{
    public InvoiceDto GetById(int invId) { ....
    public void Update(InvoiceDto inv)  { ....
    public void Delete(int invId)  { ....
    public List<InvPreviewDto> GetPreviewsByCustomer(int customerId)  { ....
    ....
}
И потом, особенно если данная область логики сложная, этот класс разрастается всё больше и больше - появляются методы частичного обновления, всякие статистические выборки, куча приватных методов (и даже приватных вспомогательных вложенных классов), которые нужны каким-то отдельным конкретным методам и т.п.

И, по сути, хоть это лишь кусочек логики всего приложения, но это ведь всё равно god-object получается. Более того - состояния у него нет, а все методы полностью независимы друг от друга в плане реализации. Ну и при вызове из контроллера всегда создаётся 'InvoiceService' со всем методами, но в рамках конкретного вызова всегда используется лишь один из них. И с зависимостями, внедряемыми через DI, тоже получается не очень удобно, т.к. либо они внедряются вообще все сразу в конструкторе (хотя конкретному методу они могут быть и не нужны), либо надо их в явном виде в параметрах метода передавать.

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

И в плане контроля версий эти большие файлы не очень удобны.

----
Вероятно, опять ищу усложнения на ровном месте, но, может, есть какие-нибудь паттерны/подходы, которые позволяют всё это как-то разбить физически на отдельные файлы, и чтобы это в C# нормально вписывалось?
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
14.07.2021, 15:07
Ответы с готовыми решениями:

Ошибка: Object reference not set to an instance of an object
Пытаюсь использовать foreach(var n in Model) в одном View он работает, а в другом View он выдает такую ошибку:Object reference not set to...

Передача строки из GridView: Object reference not set to an instance of an object
Ахтунг!) Не разберусь где я не установил ссылку оО Выскакует когда я хочу передать строку из Грида... Вот скрин:

Ошибка: annot add object with apartment model behavior to the application intrinsic object.
При выполнении кода: IF VarType(Application('presents')) &lt;&gt; vbObject then Set Application('presents') =...

8
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759
15.07.2021, 09:47  [ТС]
Нагуглил интересный подход - CQS. Вглубь пока особо не разбирался, но найденные реализации на C# выглядят очень удобно:
1. Все DTO делятся на 4 типа:
1.1. Query + QueryResult.
1.2. Command + CommandResult.
2. Для каждого Query и каждого Command на сервере реализуется отдельный Handler. Реализуется в виде самостоятельного класса, который реализует логику только конкретного запроса/команды и использует только нужные ему зависимости.
3. Если правильно реализовать базовые классы/абстракции, то можно обойтись вообще без контроллеров. Т.е. они будут, но будут типовыми, их можно будет генерировать динамически при старте хоста и регистрировать через 'IApplicationFeatureProvider<ControllerF eature>'.

Правда, если чуток глубже посмотреть, то в этом CQS много странных ограничений, которые вряд ли возможно соблюсти на реальных проектах, по крайней мере в .NET:
1. Команды не должны ничего возвращать, т.е 'void'. А как тогда вернуть ID созданной сущности? Или вернуть подробные результаты не пройденной валидации?
2. Команды - это стек записи/изменения. И заявляется, если я правильно понял, что читать данные они не должны. Но как можно обновить сущность в базе, предварительно не вычитав её из базы для анализа/сравнения? К тому же, часть методов обновления сущностей на этапе предварительной валидации, требуют запроса дополнительных данных из БД.
3. Ну и обозначенный в соседней теме сценарий большого расчёта, когда надо на сервер отправить большой пак данных для обработки и получить в ответ результаты. При этом ничто никуда не пишется и ничто ни откуда не считывается, никаких сущностей нет. Это просто обработка данных. Т.е. это и не команда на изменение, и не запрос.

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

Добавлено через 3 минуты
Более того, вероятно можно будет сделать так, чтобы для обработчиков команд использовался EF, а для обработчиков запросов - Dapper поверх рукописных SQL-запросов. В рамках одного проекта.
0
800 / 583 / 207
Регистрация: 21.02.2019
Сообщений: 2,095
15.07.2021, 10:37
kotelok,
.. была у меня такая мысль (не уверен, что она полностью соотносится с вашей темой) разбить сервисы на универсальные с одним методом типа Get (by id), Get (filtered), Save и т.д., а данные передавать в них одним универсальным DTO на все случаи жизни ... Это, разумеется, очень частная задача, но в большинстве случаев в документообороте табличная часть и инвойсов, и ТТН, и актов выполненных работ - подобная, только поля разные ... Т.е. заполнять в контроллере универсальную DTO, а тип документа передавать отдельным параметром в сервис .. Другое дело, этот универсальный объект DTO тоже может выродиться в god object ....
0
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759
15.07.2021, 14:01  [ТС]
carrotik,
Я наоборот стараюсь уходить от любой универсальности, т.к. это кучи if-else/switch-case, смешивание логики, сложная поддержка и непредсказуемые последствия во внезапных местах при незначительных изменениях.

И для каждого запроса - свой комплект DTO, даже если он на 99% пересекается с каким-нибудь уже существующим. Это разные части логики и если использовать общий DTO или, как я иногда видел, наследовать "больший" от "меньшего", то потом при внесении изменений приходится перетестировать и тот функционал по которому была задачу, и тот, что к задаче отношения не имеет, но использует те же DTO, которые чуток поменялись. Ну и измнение внешнего контракта у метода, по которому не было изменений, тоже странно выглядит.
0
 Аватар для sau
2773 / 2073 / 386
Регистрация: 22.07.2011
Сообщений: 7,820
15.07.2021, 20:00
Лучший ответ Сообщение было отмечено kotelok как решение

Решение

kotelok, можно команды делать , как выше написали . но когда становится 100500 команд , их иной раз удобно обьединить по смысловой нагрузке в один сервис , сами сервисы можно так же дробить на множество сервисов , в каждом набор команд обьедененных концепцией/зоной ответственности , грубо говоря QueryServiceA-B-C и CommandServiceA-B-C

Добавлено через 12 минут
Цитата Сообщение от kotelok Посмотреть сообщение
Или вернуть подробные результаты не пройденной валидации?
через исключение можно , а можно завершить выполнение команды и результат ее выполнения вернуть через событие OnComplette , а если это микросервис , то через какой нибудь rabbitmq
А как тогда вернуть ID созданной сущности?
это не закон , можно и вернуть - правда тогда универсального интерфейса не получится , но вообще , разделение на qurey и command делается с целью избежать нежданчиков . например , когда какой нибудь метод запроса данных внезапно их еще и меняет внутри , что неочевидно для вызывающего.
Цитата Сообщение от kotelok Посмотреть сообщение
И заявляется, если я правильно понял, что читать данные они не должны. Но как можно обновить сущность в базе, предварительно не вычитав её из базы для анализа/сравнения?
ну так для этого уже будет query/Service , с помощью которого внутри команды данные и читаются , а можно в качестве параметров передать просто все что команде нужно для выполнения.
Цитата Сообщение от kotelok Посмотреть сообщение
Ну и обозначенный в соседней теме сценарий большого расчёта, когда надо на сервер отправить большой пак данных для обработки и получить в ответ результаты. При этом ничто никуда не пишется и ничто ни откуда не считывается, никаких сущностей нет. Это просто обработка данных. Т.е. это и не команда на изменение, и не запрос.
ну от этго команда не перестает быть командой , задача команды выполниться , будет она куда-то писать или что-то отправлять уже не важно. , кстати команда может быть асинхронной , у нее могут быть события OnComplette(Result) и пропертя проверки возможности выполниться - CanExecute ,
в общем это все условно , основной посыл CQRS - не мешать в одной функции изменение данных и запрос данных. , и в целом эту концепцию можно масштабировать до уровня микросервисов , когда одни сервисы выполняют запросы , а другие команды.
1
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759
15.07.2021, 21:28  [ТС]
Цитата Сообщение от sau Посмотреть сообщение
ну так для этого уже будет query/Service , с помощью которого внутри команды данные и читаются , а можно в качестве параметров передать просто все что команде нужно для выполнения
Вот этот момент единственный, который пока совершенно не понятен. Проанализировал свои существующие сервисы - там классический CRUD, плюс немного асинхронных задач.

С запретом на запись из 'Query' всё ок - даже при обычных хаотичных реализациях такого никто не делает.

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

Цитата Сообщение от sau Посмотреть сообщение
ну так для этого уже будет query/Service
Т.е. обработчик команды, если ему нужны какие-то данные, запрашивает через DI диспетчер запросов и через него получает все нужные данные, отправляя соответствующие 'Query' по стандартному механизму?

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

Добавлено через 49 секунд
Или допускается напрямую в CommandHandler заинжектить репозиторий/DAL, который умеет данные из хранилища получать в обход механизма Query?
0
 Аватар для sau
2773 / 2073 / 386
Регистрация: 22.07.2011
Сообщений: 7,820
17.07.2021, 10:17
kotelok,
да можно как угодно делать , главное что концепция команды соблюдается.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
namespace OTFAS.UI.Commands
{
    public class UpdateRecordCommand<TView, TTable, TKey> : RecordCommand<TView, TTable, TKey>, ICommand
        where TView : RecordView, new()
        where TTable : class, IRecord<TKey>, new()
    {
        private readonly PageRecordView<TView, TTable, TKey> pageView;
        public event Action<object> OnExecuted;
 
        public UpdateRecordCommand(IRepository<TTable> repository, NotificationService notifyService, IMapper mapper, PageRecordView<TView, TTable, TKey> pageView)
            : base(repository, notifyService, mapper)
        {
            this.pageView = pageView;
        }
 
        void Execute(TView view)
        {
            try
            {
                var record = repository.Find(i => i.ID.Equals(view.ID));
                mapper.Map(view, record);
                repository.Update(record);
                var listItem = pageView.List.Find(i => i.ID == view.ID);
                mapper.Map(record, listItem);
            }
            catch (Exception ex)
            {
                if (ex.InnerException != null) ex = ex.InnerException;
                notifyService.Notify(new NotificationMessage { Severity = NotificationSeverity.Error, Summary = "ошибка", Detail = ex.Message, Duration = 4000, Style = "word-break:break-word" });
            }
 
            canExecute = true;
            OnExecuted?.Invoke(view);
        }
 
        public Task ExecuteAsync(object arg)
        {
            if (!canExecute) return Task.CompletedTask;
            canExecute = false;
            return Task.Run(() => Execute((TView)arg));
        }
 
    }
}
1
11 / 10 / 4
Регистрация: 27.01.2013
Сообщений: 15
23.07.2021, 16:09
Если вернуться к первоначальному вопросу, то разбив логику god объект на более мелкие подсистемы(репозиорий, более специфичные сервисы), то в итоге InvoiceService уже будет реализовывать facade pattern
1
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759
23.07.2021, 17:32  [ТС]
Epic,
Не, тут не об этом речь. Репозитории, доменные сущности/сервисы, инфраструктура - это, разумеется, всё отдельно в своих сборках и классах. Тут же вопрос был как отдельный сервис организовать внутри своего слоя, чтобы он не разрастался на 2000 строк.

Собственно, подход через команды/запросы очень хорошо подошёл - и дерево проекта стало более информативное, и каждый класс отвечает только за свой маленький кусок функционала, и коммиты в гите читать проще, и риск конфликтов при коммите ниже. Ну и если вдруг потребуется, то в будущем это немного упростит физическое разделение систем чтения/записи.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
23.07.2021, 17:32
Помогаю со студенческими работами здесь

Ошибка Object reference not set to an instance of an object
здравствуйте всем, у меня одна проблемка, работаю с генерацией различных документов в формат эксель, и вот выдает следующую ошибку: ...

God Mode
Здесь, смотрим видео вообще классная штука =)

light scattering или god rays своими руками
Я рендерю солнце через 2D с теми же матрицами что и весь мир (gluperspective) ну не суть, я рендерю солнце на картинку и также мир в черном...

Uncaught TypeError: Object [object Object] has no method
Всем привет. Я новичок в этой сфере. у меня возникла ошибка в консоли при добавлении карусели на сайт: &quot;Uncaught TypeError: Object ...

Создать перечисляемый тип данных GOD – лето, осень, зима, весна
Задача 1 - Создать перечисляемый тип данных GOD – лето, осень, зима, весна. Вывести порядковые номера. Program lab_6; var Type ...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
9
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru