Новые горизонты с серверным Swift
Прочтите этот пост в лучшем формате на моем сайте.
Решив присоединиться к другим конференциям в этом году, самой первой стала dotSwift. Он проходил в большом старинном театре Парижа. Конференция длилась полдня, но все было лучше, чем я предполагал. Были хорошие разговоры о бэкэнд-разработке на Swift. Много услышав об этом и отличной производительности Swift, я решил попробовать. И вот, шаг за шагом, мои первые впечатления.
В Swift есть два основных фреймворка для бэкэнда: Kitura и Vapor. Я сначала выбираю Китуру. Потому что именование методов Китуры было близко к тем, которые я знал по Node.js. Мне было удобнее понимать, что делает каждый метод.
Во-первых, настроить среду было легко, потому что у меня Mac и я активно занимаюсь разработкой приложений для iOS. Итак, Xcode и другие вещи уже были настроены.
Создание серверного проекта Swift означает создание нового пакета Swift с помощью однострочной команды swift package init
. Это создает новый структурированный пакет. Но пакеты не исполняемые, и мне нужен был исполняемый проект для запуска моего бэкэнда. Создание main.swift
файла в каталоге Sources дало мне такую возможность. На данный момент моя текущая структура папок была такой:
SwiftBackend .gitignore Package.swift |--Sources main.swift |--Tests
Пришло время расположить Package.swift
и добавить Китуру. Вот файл Package.swift
с Китурой, добавленной в качестве зависимости:
import PackageDescription
let package = Package(
name: "SwiftBackend",
dependencies: [
.Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 4)
]
)
swift build
команда устанавливает зависимости и строит проект. После добавления новой зависимости всегда логично построить проект и посмотреть, работает ли он.
Следующий шаг - настройка конечной точки. Базовый пример в руководстве Китуры:
import Kitura
let router = Router() router.get("/") { request, response, next in response.send("Hello, World!") next() } Kitura.addHTTPServer(onPort: 8090, with: router) Kitura.run()
Я добавил этот код в main.swift
файл. Когда я запускал проект локально, я должен был увидеть Hello, World! когда я ввожу localhost:8090
через браузер (или делаю запрос на получение через Postman и т. д.). Чтобы запустить этот проект, мне сначала нужно было собрать его через swift build
, и эта команда создала для меня исполняемый файл под .build/debug/SwiftBackend
. Я только что запустил исполняемый файл, набрав в командной строке .build/debug/SwiftBackend
. На этом этапе я смог отправить запрос и увидеть текст «Hello, World!» в браузере. Но в консоли я не видел никаких логов по этим запросам.
HeliumLogger
вошел в этот момент. Это компонент регистратора, доступный как отдельный модуль Swift. Я добавил этот модуль в файл Package.swift
, добавив в качестве новой зависимости. На тот момент мой Package.swift
файл был таким:
import PackageDescription
let package = Package( name: "SwiftBackend", dependencies: [ .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 4), .Package(url: "https://github.com/IBM-Swift/HeliumLogger.git", majorVersion: 1, minor: 4) ] )
И я снова выполнил команду swift build
, чтобы установить HeliumLogger
. После этого мне нужно было импортировать HeliumLogger
и использовать его в main.swift
файле. Это была всего одна строчка. Мой main.swift
файл стал таким:
import Kitura import HeliumLogger
HeliumLogger.use()
let router = Router() router.get("/") { request, response, next in response.send("Hello, World!") next() } Kitura.addHTTPServer(onPort: 8090, with: router) Kitura.run()
После создания и повторного запуска проекта я смог увидеть журналы запросов в консоли.
В качестве следующего шага я хотел подключить базу данных к моему внутреннему API. Я использовал CouchDB, потому что есть пакет Kitura-CouchDB.
После добавления пакета CouchDB в мой Package.swift
файл он стал таким:
import PackageDescription
let package = Package( name: "SwiftBackend", dependencies: [ .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 4), .Package(url: "https://github.com/IBM-Swift/HeliumLogger.git", majorVersion: 1, minor: 4), .Package(url: "https://github.com/IBM-Swift/Kitura-CouchDB.git", majorVersion: 1, minor: 4) ] )
Я снова выполнил команду swift build
, чтобы установить новый пакет.
При реализации операций с базой данных я хотел создать структуру, не позволяющую делать все в main.swift
файле. Сначала я создал структуру User
как объект модели. Моей основной целью операций с базой данных было добавление этого User
объекта в базу данных CouchDB в качестве документа. Простая модель, простота в эксплуатации. Итак, структура User
была такой:
// User.swift import Foundation
public struct User { let name: String let identifier: String }
Итак, как я уже сказал, моей основной целью было добавить документ в базу данных CouchDB. Я создал DatabaseInteraction
структуру с одним методом для достижения этой цели. Я планировал добавить в эту структуру все операции с базой данных. Вот структура DatabaseInteraction
:
// DatabaseInteraction.swift import Foundation import CouchDB import SwiftyJSON
public struct DatabaseInteraction { var db: Database public init(db: Database) { self.db = db }
func addNewUser(_ user: User, handler: @escaping (String?, String?, JSON?, NSError?) -> ()) { let userDict: [String: Any] = [ "name": user.name, "identifier": user.identifier ] let userJSON = JSON(userDict) db.create(userJSON) { (id, revision, doc, error) in if let error = error { handler(nil, nil, nil, error) return } else { handler(id, revision, doc, nil) } } } }
Я также отделил методы HTTP-запроса от маршрутизаторов. Во-первых, я создал UserRouter
для обработки HTTP-запросов пользователей и написал только один метод публикации для получения пользовательских данных в теле. КлассUserRouter
выглядит следующим образом:
// UserRouter.Swift import Foundation import Kitura import CouchDB import SwiftyJSON
public class UserRouter { var db: DatabaseInteraction
public init(db: DatabaseInteraction) { self.db = db } public func bindAll(to router: Router) { addCreateUser(to: router) }
private func addCreateUser(to router: Router) { router.post("/user/", handler: { req, res, next in guard let parsedBody = req.body else { res.status(.badRequest) next() return }
switch(parsedBody) { case .json(let jsonBody): let name = jsonBody["name"].string ?? "" let user = User(name: name, identifier: "\(name.characters.count)") self.db.addNewUser(user) { (id, revision, doc, error) in if let error = error { res.status(.internalServerError) next() } else { res.status(.OK) if let doc = doc { res.send(json: doc) } else { res.send("Something is wrong in the doc") } next() } } default: res.status(.badRequest) next() } }) } }
После этого я создал основной маршрутизатор для управления отдельными операциями маршрутизации отсюда. Вот BackendRouter
как главный объект маршрутизатора:
// BackendRouter.swift import Foundation import Kitura
public class BackendRouter {
public let router = Router() var db: DatabaseInteraction
public init(db: DatabaseInteraction) { self.db = db router.get("/status") { req, res, callNextHandler in res.status(.OK).send("Everything is working") callNextHandler() } router.all("*", middleware: BodyParser())
self.routeToUser() } func routeToUser() { let user = UserRouter(db: self.db) user.bindAll(to: self.router) } }
Напоследок я их все соединил в файл main.swift
.
// main.swift import Kitura import HeliumLogger import CouchDB HeliumLogger.use() let connProperties = ConnectionProperties( host: "127.0.0.1", // httpd address port: 5984, // httpd port secured: false, // https or http username: "admin", // admin username password: "password"// admin password ) let db = Database(connProperties: connProperties, dbName: "swift_backend_test_db") let databaseInteraction = DatabaseInteraction(db: db) let app = MainRouter(db: databaseInteraction) Kitura.addHTTPServer(onPort: 8090, with: app.router) Kitura.run()
Итак, давайте посмотрим, как сейчас выглядит общая структура папок.
SwiftBackend
.gitignore
|--Sources
BackendRouter.swift
DatabaseInteraction.swift
main.swift
User.swift
UserRouter.swift
|--Tests
Package.swift
После написания кода пришло время создать API и отправить запрос через Postman. Я добился успеха после некоторых испытаний. (Примечание: будьте осторожны со свойствами соединения и избеганием замыканий). Конечно, в коде есть что улучшить. Но все эти коды были предназначены для создания рабочего бэкэнд API, разработанного на Swift.
В следующем посте я сделал этот проект тестируемым и Dockerized. Есть некоторые моменты, на которые следует обратить внимание при тестировании.
В качестве последнего шага я попытаюсь загрузить его в облако. Но мое первое впечатление было действительно хорошим. Должен сказать, что меня убедили поработать на бэкэнд в Swift. Это очень просто. Я работаю с SublimeText вместо Xcode. Таким образом, я могу понять каждую написанную мной строчку без автозаполнения. Если вы работаете со Swift, вам обязательно стоит попробовать создать некоторые серверные API с помощью Swift.
Если он вам понравился, нажмите чуть ниже, чтобы порекомендовать его своим друзьям. Если вы хотите быть в курсе новых сообщений в блоге, вы можете перейти на мой личный веб-сайт и подписаться на мою рассылку. Вы можете подписаться на меня в Твиттере и GitHub.