神经网络模块化_快速模块化网络

神经网络模块化

Networking has always been a complex problem in mobile development. You want your application to be fast, have up to date information, and be maintainable. Over the years as server technology has gotten more complex, so have our requirements for mobile networking. A simple URLSession abstraction no longer scales to meet modern business needs.

联网一直是移动开发中的一个复杂问题。 您希望您的应用程序速度快,具有最新信息并且可维护。 多年来,随着服务器技术变得越来越复杂,我们对移动网络的要求也越来越高。 简单的URLSession抽象不再能够满足现代业务需求。

These days it is common to create a networking abstraction that accepts a generic request object with return type information, and then returns either a typed object or an error. But how do you handle all of the other baggage that is involved in making a network call? Some common problems include adding required headers, token refresh, request accumulation, caching, etc. At Ibotta, we’ve implemented a lightweight middleware and postware system to maximize reusability and testability.

如今,通常会创建一种网络抽象,该抽象接受具有返回类型信息的通用请求对象,然后返回类型化对象或错误。 但是,您如何处理网络通话中涉及的所有其他行李呢? 一些常见的问题包括添加所需的标头,令牌刷新,请求累积,缓存等。在Ibotta,我们实现了轻量级的中间件和后件系统,以最大程度地提高可重用性和可测试性。

The idea is to create a series of small utility classes that mutate requests in a chain to enable business logic. We call these small classes middlewares and postwares. Middlewares run before a request hits the network and can either pass the request along to the next middleware, or abort the operation. Postwares run after a request hits the network.

这个想法是创建一系列小的实用程序类,这些类可以改变请求链中的请求以启用业务逻辑。 我们称这些小类为中间件和后件。 中间件在请求到达网络之前运行,可以将请求传递到下一个中​​间件,也可以中止操作。 在请求到达网络后运行后件。

实作 (Implementation)

I am going to go over the high level implementation, but in order to stay focused on the meat of modular networking, some boiler plate code will be excluded. However, it should be pretty clear how to connect the missing dots to use this in your own app.

我将介绍高级实现,但是为了专注于模块化网络,将排除一些样板代码。 但是,应该很清楚如何连接缺失的点以在自己的应用程序中使用它。

Image for post

We are going to create two key classes to handle our logic: an HttpManager and an HttpClient. The manager will be responsible for processing a request through the middlewares, and later processing the response through the postwares. The client will be in charge of sending the request to the network. Their public interfaces look like:

我们将创建两个关键类来处理我们的逻辑: HttpManagerHttpClient 。 经理将负责通过中间件处理请求,然后再通过后件处理响应。 客户端将负责将请求发送到网络。 它们的公共接口如下:

protocol HttpManagerInterface {
    func register(ware: HTTPWare)
    
    func register(wares: [HTTPWare])


    var client: HttpClientInterface { get set }


    @discardableResult
    func enqueue(request: HttpRequest, completion: @escaping (Result<HttpResult, HttpError>) -> Void) -> Cancellable
}


typealias HttpResult = Result<HttpResponse, HttpError>
protocol HttpClientInterface {
    func process(request: HttpRequest, completion: @escaping (HttpResult) -> Void) -> Cancellable
}

An HttpRequest contains all of the information needed to send a request to the network. But it does not contain type information about the expected return type; towards the end of this article we will add that capability on top of the HttpManager.

HttpRequest包含将请求发送到网络所需的所有信息。 不过,这并不包含有关预期收益类型的类型信息; 在本文结尾处,我们将在HttpManager之上添加该功能。

Next we need to define what middlewares and postwares look like. They will end up housing most of our networking logic. For convenience, we sometimes call middleware and postwares HttpWare. Their protocols look like:

接下来,我们需要定义中间件和后件的外观。 他们最终将容纳我们的大多数网络逻辑。 为了方便起见,我们有时将中间件和后件称为HttpWare 。 他们的协议如下:

protocol HTTPWare { }


protocol MiddleWare: HTTPWare {
    var middleOrder: WareSortOrder { get }
    func handle(request: HttpRequest, next: @escaping (HttpRequest) -> Void, abort: @escaping (HttpResult) -> Void)
}


protocol Postware: HTTPWare {
    var postOrder: WareSortOrder { get }
    func handle(request: HttpRequest, result: HttpResult, next: @escaping (HttpResult) -> Void)
}

Notice how a middleware can abort a request. This can be used for things like early exiting when a cache hit occurs, or aborting if we know the device is offline. To help keep HttpWares in an ordered chain, we allow them to specify a sort order.

注意中间件如何中止请求。 这可用于诸如在发生高速缓存命中时提早退出或在我们知道设备处于脱机状态时中止的操作。 为了帮助将HttpWares保持在有序链中,我们允许它们指定排序顺序。

If we wanted to add authentication headers to all requests we could create a super simple middleware like:

如果我们想向所有请求添加身份验证标头,我们可以创建一个超级简单的中间件,例如:

final class AuthMiddleWare: MiddleWare {
    var middleOrder: WareSortOrder { return .last }


    func handle(request: HttpRequest, next: (HttpRequest) -> Void, abort: (HttpResult) -> Void) {
        var request = request


        if let token = TokenService.default.token {
            request.headers["Authorization"] = "Bearer \(token)"
        }


        next(request)
    }
}

Assuming we can mock the TokenService, this tiny class is super easy to test. One of the big benefits to this approach is testability. We can build out complex HttpWares and write pretty simple unit tests directly against them. Then all of these well-tested classes can be injected into our HttpManager, and we can be confident everything works.

假设我们可以模拟TokenService ,那么这个小类非常容易测试。 这种方法的最大好处之一就是可测试性。 我们可以构建复杂的HttpWares并直接针对它们编写非常简单的单元测试。 然后,所有这些经过良好测试的类都可以注入到我们的HttpManager ,我们可以确信一切正常。

Middlewares and postwares can act as a bridge between services and our networking requests. In the above example, the AuthMiddleWare acts as a bridge between the TokenService and our networking layer.

中间件和后件可以充当服务与我们的网络请求之间的桥梁。 在上面的示例中, AuthMiddleWare充当TokenService和我们的网络层之间的桥梁。

So now that we’ve added an auth token to all our requests, what happens if we need to refresh the auth token? We could have several incoming requests when we notice we need to refresh our token, so we will want to collect all incoming requests while we refresh our token. This can be done with another middleware:

因此,既然我们已经向所有请求添加了身份验证令牌,如果我们需要刷新身份验证令牌会发生什么情况? 当我们注意到需要刷新令牌时,我们可能会有多个传入请求,因此我们将希望在刷新令牌时收集所有传入请求。 这可以用另一个中间件来完成:

final class TokenRefreshMiddleWare: MiddleWare {


    var middleOrder: WareSortOrder { return .middle }


    private var capturedRequests = [(HttpError?) -> Void]()
    private var state: State = .off


    func handle(request: HttpRequest, next: @escaping (HttpRequest) -> Void, abort: @escaping (HttpResult) -> Void) {
        guard TokenService.default.shouldTokenBeRefreshed() else { return next(request) }


        capturedRequests.append({ error in
            if let error = error {
                abort(.failure(error))
            }
            else {
                next(request)
            }
        })


        guard state == .off else { return }
        state = .refreshing


        TokenService.default.refreshTokenIfNeeded { error in
            self.capturedRequests.forEachRemove { $0(error) }
            self.state = .off
        }
    }
}

Notice how this middleware defines an order of .middle and the AuthMiddlWare defined an order of .last . Ordering a lot of HttpWares can be tricky, but we’ve found that in practice a lot of wares don’t require a predefined order.

通知此中间件如何定义的顺序.middleAuthMiddlWare定义的顺序.last 。 订购许多HttpWare可能很棘手,但是我们发现实际上许多商品不需要预定义的订单。

Now let’s say we made a network call and we received a 401 letting the app know to log the user out. This is a great opportunity to use a postware:

现在,我们打电话了,收到了401,让应用知道注销用户。 这是使用后件的绝佳机会:

final class TokenValidationPostWare: Postware {
    func handle(request: HttpRequest, result: HttpResult, next: @escaping (HttpResult) -> Void) {
        guard case .failure(let error) = result else { return next(result) }


        // Confirm we have an invalid token
        guard error.code == 401 else { return next(result) }


        TokenService.default.clear()
        next(result)
    }
}

We’ve just enabled complex auth token logic without touching our core networking code. Pretty cool!

我们只是启用了复杂的身份验证令牌逻辑,而没有涉及我们的核心网络代码。 太酷了!

We’ve only looked at classes that are either a middleware or a postware, but a class could be both a middleware and postware. A 304 cache for instance would want to save response data as a postware and adjust requests with a timestamp as a middleware.

我们仅查看了既可以是中间件也可以是后件的类,但是一个类可以既是中间件又可以是后件。 例如,一个304缓存希望将响应数据保存为后件,并以时间戳作为中间件来调整请求。

To implement the facilitation of these HttpWares, we define a function to process a request through the middlewares, and a function to process a request + a response through the postwares. This would look something like:

为了实现这些HttpWare的简化,我们定义了一个通过中间件处理请求的功能,以及一个通过后件处理请求+响应的功能。 这看起来像:

final class HttpManager: HttpManagerInterface {    
    // ...
    private func processRequestThrough(middlewares: [MiddleWare],
                                       request: HttpRequest,
                                       onProcessed: @escaping (HttpRequest) -> Void,
                                       onReturn: @escaping (HttpResult) -> Void) {


        guard let ware = middlewares.first else {
            return onProcessed(request)
        }


        ware.handle(request: request, next: { (handled) in
            let middlewares = Array(middlewares[1...])
            self.processRequestThrough(middlewares: middlewares, request: handled, onProcessed: onProcessed, onReturn: onReturn)
        }, abort: { (result) in
            onReturn(result)
        })
    }
    // ...

完善实施 (Polishing Up Our Implementation)

At this point our HttpManager accepts an HttpRequest, but an HttpRequest does not contain type information. To polish up our HttpManager, we will implement a new request type that contains type information so we can return type safe results.

此时,我们的HttpManager接受HttpRequest ,但是HttpRequest不包含类型信息。 为了HttpManager ,我们将实现一个包含类型信息的新请求类型,以便我们可以返回类型安全的结果。

struct Request<Expected> where Expected: Decodable {
    var path: String
    var method: HTTP.Method
    var params: Any?
    // ...
}

Then inside our HttpManager we will convert a Request to an HttpRequest, and then process that request through our HttpWares.

然后,在我们的HttpManager我们将一个Request转换为一个HttpRequest ,然后通过我们的HttpWares处理该请求。

protocol HttpManagerInterface {
    init(service: ServiceDefinition)
    
    func register(ware: HTTPWare)


    func register(wares: [HTTPWare])
    
    @discardableResult
    func enqueue<Expected: DataDecodable>(requests: Request<Expected>, completion: @escaping (Result<Expected, HttpError>) -> Void) -> Cancellable
}

Another optimization we can make is to define a ServiceDefinition for each service we send networking requests to. A ServiceDefinition contains information like base url and default encoding strategies. Then when creating an HttpManager, a ServiceDefinition can be injected at initialization.

我们可以做的另一种优化是为向网络发送请求的每个服务定义一个ServiceDefinitionServiceDefinition包含诸如基本URL和默认编码策略之类的信息。 然后,当创建HttpManager ,可以在初始化时注入ServiceDefinition

结论 (Conclusion)

Using middlewares and postwares to encapsulate networking logic is a great way of maintaining separation of concern while enabling complex networking logic. There is no limit to what you can build into your networking layer using this modular approach. This is a great bottom layer to build additional layers of abstraction on top of.

使用中间件和后件封装网络逻辑是在启用复杂网络逻辑的同时保持关注点分离的一种好方法。 使用此模块化方法,您可以在网络层中构建的内容不受限制。 这是一个很好的底层,用于在其之上构建其他抽象层。

Some useful wares we use include request accumulators, http cache, 304 cache, time logging, token refresh, token validation, and various middlewares for different required headers. At Ibotta we have 19 middlewares and postwares that our services use. Some are used for almost every request, and some are only used in rare cases.

我们使用的一些有用的软件包括请求累加器,http缓存,304缓存,时间记录,令牌刷新,令牌验证以及用于不同所需标头的各种中间件。 在Ibotta,我们有19种中间件和我们的服务使用的后件。 有些几乎用于每个请求,有些仅在极少数情况下使用。

我们正在招聘! (We’re Hiring!)

If these kinds of projects and challenges sound interesting to you, Ibotta is hiring! Check out our jobs page for more information.

如果您觉得这些项目和挑战听起来很有趣,那么Ibotta正在招聘! 请查看我们的工作页面以获取更多信息。

翻译自: https://medium.com/building-ibotta/modular-networking-in-swift-c59cc590da3e

神经网络模块化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值