首页 > 解决方案 > 如何更好地构建架构

问题描述

我有一个 ASP.NET Core 应用程序,它从另一个库调用服务。该服务使用外部 API,这需要sessionId. 我们必须调用 Login API 方法来获取sessionId. 这sessionId能活多久,什么时候可以改变——我们不知道。规则是:sessionId1个请求有效,10个请求有效,100个请求有效,1分钟有效,10分钟有效,1天有效……没人知道。

该服务有许多方法可以调用类似的 API:

public class BillRequest
{
    private readonly HttpClient client;

    public BillRequest()
    {
        client = new HttpClient
        {
            BaseAddress = new Uri("https://myapi.com/api/v2/")
        };
    }

    public async Task<List<Dto1>> CustomerBankAccountListAsync(int start, int count)
    {
        List<KeyValuePair<string, string>> nvc = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string, string>("sessionId", CURRENT_SESSION_ID)
        };

        var customerStream = await client.PostAsync("List/CustomerBankAccount.json", new FormUrlEncodedContent(nvc));
        var customerString = await customerStream.Content.ReadAsStringAsync();
        //....
    }

    public async Task<List<Dto2>> Method2(int start, int count)
    {
        List<KeyValuePair<string, string>> nvc = new List<KeyValuePair<string, string>>
        {
            new KeyValuePair<string, string>("sessionId", CURRENT_SESSION_ID)
        };

        var customerStream = await client.PostAsync("List/Method2.json", new FormUrlEncodedContent(nvc));
        var customerString = await customerStream.Content.ReadAsStringAsync();
        //....
    }

    // logic to get SessionId here
    public async Task LoginAsync()
    {

    }

如何实施以保存此sessionId内部服务?

有很多选项可以实现:

  1. Login每次调用方法之前调用方法。易于实现,但方法不好,因为那时我们有很多不必要的请求并且sessionId只使用一次

  2. 保存sessionIdweb 应用程序级别并尝试捕获异常,当任何方法返回“无效 sessionId”时,然后调用Login方法,该方法将返回一个新的sessionId. 在这种情况下,我们必须传递sessionId给类的构造函数BillRequest。它可以工作,但我不喜欢将服务责任转移给其他人,因为如何使用 API 是服务的内部责任。

  3. 保存sessionId在服务本身内部并在服务内部调用Login方法,当旧sessionId的被认为无效时,用新的等重写它。但是如何将其保存为内存中的“静态”?我不想将它保存到任何外部位置(文件系统、云等),但我也不能保存到类的变量中,因为可以重新创建类的对象......

标签: c#apiasp.net-corearchitectureglobal-variables

解决方案


我建议在这里对函数式编程进行一定的心理转变。

sessionID其视为独立值而不是单个对象。然后可以通过以下(语义等效)方式重新定义您的问题:给定一个类型化的流(string在您的情况下),如何观察其流程并对传入的更改做出反应,而您的源代码无法控制?

嗯,有一个由 Enterprise™ 证明的答案:反应式扩展

从技术上讲,这种转变意味着您正在处理IObservable<string>控制器的内部,它可以通过标准的 .NET Core DI 方法注入,或者简单地由构造函数定义。这是非常灵活的,因为rX它为您提供了完全可测试、令人难以置信的强大工具集来处理此类任务;rX也与本机兼容,Task因此也与async/await功能兼容。不错的事实是,从外部世界注入所需的行为并用更合适的行为装饰现有的 observable 非常容易:因此,您很安全:一旦第 3 方的服务逻辑发生变化,您几乎可以立即轻松地采用您的代码库。

里面会是什么IObservable<string>?好吧,我不能说,因为你没有提供足够的信息。这可能是一个间隔,询问远程服务器当前sessionID是否仍然有效,如果不是 - 运行重新登录程序并通知它的订阅者新值;它可能是一个负责编译时已知到期规则的计时器rX,它可能是您需要的复杂逻辑:足够灵活,不会限制您使用它可以实现的目标,只要您处理(可能是无限的)流.

因此,这意味着您不需要任何全局值。只需订阅会话 ID 流并获取最新的 - 当前有效的,完成工作并处理您的订阅。它贵,不会影响性能;两者都不会破坏并发性。如果您想坚持一种常见的 .NET 方式,请rX使用它Taskawait

PS 交付实施所需的 99% 已经存在;你只需要结合它。


推荐阅读