Slide 1

Slide 1 text

Same language, different platform Searching for synergy between iOS and Vapor SwiftConf 2018

Slide 2

Slide 2 text

!

Slide 3

Slide 3 text

• " Head of Vapor Development at Nodes • # Background in iOS development • $ Full time Vapor developer since January 2017 • % ~50 customer projects and ~40 packages • & On the hunt for synergy.. @steffendsommer

Slide 4

Slide 4 text

'

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

“Platform support for all Apple platforms as well as Linux”

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

• ( A web framework built in Swift • ) Official support for SQLite, MySQL, PostgreSQL and Redis • * Built on top of SwiftNIO

Slide 10

Slide 10 text

import Vapor let app = try Application() let router = try app.make(Router.self) router.get("hello") { req in return "Hello, world." } try app.run()

Slide 11

Slide 11 text

• + Strong, safe and modern language • , Fast and has a low memory footprint (aka. -) • ♻ Potential synergy between iOS and Vapor • / Tech awesomeness

Slide 12

Slide 12 text

• * Modern and Swifty API’s • 0 Great community • 1 Growing eco system • 2 Most popular

Slide 13

Slide 13 text

3 • 4 Everything is “new” • ( Tooling • ⚙ Foundation • 1 Eco system • 6 Resources

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

–Wikipedia “Synergy is the creation of a whole that is greater than the simple sum of its parts.”

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

☁ # JSON Models Models Errors Errors Validation Validation Endpoints Endpoints

Slide 18

Slide 18 text

☁ # JSON Models Models Errors Errors Validation Validation Endpoints Endpoints

Slide 19

Slide 19 text

☁ # ♻ Models Errors Validation Endpoints JSON

Slide 20

Slide 20 text

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

No content

Slide 23

Slide 23 text

" 9

Slide 24

Slide 24 text

" 9 ♻

Slide 25

Slide 25 text

:

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

;

Slide 28

Slide 28 text

# “Can I get an endpoint which return a list of subcategories?” “Yes.. it’s ready now” “Wait a sec, that endpoint gives me product categories?” “Oh.. so you want categories to be returned?”

Slide 29

Slide 29 text

<

Slide 30

Slide 30 text

• ⏱ To save time (write once, use twice) • "9 To share work-load • : To keep code in sync across platforms • ; To share knowledge about implementation • < To hide the data interchange format

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

=

Slide 33

Slide 33 text

final class Work: Codable { enum Kind: String, Codable { // .. } enum Framework: String, Codable { // .. } var id: Int? var company: String var companyLogoUrl: String var location: String var kind: Kind var framework: Framework var remoteAllowed: Bool var title: String var description: String var externalUrl: String var contactEmail: String var approvedAt: Date? var createdAt: Date? var updatedAt: Date? var deletedAt: Date? init( company: String, companyLogoUrl: String, location: String, kind: Kind, framework: Framework, remoteAllowed: Bool, title: String, description: String, externalUrl: String, contactEmail: String ) { // .. } }

Slide 34

Slide 34 text

# struct Work: Codable { enum Kind: String, Codable { // .. } enum Framework: String, Codable { // .. } var id: Int? var company: String var companyLogoUrl: String var location: String var kind: Kind var framework: Framework var remoteAllowed: Bool var title: String var description: String var externalUrl: String var contactEmail: String var approvedAt: Date? var createdAt: Date? var updatedAt: Date? var deletedAt: Date? }

Slide 35

Slide 35 text

♻ public final class Work: Codable { public enum Kind: String, Codable { // .. } public enum Framework: String, Codable { // .. } public var id: Int? public var company: String public var companyLogoUrl: String public var location: String public var kind: Kind public var framework: Framework public var remoteAllowed: Bool public var title: String public var description: String public var externalUrl: String public var contactEmail: String public var approvedAt: Date? public var createdAt: Date? public var updatedAt: Date? public var deletedAt: Date? public init( company: String, companyLogoUrl: String, location: String, kind: Kind, framework: Framework, remoteAllowed: Bool, title: String, description: String, externalUrl: String, contactEmail: String ) { // .. } }

Slide 36

Slide 36 text

Slide 37

Slide 37 text

enum WorkError: Error { case noWorkAvailable } extension WorkError: AbortError { var identifier: String { return "noWork" } var status: HTTPResponseStatus { return .custom(code: 499, reasonPhrase: "No work available") } var reason: String { return "Unfortunately, no work available at this moment." } }

Slide 38

Slide 38 text

# enum WorkError: Error { case noWork } extension WorkError { init?(from statusCode: Int) { switch statusCode { case 499: self = .noWork default: return nil } } }

Slide 39

Slide 39 text

♻ enum APIError: Error { case noWorkAvailable var httpCode: UInt { switch self { case .noWorkAvailable: return 499 } } init?(from httpCode: UInt) { switch httpCode { case 499: self = .noWorkAvailable default: return nil } } }

Slide 40

Slide 40 text

?

Slide 41

Slide 41 text

public func routes(_ router: Router) throws { let api = router.grouped("api") let workAPIController = APIWorkController() api.get("/work", use: workAPIController.workList) api.get("/work", Work.parameter, use: workAPIController.work) } enum EnvironmentKey { enum Project { static let url = "PROJECT_URL" } }

Slide 42

Slide 42 text

# struct WorkAPIService { enum Environment: String { case production = "http://serversideswift.work" } let environment: Environment func work(complete: @escaping ([Work]) -> ()) { sendRequest(to: "\(environment.rawValue)/api/work/") { (work: [Work]) in complete(work) } } func work(for id: Int, complete: @escaping (Work) -> ()) { sendRequest(to: "\(environment.rawValue)/api/work/\(id)") { (work: Work) in complete(work) } } }

Slide 43

Slide 43 text

♻ public enum API { public enum Environment: String { case production = "http://serversideswift.work" func url(to endpoint: Endpoint) -> String { return self.rawValue + endpoint.path } } public enum Endpoint { case workList case work(Int) var path: String { switch self { case .workList: return "/api/work" case .work(let id): return "/api/work/\(id)" } } } }

Slide 44

Slide 44 text

Slide 45

Slide 45 text

final class APIWorkController { func workList(_ req: Request) throws -> Future<[Work]> { return Work .query(on: req) .filter(\.approvedAt != nil) .sort(\.approvedAt, .descending) .all() .thenThrowing { items in guard items.count > 0 else { throw WorkError.noWorkAvailable } return items } } func work(_ req: Request) throws -> Future { return try req.parameters.next(Work.self) } }

Slide 46

Slide 46 text

# struct WorkAPIService { let environment: API.Environment func work(complete: @escaping ([Work]) -> ()) { sendRequest(to: environment.url(to: .workList)) { (work: [Work]) in complete(work) } } func work(for id: Int, complete: @escaping (Work) -> ()) { sendRequest(to: environment.url(to: .work(id))) { (work: Work) in complete(work) } } func sendRequest(to url: String, complete: @escaping (C) -> ()) { guard let url = URL(string: url) else { return } let task = URLSession.shared.dataTask(with: url) { (data, response, error) in guard let dataResponse = data, let urlResponse = response as? HTTPURLResponse, error == nil else { return } do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 let results = try decoder.decode(C.self, from: dataResponse) complete(results) } catch let parsingError { print("Error", parsingError) } } task.resume() } }

Slide 47

Slide 47 text

♻ public protocol HTTPClient { func sendRequest(to url: String, complete: @escaping (C) -> ()) } public struct APIClient { internal let client: HTTPClient internal let environment: API.Environment public init(client: HTTPClient, environment: API.Environment) { self.client = client self.environment = environment } public func work(complete: @escaping ([Work]) -> ()) { client.sendRequest(to: environment.url(to: .workList)) { (work: [Work]) in complete(work) } } public func work(for id: Int, complete: @escaping (Work) -> ()) { client.sendRequest(to: environment.url(to: .work(id))) { (work: Work) in complete(work) } } }

Slide 48

Slide 48 text

A

Slide 49

Slide 49 text

• = Models • ⚠ Errors • ✅ Validation • ? Endpoints & Environments • C Business logic • D Styling • E Test code (e.g. mocks) • F Frameworks used in both places • ☂ A framework that wraps the endpoints

Slide 50

Slide 50 text

♻ ☁ # my-app-ios my-app-vapor my-app-shared

Slide 51

Slide 51 text

No content

Slide 52

Slide 52 text

G • # Copy files from shared repo into your project • $ Copy files from shared repo into your project

Slide 53

Slide 53 text

H • # Add submodule and drag files into project • $ Add submodule and re-generate Xcode project file

Slide 54

Slide 54 text

< • # Use SPM to pull down dependences and use this trick* to generate an iOS project. Alternatively use SwiftXcode. • $ Add the shared repo as a dependency * https://www.ralfebert.de/ios-examples/xcode/ios-dependency-management-with-swift-package-manager/

Slide 55

Slide 55 text

I • # Check in Xcode project on shared repo and add the dependency to your Cartfile • $ N/A

Slide 56

Slide 56 text

• G Copy and paste • H Git submodules • < Swift Package Manager • I Carthage • ☕ CocoaPods

Slide 57

Slide 57 text

• K A change most likely requires platform-specific updates • L Both platforms would still have to sync and deploy/release • ☁ Code might depend on something that doesn’t run on the other platform • M Potential overhead in abstracting code that can be shared • N Not great until SPM gets integrated in Xcode 3

Slide 58

Slide 58 text

O

Slide 59

Slide 59 text

• < Swift Package Manager integrated in Xcode • P Fluent on iOS • $ Vapor on iOS • Q Examples/resources/tools/frameworks • R Swift on Android

Slide 60

Slide 60 text

• $ https://docs.vapor.codes • S https://discord.gg/vapor • T https://www.serversideswift.info • U Tim Condon - Getting Started with Server Side Swift and Vapor (Codemobile 2018) • 9 https://www.serversideswift.work

Slide 61

Slide 61 text

V Any questions? @steffendsommer