Protocol-Oriented Networking

Preview:

Citation preview

Protocol-Oriented Networking@mostafa_amer

Starting Point#import <AFNetworking/AFNetworking.h>

@interface GHAPIClient : AFHTTPSessionManager

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

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

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

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

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

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

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

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

} }

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

Hide Implementation

@interface GHAPIClient : AFHTTPSessionManager

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

#import <AFNetworking/AFNetworking.h>

Hide Implementation

@interface GHAPIClient : NSObject

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

@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

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

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

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

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

} }

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

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

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

More Protocols

More Protocols

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

}

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

associatedtype ResponseType: Response

}

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

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

}

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

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

} }

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

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

} }

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 }

} }

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

} }

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 */ } } }

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

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

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

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

Summary

Summary• Client has too many responsibilities

Summary• Client has too many responsibilities

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

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

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

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

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

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

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

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

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

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

@mostafa_amer

Questions?

Thank You@mostafa_amer

Questions?

Recommended