Где разместить AsyncScopedLifestyle при использовании простого инжектора - PullRequest
0 голосов
/ 03 июня 2019

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

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

class CallTaskFactory
{
    private readonly Container Container;

    public CallTaskFactory(Container container)
    {
        Container = container;
    }

    public async Task CreateCallTask()
    {
        using (Scope scope = AsyncScopedLifestyle.BeginScope(Container))
        {
            // Get the socket's destination
            SocketDestinationProvider socketDestProvider =
                Container.GetInstance<SocketDestinationProvider>();

            EndPoint ep = await socketDestProvider.GetSocketDestination();

            // Now create a socket and connect to that destination
            Socket socket = Container.GetInstance<Socket>();
            await socket.ConnectAsync(ep);

            // Send a simple message on the socket
            var Sender1 = Container.GetInstance<MessageSender1>();
            await Sender1.SendStartMessage();

            // Send another message, and the response tells us whether we need
            // to create some object that does something on a timer
            var Sender2 = Container.GetInstance<MessageSender2>();
            var Response = await Sender2.SendStartMessageAndAwaitResponse();
            if (Response.Result)
            {
                Container.GetInstance<ClassThatChecksSomethingOnATimer>();
            }

            // The call stays active until the socket closes
            TaskCompletionSource<int> Completion = new TaskCompletionSource<int>();
            socket.Closed += (sender, e) => { Completion.TrySetResult(0); };
            await Completion.Task;

            // Clean up
            await Sender2.SendStopMessage();
            await Sender1.SendStopMessage();
            await socket.DisconnectAsync();
        }
    }
}

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

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

1 Ответ

0 голосов
/ 03 июня 2019

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

Абсолютно. Только Composition Root должен относиться к контейнеру. Обычно это означает, что вам нужно ввести абстракцию. Это позволяет реализовать логику адаптера, которая зависит от контейнера DI внутри корня композиции, а код приложения может зависеть от его абстракции.

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

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

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

Чтобы решить эту проблему, вы должны попытаться подойти к этому под другим углом. Ваш корень композиции в идеале должен иметь только один вызов GetInstance и один вызов метода для разрешенных объектов. Как правило, этого можно достичь, поместив весь код в новый класс. Разрешенные зависимости будут преобразованы в аргументы конструктора в новом классе.

Когда вы применяете эту технику к своему коду, вы получите что-то вроде этого:

public class CallExecutor {
    ...        
    public CallExecutor(
        SocketDestinationProvider socketDestinationProvider, Socket socket,
        MessageSender1 messageSender1, MessageSender2 messageSender2,
        ClassThatChecksSomethingOnATimer checker)
    {
        this.socketDestinationProvider = socketDestinationProvider;
        this.socket = socket;
        this.messageSender1 = messageSender1;
        this.messageSender2 = messageSender2;
        this.checker = checker;
    }

    public async Task Call()
    {
        EndPoint ep = await this.socketDestProvider.GetSocketDestination();
        await this.socket.ConnectAsync(ep);
        await this.sender1.SendStartMessage();
        var response = await this.sSender2.SendStartMessageAndAwaitResponse();
        if (response.Result)
        {
            this.checker.DoSomething(...)
        }

        TaskCompletionSource<int> Completion = new TaskCompletionSource<int>();
        socket.Closed += (sender, e) => { Completion.TrySetResult(0); };
        await Completion.Task;

        await this.sender2.SendStopMessage();
        await this.sender1.SendStopMessage();
        await this.socket.DisconnectAsync();        
    }
}

В приведенном выше коде я сделал простое преобразование один в один. Каждая решимость становилась зависимостью конструктора. Это может быть не правильно во всех случаях. Например, я могу представить, что вы хотите разрешить несколько Socket экземпляров из контейнера. Заметьте, однако, что Socket мне больше походит на данные времени выполнения. Возможно, вы захотите воздержаться от использования Контейнера для разрешения подобных объектов (как указано в этой статье ).

Этот новый класс CallExecutor может быть полностью определен как код приложения; нет никакой зависимости от DI-контейнера. Оставшийся код CallTaskFactory настолько короткий, что его легко реализовать в корне композиции:

class CallTaskFactory : ICallTaskFactory
{
    private Container container;

    public CallTaskFactory(Container container)
    {
        this.container = container;
    }

    public async Task CreateCallTask()
    {
        using (AsyncScopedLifestyle.BeginScope(this.container))
        {
            await this.container.GetInstance<CallExecutor>().Call();
        }
    }
}

Конечно, введение CallExecutor, однако, привело к стремительному созданию всех зависимостей. Это может показаться неэффективным, но не должно быть так, потому что композиция объектов должна быть быстрой , поэтому вы можете составлять графики с уверенностью . Когда вы сделаете это в сочетании с Simple Injector, вы вряд ли когда-либо столкнетесь с проблемами производительности, поскольку Simple Injector может легко создать тысячи объектов за долю секунды.

...