Как стать автором
Обновить
29
Карма
0
Рейтинг

Пользователь

  • Подписчики 41
  • Подписки 3

Exposable паттерн. Независимые инжекции путём экспанирования

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

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

Exposable паттерн. Независимые инжекции путём экспанирования

… и мы вернемся к ситуации «что будет, если вызвать GetSomeData до вызова Expose».

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

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

Нет ничего нерешаемого в данной ситуации, если это достаточно критично для приложения.

А смысл? Поведение между Expose/GetSomeData и просто GetSomeData ничем не отличается. Второе даже лучше, потому что сеодинение захватывается попозже.

С помощью вызовов Expose/Dispose можно более точно контролировать время жизни соединения, если это нужно. Более того так очень легко проверить доступность сервера базы данных (протестировать соединение).

Вы не понимаете, похоже. В моем случае не надо ничего оборачивать в try-catch (параноики могут обернуть вызов _conn.Open в конструкторе, но это не обязательно), потому что объект хоть и будет создан, но на него не будет ни одной ссылки, поэтому он будет сразу собран GC. А в вашем случае некорректно инициализированное соединение будет висеть внутри вашего объекта, и никакого способа избавиться от него нет (если, конечно, вы не реализуете try-catch внутри Expose).

Как так не нужно оборачивать? Если у вас в конструкторе возникнет исключение, а вы его нигде не обработаете, то приложение упадёт с UnhandledException. Или вы что-то другое имеете в виду?

А ничего, что TimeoutException бывает не только в таких случаях, и не всегда гарантировано, что соединение закрыто?

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

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

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

Exposable паттерн. Независимые инжекции путём экспанирования

Зато теперь все стало еще смешнее. Теперь при вызове GetSomeData незаметно для пользователя откроется соединение. А когда оно закроется? Никто не знает.

Если вас эта строчка очень смущает, то можете её убрать. Гарантировано Dispose вызовется, когда ссылок на объект не останется.

Но ладно бы у вас было неявное состояние, это еще можно понять, но какой тогда смысл в методе Expose? Можно просто вызывать GetSomeData, поведение будет ровно таким же.

Есть ещё свойство HasConnection, поэтому при желании можно контролировать наличие соединения методами Expose/Dispose, или можно не контролировать если лень или это не критично.

Собственно, один минус никуда не делся — что случится, если внутри Expose, на вызове _conn.Open, произойдет ошибка?

Вызов можно обернуть в try-catch блок и произвести нужные действия в зависимости от типа исключения. В вашем же случае нужно оборачивать в try-catch весь блок using, что уже смотрится не очень, поскольку последний тоже разворачивается в try-finally.

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

Здесь имеется в виду превышение интервала бездействия по которому соединение закрылось автоматически.

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

Exposable паттерн. Независимые инжекции путём экспанирования

Если внутри конструктора будет эксепшн, объект сразу попадет в очередь GC, где и будет радостно собран вместе со всеми его зависимостями (включая вызов финализатора, если тотопределен)

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

Вызов GetSomeData до инициализации объекта невозможен

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

Меня бы устроила примерно следующая реализация.

    internal class UserDataProvider : IExposable, IDisposable
    {
        public Guid UserId { get; set; }
        public IDictionary<string, object> SessionData { get; private set; }

        private SqlConnection _conn;

        public bool HasConnection { get { return _conn != null; } }

        public UserDataProvider()
        {
            Expose();
        }

        ~UserDataProvider()
        {
            Dispose();
        }

        public void Expose()
        {
            if (HasConnection) return;
            _conn = new SqlConnection();
            _conn.Open();
        }

        public void Dispose()
        {
            if (!HasConnection) return;
            _conn.Dispose();
            _conn = null;
        }

        public X GetSomeData()
        {
            if (!HasConnection) Expose();

            try
            {
                var cmd = _conn.CreateCommand();
                //...
            }
            catch (TimeoutException exception)
            {
                _conn = null;
                return GetSomeData();
            }
        }
    }


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

Exposable паттерн. Независимые инжекции путём экспанирования

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

Exposable паттерн. Независимые инжекции путём экспанирования

Почему нет разделения ответственности? Всё так же, как и у вас, единственное отличие в том, что вместо new Provider(ConnectionString) будет _provider.Expose(). Если вам привычнее классический вариант, то используйте его, или назовите методы по-другому, суть паттерна останется прежней.

Мне нравится связка Expose/Dispose, непривычно, но ничего сверхсложного в ней тоже нет.

Exposable паттерн. Независимые инжекции путём экспанирования

Или тривиально решается при помощи Expose/Dispose с тем же разделением ответственности, но только провайдер не создаётся заново каждый раз, а выходит из режима ожидания. Просто альтернативные способы со своими плюсами и минусами. И каждый имеет право на жизнь.

Exposable паттерн. Независимые инжекции путём экспанирования

Пусть это будет провайдер данных для пользователя. Внутри он хранит информацию о сессии пользователя и кэш-данных. В Exposed состоянии (активная работа пользователя) держит соединение к БД, а в Disposed (простой или малая активность) ожидает запросов или использует только кэш.

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

Exposable паттерн. Независимые инжекции путём экспанирования

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

Exposable паттерн. Независимые инжекции путём экспанирования

Пускай есть объект, который достаточно дорог при создании и занимает ресурсы которые нужно переодически освобождать, а также важно его внутреннее состояние. То есть вариант с UOW и пулом не подходит, поскольку внутреннее состояние должно оставаться прежним. Совместное же использование Expose/Dispose решает эту проблему.

Если вы обнаружили что-то новое, используйте это новое, а не трогайте уже существующее. Вы правда не понимаете смысла понятия «паттерн»/«шаблон проектирования»?

Глупо соревноваться в том, кто лучше или хуже понимает понятие «паттерн». Просто мы с вами вкладываем в него несколько отличающийся смысл. Часто новое строится на основе уже существующего, для меня это как раз тот случай.

Exposable паттерн. Независимые инжекции путём экспанирования

Соглашусь с вашими рассуждениями. Вероятно, подобрал не самый удачный пример для иллюстрации паттерна. Взгляните на вышестоящий комментарий habrahabr.ru/post/256629/#comment_8395113, чтобы лучше уловить суть того, что мне хотелось сказать.

Exposable паттерн. Независимые инжекции путём экспанирования

Мой ответ, наверняка, вас не устроит — в этом есть красота :)
По крайней мере, вижу её там очень чётко.

Чем вас не устраивает object pool, в котором все уже реализовано?

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

Потому что его семантика уже определена.

Возможно, иногда стоит выходить за привычные границы, чтобы обнаружить что-то новое.

Exposable паттерн. Независимые инжекции путём экспанирования

В том-то и дело, что Dispose — это не деинициализация, это полный отказ от объекта.

В обычном понимании отказ, но если вдуматься, то не совсем так.Cсылка на объект вполне может оставаться и после вызова Dispose. Зачастую обращение к большинству свойств и методов вызовет исключение, если программист это предусмотрел, но экземпляр обычно запросто можно использовать в качестве ключа, вызывать ToString, Equals… Так почему бы не расширить понимание паттерна Disposable? Пусть метод приводит объект в дежурное состояние, когда он занимает меньше ресурсов, в спящий режим! Но тогда должен существовать и метод выводящий из этого состояния — Expose. Всё очень закономерно и логично. То есть мы получили некоторое обобщение паттерна Disposable, а ваш сценарий с отказом от объекта — это лишь его частный случай…

Exposable паттерн. Независимые инжекции путём экспанирования

И вас вот вообще не смущает, что DbContext — это UOW, и его нельзя разделять между разными клиентами?

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

На основании чего вы думаете, что создание и уничтожение DbContext — такая дорогая операция?

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

Это означает, что не «существует паттерн Exposable», а «я нашел что-то похожее на паттерн». Так вот, то, что вы пытаетесь выше показать для DbContext, называется object pooling, и реализуется не так. А то, что вы показываете во вью-моделях — это обычный инициализационный метод. И это два разных паттерна для решения разных задач.

Даже если классический object pooling выглядит по-другому, то это не означает, что альтернативных реализаций быть не может. Если Expose — обычный инициализационный метод, то Dispose — деинициализационный — всё просто :)

Но стоит учитывать те преимущества, которые даёт использование этого инициализационного метода перед классическим вызовом конструктора.

Хочу лишь спросить, не замечаете ли вы своего рода гармонии и красоты в существовании Expose и Dispose, конструктора и финализатора? Это, конечно, странный вопрос, но лично меня в программировании цепляет такая стройность…

Exposable паттерн. Независимые инжекции путём экспанирования

Да, спасибо! Вкралась опечатка. Уже исправил.

Exposable паттерн. Независимые инжекции путём экспанирования

При необходимости цикличных ссылок двух классов друг на друга, проще поддерживать шаблон медиатор.

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

Exposable паттерн. Независимые инжекции путём экспанирования

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

Решение:
Когда приходит запрос, проверяется по таймеру состояние объекта context и, если он не готов к работе, вызывается метод Expose, после чего включается таймер, например, на секунд 10. Если запросов больше не было, то спустя этот промежуток вызывается Dispose, иначе таймер продлевается. В результате соединение слишком долго не держится и не нужно постоянно создавать и удалять context. По-моему, по такой схеме вполне можно получить выигрыш в производительности. Не проверял это на практике, но исхожу из логических рассуждений.

А кто-нибудь, кроме вас, его признает?

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

Exposable паттерн. Независимые инжекции путём экспанирования

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

Exposable и Disposable применимы как раздельно, так и совместно — зависит от ситуации. Внимания заслуживает тот факт, что оба паттерна обратно симметричны относительно жизненного цикла объекта.

Exposable паттерн. Независимые инжекции путём экспанирования

Самое очевидное, что можно сделать для синхронизации:

lock(context)
{
    context.Expose();
    persons = context.Persons.Where(p=>p.Age > minAge).ToList();
    context.Dispose();
}

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

Реальных измерений производительности в описанных сценариях у меня нет, тут могут оказывать влияние многие факторы, например, ресурсоёмкость объекта DbContext, наличие свободной оперативной памяти, количество и временное распределение запросов. Суть в том, чтобы показать, что существует такой паттерн Exposable обратный Disposable и их возможно применять как по раздельности, так и вместе.

Информация

В рейтинге
Не участвует
Зарегистрирован
Активность