Protocol-Oriented Networking

50
Protocol-Oriented Networking @mostafa_amer

Transcript of Protocol-Oriented Networking

Page 1: Protocol-Oriented Networking

Protocol-Oriented Networking@mostafa_amer

Page 2: Protocol-Oriented Networking

Starting Point#import <AFNetworking/AFNetworking.h>

@interface GHAPIClient : AFHTTPSessionManager

+ (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name

completionHandler:(void(^)(id result, NSError *error))handler; @end

Page 3: Protocol-Oriented Networking

Starting Point#import <AFNetworking/AFNetworking.h>

@interface GHAPIClient : AFHTTPSessionManager

+ (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name

completionHandler:(void(^)(id result, NSError *error))handler; @end

• Client has too many responsibilities

Page 4: Protocol-Oriented Networking

Starting Point#import <AFNetworking/AFNetworking.h>

@interface GHAPIClient : AFHTTPSessionManager

+ (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name

completionHandler:(void(^)(id result, NSError *error))handler; @end

• Client has too many responsibilities• Client is tightly coupled to the network library

Page 5: Protocol-Oriented Networking

Starting Point#import <AFNetworking/AFNetworking.h>

@interface GHAPIClient : AFHTTPSessionManager

+ (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name

completionHandler:(void(^)(id result, NSError *error))handler; @end

• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test

Page 6: Protocol-Oriented Networking

Starting Point#import <AFNetworking/AFNetworking.h>

@interface GHAPIClient : AFHTTPSessionManager

+ (instancetype)sharedClient; -(void)fetchGitHubUserWithName:(NSString *)name

completionHandler:(void(^)(id result, NSError *error))handler; @end

• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden

Page 7: Protocol-Oriented Networking

Decouplingprotocol NetworkServiceType { func requestEndpoint(path: String, method: HTTPMethod, parameters: [String: Any]?, handler: (Data?, Error?) -> ()) }

Page 8: Protocol-Oriented Networking

Decouplingclass NetworkService: AFHTTPSessionManager, NetworkServiceType { func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) {

} }

Page 9: Protocol-Oriented Networking

Decouplingclass NetworkService: AFHTTPSessionManager, NetworkServiceType { func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { switch method { case .GET: get(path, parameters: parameters, success: {_, result in handler(result, nil) }, failure: {_, error in handler(nil, error) } ) } } }

Page 10: Protocol-Oriented Networking

Hide Implementation

@interface GHAPIClient : AFHTTPSessionManager

+ (instancetype)sharedClient; - (void)fetchListWithCompletionHandler:(void(^)(id result, NSError *error))handler; @end

#import <AFNetworking/AFNetworking.h>

Page 11: Protocol-Oriented Networking

Hide Implementation

@interface GHAPIClient : NSObject

+ (instancetype)sharedClient; - (void)fetchListWithCompletionHandler:(void(^)(id result, NSError *error))handler; @end

Page 12: Protocol-Oriented Networking

@interface GHAPIClient()

@end

@implementation GHAPIClient

- (instancetype)initWithBaseURL:(NSURL *)baseURL { self = [super init]; if(self) {

} return self; }

+ (instancetype)sharedClient { static GHAPIClient *_instance = nil; if(! _instance) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{

_instance = [[GHAPIClient alloc] init]; }); } return _instance; }

Refactoring Client

Page 13: Protocol-Oriented Networking

Refactoring Client@interface GHAPIClient() @property (nonatomic, strong) id<NetworkServiceType> network; @end

@implementation GHAPIClient

- (instancetype)initWithBaseURL:(NSURL *)baseURL { self = [super init]; if(self) {

} return self; }

+ (instancetype)sharedClient { static GHAPIClient *_instance = nil; if(! _instance) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{

_instance = [[GHAPIClient alloc] init]; }); } return _instance; }

Page 14: Protocol-Oriented Networking

Refactoring Client@interface GHAPIClient() @property (nonatomic, strong) id<NetworkServiceType> network; @end

@implementation GHAPIClient

- (instancetype)initWithNetworkService:(id<NetworkServiceType>)service { self = [super init]; if(self) { _network = service; } return self; }

+ (instancetype)sharedClient { static GHAPIClient *_instance = nil; if(! _instance) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{

_instance = [[GHAPIClient alloc] init]; }); } return _instance; }

Page 15: Protocol-Oriented Networking

Refactoring Client@interface GHAPIClient() @property (nonatomic, strong) id<NetworkServiceType> network; @end

@implementation GHAPIClient

- (instancetype)initWithNetworkService:(id<NetworkServiceType>)service { self = [super init]; if(self) { _network = service; } return self; }

+ (instancetype)sharedClient { static GHAPIClient *_instance = nil; if(! _instance) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSURL *baseURL = [NSURL URLWithString:@"https://api.github.com"]; NetworkService *service = [[NetworkService alloc] initWithBaseURL:baseURL]; _instance = [[GHAPIClient alloc] initWithNetworkService:service]; }); } return _instance; }

Page 16: Protocol-Oriented Networking

Testing Clientclass NetworkMock: NetworkServiceType { var response: Data? var error: Error? func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { handler(response, error) } }

Page 17: Protocol-Oriented Networking

class APIClientSpecs: QuickSpec { override func spec() { var mock: NetworkMock! var sut: GHAPIClient! beforeEach { mock = NetworkMock() sut = GHAPIClient(networkService: mock) }

} }

Testing Clientclass NetworkMock: NetworkServiceType { var response: Data? var error: Error? func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { handler(response, error) } }

Page 18: Protocol-Oriented Networking

Testing Clientclass NetworkMock: NetworkServiceType { var response: Data? var error: Error? func requestEndpoint(path: String, method: HTTPMethod, parameters: [String : Any]?, handler: (Data?, Error?) -> ()) { handler(response, error) } }

class APIClientSpecs: QuickSpec { override func spec() { var mock: NetworkMock! var sut: GHAPIClient! beforeEach { mock = NetworkMock() sut = GHAPIClient(networkService: mock) } it("handle error") { mock.data = // Data from JSON file sut.fetchGitHubUser(loginName: "mosamer") { (_, error) in expect(error).notTo(beNil()) } } } }

Page 19: Protocol-Oriented Networking

Client Again

- (void)fetchGitHubUser:(NSString *)name completionHandler:(void (^)(id, NSError *))handler {

[self.network requestEndpointWithPath:[NSString stringWithFormat:@“users/%@”,name] method:HTTPMethodGET parameters:nil handler:^(id result, NSError *error) { handler(result, error); }]; }

Page 20: Protocol-Oriented Networking

Client Again

- (void)fetchGitHubUser:(NSString *)name completionHandler:(void (^)(id, NSError *))handler {

[self.network requestEndpointWithPath:[NSString stringWithFormat:@“users/%@”,name] method:HTTPMethodGET parameters:nil handler:^(id result, NSError *error) { handler(result, error); }]; }

• Client still has too many responsibilities

Page 21: Protocol-Oriented Networking

Client Again

- (void)fetchGitHubUser:(NSString *)name completionHandler:(void (^)(id, NSError *))handler {

[self.network requestEndpointWithPath:[NSString stringWithFormat:@“users/%@”,name] method:HTTPMethodGET parameters:nil handler:^(id result, NSError *error) { handler(result, error); }]; }

• Client still has too many responsibilities• Client lacks general handlers

Page 22: Protocol-Oriented Networking

More Protocols

Page 23: Protocol-Oriented Networking

More Protocols

protocol Resource { var path: String { get } var method: HTTPMethod { get } var parameters: [String: Any]? { get }

}

Page 24: Protocol-Oriented Networking

More Protocolsprotocol Response {} protocol Resource { var path: String { get } var method: HTTPMethod { get } var parameters: [String: Any]? { get }

associatedtype ResponseType: Response

}

Page 25: Protocol-Oriented Networking

More Protocolsprotocol Response {} protocol Resource { var path: String { get } var method: HTTPMethod { get } var parameters: [String: Any]? { get }

associatedtype ResponseType: Response func parse(response: Data) -> (ResponseType?, Error?) }

Page 26: Protocol-Oriented Networking

Define Endpointsstruct GitHubUserEndpoint: Resource { private let name: String init(loginName: String) { name = loginName } var path: String { return "users/\(name)" } var method: HTTPMethod { return .GET } var parameters: [String : Any]? { return nil } func parse(response: Data) -> ( GitHubUser?, Error?) {

// Parse JSON -> data model object } }struct GitHubUser: Response { // Define data model

}

Page 27: Protocol-Oriented Networking

Testing Endpointsclass GitHubUserEndpointSpecs: QuickSpec { override func spec() {

var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") }

} }

Page 28: Protocol-Oriented Networking

Testing Endpointsclass GitHubUserEndpointSpecs: QuickSpec { override func spec() {

var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") } it("build path") { expect(sut.path) == "users/mosamer" }

} }

Page 29: Protocol-Oriented Networking

Testing Endpointsclass GitHubUserEndpointSpecs: QuickSpec { override func spec() {

var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") } it("build path") { expect(sut.path) == "users/mosamer" } it("GET method") { expect(sut.method) == HTTPMethod.GET }

} }

Page 30: Protocol-Oriented Networking

Testing Endpointsclass GitHubUserEndpointSpecs: QuickSpec { override func spec() {

var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") } it("build path") { expect(sut.path) == "users/mosamer" } it("GET method") { expect(sut.method) == HTTPMethod.GET } it("without parameters") { expect(sut.parameters).to(beNil()) }

} }

Page 31: Protocol-Oriented Networking

Testing Endpointsclass GitHubUserEndpointSpecs: QuickSpec { override func spec() {

var sut: GitHubUserEndpoint! beforeEach { sut = GitHubUserEndpoint(loginName: "mosamer") } it("build path") { expect(sut.path) == "users/mosamer" } it("GET method") { expect(sut.method) == HTTPMethod.GET } it("without parameters") { expect(sut.parameters).to(beNil()) } it("parse response") { let userJSON = Data() /* Data from JSON file */ let (user, _) = sut.parse(response: userJSON) /* Check user properties are fetched correctly */ } } }

Page 32: Protocol-Oriented Networking

Client One More Time

- (void)fetchGitHubUser:(NSString *)name completionHandler:(void (^)(id, NSError *))handler {

[self.network requestEndpointWithPath:[NSString stringWithFormat:@“users/%@”,name] method:HTTPMethodGET parameters:nil handler:^(id result, NSError *error) { handler(result, error); }]; }

Page 33: Protocol-Oriented Networking

Client One More Time

- (void)fetchGitHubUser:(NSString *)name completionHandler:(void (^)(id, NSError *))handler {

[self.network requestEndpointWithPath:[NSString stringWithFormat:@“users/%@”,name] method:HTTPMethodGET parameters:nil handler:^(id result, NSError *error) { handler(result, error); }]; }

func user(loginName: String, completionhandler: (GitHubUser?, Error?) -> ()) { let endpoint = GitHubUserEndpoint(loginName: loginName) request(endpoint, handler: completionhandler) }

Page 34: Protocol-Oriented Networking

Client - General Handlers

private func request<R: Resource>(_ endpoint: R, handler:(R.ResponseType?, Error?) -> ()) {

} } }

extension GHAPIClient { func user(loginName: String, completionhandler: (GitHubUser?, Error?) -> ()) { let endpoint = GitHubUserEndpoint(loginName: loginName) request(endpoint, handler: completionhandler) }

Page 35: Protocol-Oriented Networking

Client - General Handlers

private func request<R: Resource>(_ endpoint: R, handler:(R.ResponseType?, Error?) -> ()) {

network.requestEndpoint(path: endpoint.path, method: endpoint.method, parameters: endpoint.parameters) { (data, error) in if let _ = error { handler(nil, error) return } guard let data = data else { handler(nil, nil) return } let (result, error) = endpoint.parse(response: data) handler(result, error) } } }

extension GHAPIClient { func user(loginName: String, completionhandler: (GitHubUser?, Error?) -> ()) { let endpoint = GitHubUserEndpoint(loginName: loginName) request(endpoint, handler: completionhandler) }

Page 36: Protocol-Oriented Networking

Summary

Page 37: Protocol-Oriented Networking

Summary• Client has too many responsibilities

Page 38: Protocol-Oriented Networking

Summary• Client has too many responsibilities

Page 39: Protocol-Oriented Networking

Summary• Client has too many responsibilities• Client is tightly coupled to the network library

Page 40: Protocol-Oriented Networking

Summary• Client has too many responsibilities• Client is tightly coupled to the network library

Page 41: Protocol-Oriented Networking

Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test

Page 42: Protocol-Oriented Networking

Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test

Page 43: Protocol-Oriented Networking

Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden

Page 44: Protocol-Oriented Networking

Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden

Page 45: Protocol-Oriented Networking

Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden• Client still has too many responsibilities

Page 46: Protocol-Oriented Networking

Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden• Client still has too many responsibilities

Page 47: Protocol-Oriented Networking

Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden• Client still has too many responsibilities• Client lacks general handlers

Page 48: Protocol-Oriented Networking

Summary• Client has too many responsibilities• Client is tightly coupled to the network library• Client is hard to test• Implementation details is not hidden• Client still has too many responsibilities• Client lacks general handlers

Page 49: Protocol-Oriented Networking

@mostafa_amer

Questions?

Page 50: Protocol-Oriented Networking

Thank You@mostafa_amer

Questions?