취소 토큰 주입

Sep 30 2020

매번 매개 변수 대신 종속성 주입을 통해 취소 토큰을 전달할 수 있기를 바랍니다. 이건가요?

우리는 우리가 서비스하는 핀 테크 규제 도메인의 비잔틴 요구를 충족시키기 위해 컨트롤러의 호출을 비동기 라이브러리, 핸들러 및 기타 서비스의 미로로 전달하는 asp.net-core 2.1 앱이 있습니다.

요청 맨 위에서 취소 토큰을 원한다고 선언하면 하나를 얻을 수 있습니다.

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

이제 저는 좋은 비동기 프로그래머가되고 싶고 cancellationToken콜 체인을 통해 모든 비동기 메서드에 전달 되는지 확인 합니다. 나는 그것이 EF, System.IO 스트림 등에 전달되는지 확인하고 싶습니다. 우리는 당신이 기대하는 모든 일반적인 저장소 패턴과 메시지 전달 관행을 가지고 있습니다. 우리는 방법을 간결하게 유지하고 하나의 책임을 갖도록 노력합니다. 내 기술 책임자는 'Fowler'라는 단어에 눈에 띄게 흥분합니다. 따라서 클래스 크기와 함수 본문은 작지만 콜 체인은 매우 깊습니다.

이것이 의미하는 것은 모든 계층, 모든 기능이 망할 토큰을 전달해야한다는 것입니다.

    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);
    }

이것은 도메인 복잡성의 요구에 따라 무한정 계속됩니다. CancellationToken다른 용어보다 코드베이스에 더 많이 나타나는 것 같습니다 . 우리의 arg 목록은 백만 개의 객체 유형을 선언하더라도 이미 너무 길다 (즉, 둘 이상). 그리고 이제 우리는 모든 arg 목록, 모든 메서드 decl에이 여분의 작은 취소 토큰 친구가 있습니다.

내 질문은 Kestrel 및 / 또는 파이프 라인이 처음에 나에게 토큰을 주었기 때문에 다음과 같은 것을 가질 수 있다면 좋을 것입니다.

    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);
    }

그런 다음 DI 구성을 통해 전달되고 토큰이 명시 적으로 필요한 것이 있으면 다음을 수행 할 수 있습니다.

    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);
    }

내가 미쳤어? 나는 매번 망할 토큰을 전달하고 주어진 현상금에 대해 비동기 신들을 칭찬 하는가? 라마 농부로 재교육을 받아야합니까? 멋져 보입니다. 이단을 요구하는 건가요? 지금 회개해야합니까? async / await가 제대로 작동하려면 토큰이 func decl에 있어야한다고 생각합니다. 그래서 아마도 라마는

답변

1 Keith Oct 06 2020 at 04:19

우선, Singleton, Scoped 및 Transient의 세 가지 주입 범위가 있습니다. 그중 두 가지는 공유 토큰 사용을 배제합니다.

추가 된 DI 서비스는 AddSingleton모든 요청에 ​​존재하므로 취소 토큰은 특정 메서드 (또는 전체 애플리케이션)에 전달되어야합니다.

추가 된 DI 서비스 AddTransient는 요청시 인스턴스화 될 수 있으며 이미 취소 된 토큰에 대해 새 인스턴스가 생성되는 문제가 발생할 수 있습니다. 현재 토큰을 전달할 방법 [FromServices]이나 다른 라이브러리 변경 방법이 필요할 수 있습니다.

그러나 AddScoped나는 방법이 있다고 생각 하고 비슷한 질문에 대한이 대답 으로 도움을 받았습니다 . 토큰 자체를 DI에 전달할 수는 없지만 IHttpContextAccessor.

따라서 in Startup.ConfigureServices또는 확장 방법을 사용하여 등록하는 데 IRepository사용합니다.


// 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))

해당 IHttpContextAccessor서비스는 HTTP 요청 당 한 번 검색 되며 컨트롤러 작업 내부에서 호출 했거나 작업의 매개 변수에 추가 ?.HttpContext?.RequestAborted한 것과 동일한 결과 를 반환합니다 .CancellationTokenthis.HttpContext.RequestAborted