Wstrzyknięcie tokena anulowania

Sep 30 2020

Chciałbym móc za każdym razem przekazywać tokeny anulowania za pośrednictwem iniekcji zależności zamiast jako parametry. Czy to jest rzecz?

Mamy aplikację asp.net-core 2.1, w której przekazujemy wywołania z kontrolerów do labiryntu bibliotek asynchronicznych, programów obsługi i innych usług, aby spełnić bizantyjskie potrzeby obsługiwanej domeny regulacyjnej fintech.

Na górze żądania mogę zadeklarować, że chcę token anulowania, i otrzymam go:

    [HttpPost]
    public async Task<IActionResult> DoSomeComplexThingAsync(object thing, CancellationToken cancellationToken) {
        await _someComplexLibrary.DoThisComplexThingAsync(thing, cancellationToken);
        return Ok();
    }

Teraz chcę być dobrym programistą asynchronicznym i upewnić się, że moje zostaną cancellationTokenprzekazane do każdej metody asynchronicznej w łańcuchu wywołań. Chcę się upewnić, że zostanie on przekazany do EF, strumieni System.IO itp. Mamy wszystkie typowe wzorce repozytoriów i praktyki przekazywania wiadomości, których można się spodziewać. Staramy się, aby nasze metody były zwięzłe i ponosimy jedną odpowiedzialność. Mojego kierownika technicznego wyraźnie podnieca słowo „Fowler”. Więc rozmiary naszych klas i ciała funkcyjne są małe, ale nasze łańcuchy wywołań są bardzo, bardzo głębokie.

Oznacza to, że każda warstwa, każda funkcja musi przekazać ten cholerny token:

    private readonly ISomething _something;
    private readonly IRepository<WeirdType> _repository;

    public SomeMessageHandler(ISomething<SomethingElse> something, IRepository<WeirdType> repository) {
        _something = something;
        _repository = repository;
    }

    public async Task<SomethingResult> Handle(ComplexThing request, CancellationToken cancellationToken) {
        var result = await DoMyPart(cancellationToken);
        cancellationToken.ThrowIfCancellationRequested();
        result.SomethingResult = await _something.DoSomethingElse(result, cancellationToken);
        return result;
    }

    public async Task<SomethingResult> DoMyPart(ComplexSubThing request, CancellationToken cancellationToken) {
        return await _repository.SomeEntityFrameworkThingEventually(request, cancellationToken);
    }

Trwa to w nieskończoność, zgodnie z potrzebami złożoności naszej domeny. Wydaje się, że CancellationTokenpojawia się więcej razy w naszej bazie kodów niż jakikolwiek inny termin. Nasze listy argumentów są często zbyt długie (tj. Więcej niż jeden) w obecnej postaci, mimo że deklarujemy milion typów obiektów. A teraz mamy tego dodatkowego małego kumpla tokena anulowania, który kręci się po każdej liście argumentów, każdej deklaracji metody.

Moje pytanie brzmi, skoro Kestrel i / lub rurociąg dały mi token w pierwszej kolejności, byłoby wspaniale, gdybym mógł mieć coś takiego:

    private readonly ISomething _something;
    private readonly IRepository<WeirdType> _repository;
    private readonly ICancellationToken _cancellationToken;

    public SomeMessageHandler(ISomething<SomethingElse> something, ICancellationToken cancellationToken) {
        _something = something;
        _repository = repository;
        _cancellationToken = cancellationToken;
    }

    public async Task<SomethingResult> Handle(ComplexThing request) {
        var result = await DoMyPart(request);
        _cancellationToken.ThrowIfCancellationRequested();
        result.SomethingResult = await _something.DoSomethingElse(result);
        return result;
    }

    public async Task<SomethingResult> DoMyPart(ComplexSubThing request) {
        return await _repository.SomeEntityFrameworkThingEventually(request);
    }

To byłoby następnie przekazywane przez kompozycję DI, a kiedy miałem coś, co wyraźnie potrzebuje tokenu, mogłem zrobić to:

    private readonly IDatabaseContext _context;
    private readonly ICancellationToken _cancellationToken;

    public IDatabaseRepository(IDatabaseContext context, ICancellationToken cancellationToken) {
        _context = context;
        _cancellationToken = cancellationToken;
    }

    public async Task<SomethingResult> DoDatabaseThing() {
        return await _context.EntityFrameworkThing(_cancellationToken);
    }

Czy jestem szalony? Czy po prostu przekazuję ten cholerny żeton za każdym razem i chwalę asynchronicznych bogów za daną nagrodę? Czy powinienem po prostu przekwalifikować się na farmera lam? Wydają się mili. Czy to w ogóle jest jakaś herezja? Czy powinienem teraz żałować? Myślę, że aby async / await działało poprawnie, token musi znajdować się w pliku func decl. Więc może to jest lamy

Odpowiedzi

1 Keith Oct 06 2020 at 04:19

Przede wszystkim są 3 zakresy wtrysku: Singleton, Scoped i Transient. Dwie z nich wykluczają używanie wspólnego tokena.

Usługi DI dodane za pomocą AddSingletonistnieją we wszystkich żądaniach, więc każdy token anulowania musi zostać przekazany do określonej metody (lub w całej aplikacji).

Usługi DI dodane za pomocą AddTransientmogą być tworzone na żądanie i mogą wystąpić problemy, gdy zostanie utworzone nowe wystąpienie dla tokenu, który został już anulowany. Prawdopodobnie potrzebowaliby jakiegoś sposobu na przekazanie bieżącego tokenu do [FromServices]lub innej zmiany w bibliotece.

Jednak AddScopedmyślę, że jest sposób i pomogła mi ta odpowiedź na moje podobne pytanie - nie możesz przekazać samego tokena do DI, ale możesz przejść IHttpContextAccessor.

Tak więc w Startup.ConfigureServiceslub metoda rozszerzenia, której używasz do rejestrowania dowolnego IRepositoryużycia:


// For imaginary repository that looks something like
class RepositoryImplementation : IRepository {
    public RepositoryImplementation(string connection, CancellationToken cancellationToken) { }
}

// Add a scoped service that references IHttpContextAccessor on create
services.AddScoped<IRepository>(provider => 
    new RepositoryImplementation(
        "Repository connection string/options",
        provider.GetService<IHttpContextAccessor>()?.HttpContext?.RequestAborted ?? default))

Ta IHttpContextAccessorusługa zostanie pobrana raz na żądanie HTTP i ?.HttpContext?.RequestAbortedzwróci to samo, CancellationTokenco w przypadku wywołania this.HttpContext.RequestAbortedz wnętrza akcji kontrolera lub dodania jej do parametrów akcji.