Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The iOS Developer’s Introduction to Vapor (3)

The iOS Developer’s Introduction to Vapor (3)

Server-side Swift has been around for a couple of years and as time goes by the maturity of putting Swift on the server grows. With this talk, I will give an introduction to one of the most popular server-side Swift frameworks called Vapor, which recently released a new major version. I will talk a bit about how I ended up doing server-side Swift development after working with iOS for years and I’ll look at the potential synergy between Vapor and iOS. This talk requires no prior knowledge to server-side Swift.

Steffen D. Sommer

October 04, 2018
Tweet

More Decks by Steffen D. Sommer

Other Decks in Programming

Transcript

  1. Introduction to Vapor
    The Developer’s
    3

    View Slide

  2. " Head of Vapor Development at Nodes
    Full time Vapor developer since January 2017
    Background in iOS development
    ~50 customer projects and ~40 packages
    @steffendsommer

    View Slide

  3. View Slide

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

    View Slide

  5. View Slide

  6. A web framework built in Swift
    Official support for SQLite, MySQL, PostgreSQL and Redis
    Built on top of SwiftNIO

    View Slide

  7. View Slide


  8. Frontend Backend
    Static websites
    Dynamic websites
    API

    View Slide

  9. Backend
    API
    Android application
    iOS application
    Single page application
    Illustration borrowed from Jonas Schwartz

    View Slide

  10. 1.0
    Sep, 2016
    2.0
    May, 2017
    3.0
    April, 2018
    0.1
    Jan, 2016
    Timeline borrowed from Tanner Nelson

    View Slide

  11. ⚙ Configs
    Codable
    Async

    View Slide

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

    View Slide

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

    View Slide


  14. Models
    ⚠ Errors
    Frameworks used in both places
    Endpoints & Environments
    Business logic
    Styling
    Test code (e.g. mocks)
    ✅ Validation
    ☂ A framework that wraps the endpoints

    View Slide

  15. Modern and Swifty API’s
    Great community
    Growing eco system
    Most popular

    View Slide


  16. Everything is “new”
    Tooling
    ⚙ Foundation
    Eco system
    Resources

    View Slide


  17. View Slide

  18. Number of users
    1



    View Slide

  19. Performance
    ?

    < 2 sec

    View Slide

  20. Performance
    Free

    Pricy

    View Slide

  21. Platforms
    iOS
    tvOS
    macOS
    watchOS

    macOS
    Linux

    View Slide

  22. Bug fixing
    Next release cycle

    Fix it now

    View Slide

  23. User Interfaces
    Storyboards
    Auto Layout
    Xib

    HTML
    CSS
    JavaScript

    Leaf

    View Slide

  24. The Xcode Project File
    Merge madness

    What file?

    View Slide

  25. State
    Used when needed

    It’s all about the request

    View Slide

  26. Persistence
    Used

    Very central

    View Slide

  27. Architecture
    MVC
    MVVM
    MVP
    VIPER

    MVC

    View Slide

  28. ?

    View Slide

  29. https://www.serversideswift.work

    View Slide

  30. Create a new post
    Show all published posts, sorted by a publish date
    Show details of a single post

    View Slide

  31. brew install vapor/tap/vapor
    1

    View Slide

  32. vapor new ServerSideSwiftWork
    2

    View Slide

  33. vapor xcode
    3

    View Slide

  34. View Slide

  35. http://localhost:8080/hello

    View Slide

  36. SQLite
    Fluent
    Leaf

    View Slide

  37. View Slide

  38. // swift-tools-version:4.2
    import PackageDescription
    let package = Package(
    name: "ServerSideSwiftWork",
    dependencies: [
    // A server-side Swift web framework.
    .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0"),
    // Swift ORM (queries, models, relations, etc) built on SQLite 3.
    .package(url: "https://github.com/vapor/fluent-sqlite.git", from: "3.0.0"),
    // Template engine
    .package(url: "https://github.com/vapor/leaf.git", from: "3.0.1"),
    ],
    targets: [
    .target(name: "App", dependencies: ["Leaf", "FluentSQLite", "Vapor"]),
    .target(name: "Run", dependencies: ["App"]),
    .testTarget(name: "AppTests", dependencies: ["App"])
    ]
    )

    View Slide

  39. View Slide

  40. final class Work: Codable {
    public var id: Int?
    public var company: String
    public var location: String
    public var title: String
    public var description: String
    public var externalUrl: String
    public var publishedAt: Date?
    public init(
    company: String,
    location: String,
    title: String,
    description: String,
    externalUrl: String,
    publishedAt: Date? = nil
    ) {
    self.company = company
    self.location = location
    self.title = title
    self.description = description
    self.externalUrl = externalUrl
    self.publishedAt = publishedAt
    }
    }

    View Slide

  41. extension Work: SQLiteModel {}
    extension Work: Migration {}
    extension Work: Content {}
    extension Work: Parameter {}

    View Slide

  42. View Slide

  43. func create(_ req: Request) throws -> Future {
    return try req
    .content
    .decode(Work.self)
    .save(on: req)
    }

    View Slide

  44. func index(_ req: Request) throws -> Future<[Work]> {
    return Work
    .query(on: req)
    .filter(\.publishedAt != nil)
    .sort(\.publishedAt, .descending)
    .all()
    }

    View Slide

  45. struct ViewData: Codable {
    let work: [Work]
    }
    func renderIndex(_ req: Request) throws -> Future {
    return Work
    .query(on: req)
    .filter(\.publishedAt != nil)
    .sort(\.publishedAt, .descending)
    .all()
    .flatMap { items in
    try req.view().render("index", ViewData(work: items))
    }
    }

    View Slide

  46. func show(_ req: Request) throws -> Future {
    return try req
    .parameters
    .next(Work.self)
    }

    View Slide

  47. View Slide

  48. Work

    #for(item in work) {

    #(item.title) at #(item.company)

    #(item.description)

    }

    View Slide

  49. View Slide

  50. public func routes(_ router: Router) throws {
    let workController = WorkController()
    router.get("api/work", use: workController.index)
    router.get("api/work", Work.parameter, use: workController.show)
    router.post("api/work", use: workController.create)
    router.get("work", use: workController.renderIndex)
    }

    View Slide


  51. View Slide

  52. public func configure(_ config: inout Config, _ env: inout Environment, _ services: inout Services) throws {
    /// Register providers first
    try services.register(FluentSQLiteProvider())
    try services.register(LeafProvider())
    config.prefer(LeafRenderer.self, for: ViewRenderer.self)
    /// Register routes to the router
    let router = EngineRouter.default()
    try routes(router)
    services.register(router, as: Router.self)
    /// Register middleware
    var middlewares = MiddlewareConfig()
    middlewares.use(ErrorMiddleware.self)
    services.register(middlewares)
    // Configure a SQLite database
    let sqlite = try SQLiteDatabase(storage: .memory)
    /// Register the configured SQLite database to the database config.
    var databases = DatabasesConfig()
    databases.add(database: sqlite, as: .sqlite)
    services.register(databases)
    /// Configure migrations
    var migrations = MigrationConfig()
    migrations.add(model: Work.self, database: .sqlite)
    services.register(migrations)
    }

    View Slide

  53. View Slide

  54. try services.register(FluentSQLiteProvider())
    try services.register(LeafProvider())
    config.prefer(LeafRenderer.self, for: ViewRenderer.self)

    View Slide

  55. let router = EngineRouter.default()
    try routes(router)
    services.register(router, as: Router.self)

    View Slide

  56. let sqlite = try SQLiteDatabase(storage: .memory)
    var databases = DatabasesConfig()
    databases.add(database: sqlite, as: .sqlite)
    services.register(databases)

    View Slide

  57. var migrations = MigrationConfig()
    migrations.add(model: Work.self, database: .sqlite)
    services.register(migrations)

    View Slide

  58. View Slide

  59. View Slide

  60. View Slide

  61. View Slide

  62. GET http://localhost:8080/work

    View Slide


  63. Yes, async is hard at first
    RxSwift/ReactiveSwift can be a plus
    Async/await would be great

    View Slide


  64. View Slide

  65. View Slide

  66. vapor cloud deploy

    View Slide

  67. https://vapor.cloud

    View Slide

  68. Multiple regions and cloud providers
    Custom docker templates
    ♻ Internal DNS setup
    Build pipeline
    ✏ Releases and rollback
    Public API
    Database IP
    Built-in Git server
    Commands in separate replicas
    ⚙ Loadbalancer configuration
    More statistics
    All features in the dashboard
    Config updates without redeployment
    And much more…
    Version 2

    View Slide

  69. Public alpha
    Now-ish
    Week 40-41

    View Slide

  70. https://docs.vapor.codes
    https://discord.gg/vapor
    Tim Condon - Getting Started with Server Side Swift and
    Vapor (Codemobile 2018)
    ? https://www.serversideswift.work
    https://www.serversideswift.info

    View Slide

  71. View Slide

  72. View Slide


  73. @steffendsommer

    View Slide