Как работать с такими сервисами, как транзакции, в среде DI и IOC - PullRequest
5 голосов
/ 03 мая 2011

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

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

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

Чтобы иметь возможность использовать using, нам нужен IDisposable, но, поскольку интерфейс зависимости не должен быть IDisposable, как вы можете обойти его, чтобы получить ограниченное поведение?

Ответы [ 4 ]

4 голосов
/ 03 мая 2011

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

public interface ITransaction : IDisposable
{
}

public interface ITransactionFactory 
{
    ITransaction CreateTransaction();
}

public class Foo
{
    private readonly ITransactionFactory transactionFactory;

    public Foo(ITransactionFactory transactionFactory)
    {
        this.transactionFactory = transactionFactory;            
    }

    public void DoSomethingWithinTransaction()
    {
        using(ITransaction transaction = this.transactionFactory.CreateTransaction())
        {
            DoSomething();
        }
    }
}
1 голос
/ 03 мая 2011

Большинство контейнеров IoC сегодня имеют существенную встроенную поддержку для единиц работы такого типа.

В Autofac механизм, который наилучшим образом соответствует вашим требованиям, - это тип отношений Owned<T>. Вы можете увидеть это в действии (и получить больше материала) через эту статью .

Надеюсь, это поможет,

Ник

1 голос
/ 03 мая 2011

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

public class DisposableWrapper<T> : IDisposable
{
    private readonly T _instance;
    private readonly IDisposable _disposable;

    public DisposableWrapper(T instance)
    {
        _instance = instance;
        _disposable = instance as IDisposable;
    }

    public void Dispose()
    {
        if (_disposable != null)
            _disposable.Dispose();
    }

    public static implicit operator T(DisposableWrapper<T> disposableWrapper)
    {
        return disposableWrapper._instance;
    }
}

(Надеюсь, с немного большей обработкой ошибок!)

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

public static class DisposableExtensions
{
    public static DisposableWrapper<T> Wrap<T>(this T instance)
    {
        return new DisposableWrapper<T>(instance);
    }
}

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

public interface IUserService
{
    IUser GetUser();
}

Я мог бы потенциально сделатьчто-то вроде:

public HomeController(IUserService service)
{
    using (var disposable = service.Wrap())
    {
        var user = service.GetUser();

        // I can even grab it again, implicitly.
        IUserService service2 = disposable;
    }
}

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

Другойпример быстрой консоли:

class Program
{
    static void Main(string[] args)
    {
        using (var instance = new ClassA().Wrap())
        {
            ClassA instanceA = instance;
        }

        using (var instance = new ClassB().Wrap())
        {
            ClassB instanceB = instance;   
        }

        Console.ReadKey();
    }
}

public class ClassA
{

}

public class ClassB : IDisposable
{
    public void Dispose()
    {
        Console.Write("Disposed");
    }
}
1 голос
/ 03 мая 2011

Свернуть свой собственный "сборщик мусора" может быть?Что-то, что периодически проверяет IsComplete и / или атрибут LastAccessed на Dictionary<Transaction> и тратит «старые».Это «ходячая утечка памяти», но вы либо выполняете очистку явно (например, с помощью IDisposable), либо вы учитесь, как выполнять очистку автоматически.

Может быть решение AOP для отключения «gc».... фиксация / откат звучит как хорошее место для обрезки ... и, возможно, вам даже не понадобится сборщик мусора ... просто очистите транзакцию при резервном копировании стека вызовов из фиксации или отката.

Удачи с этим.Мне будет интересно посмотреть, какие решения (и идеи) придут другим людям.

Приветствия.Кит.

...