Swift에서 가장 많이 사용하는 Server Side Frameworks
이다.
Vapor Toolbox
의 경우는 Homebrew
를 통해 설치할 수 있다
brew install vapor
Vapor Toolbox의 명령어를 확인하기 위해서는 vapor --help
입력
vapor new task-management
를 통해서 vapor 프로젝를 생성
이후 세 가지 옵션을 정해야 된다.
Fluent를 사용할지, 데이터베이스의 종류, Leaf를 사용할지 안할지를 입력한다
Leaf는 HTML 템플릿 언어
Package.swift
Swift Package Manager가 프로젝트에서 가장 먼저 찾는 파일로, 패키지의 의존성과 타깃 등을 정의합니다. 이 파일은 항상 프로젝트의 루트 디렉터리에 위치하고, Package.swift라는 이름이어야 합니다.
Sources
프로젝트의 모든 Swift 소스 파일을 포함한다
App
앱을 구성하는데 필요한 코드를 포함합니다
Controllers
로직을 그룹화하는 컨트롤러가 위치한다
Migrations
데이터베이스 마이그레이션을 정의하는 타입이 위치한다
Models
데이터베이스의 데이터 구조를 나타내는 모델이 위치한다
Configure.swift
앱을 구성하는 configure(_:)
함수를 포함합니다. main.swift
에 의해 호출되며 이 함수에 라우트, 테이터베이스 등의 서비스를 등록해야 한다.
route.swift
앱에 라우트를 등록하는 route(_:)
함수를 포함하며, configure(_:)
함수에 의해 호출됩니다.
Run
앱을 실행하는데 필요한 코드만 포함합니다.
main.swift
앱의 인스턴스를 생성하고 실행합니다.
Tests
XCTVapor
모듈을 사용하는 단위 테스트를 포함합니다.
SwiftNIO
Vapor는 Applie에서 만든 SwiftNIO 프레임워크를 기반으로 설계되었습니다. SwiftNIO는 비동기 네트워킹 프레임워크로, Vapor의 모든 HTTP 통신을 처리합니다. Vapor가 요청을 받고 응답을 보낼 수 있게 하며, 연결 및 데이터 전송을 관리합니다.
Vapor의 일부 API는 EventLoopFuture
제네릭 타입을 반환합니다. EventLoopFuture
는 나중에 제공될 결과에 대한 플레이스홀더입니다. 비동기적으로 동작하는 함수는 실제 데이터를 즉시 반환하지 않고, 대신 EventLoopFuture
를 반환합니다.
Fluent
Fluent
는 Swift용 ORM(Object-relational mapping)
프레임워크입니다. Vapor 앱과 데이터베이스 사이의 추상화 계층으로, 데이터베이스를 쉽게 사용할 수 있도록 인터페이스를 제공합니다. 따라서 SQL을 작성하지 않고도 Swift로 데이터베이스 작업을 수행할 수 있습니다.
Database 설치를 위해서 이번에 사용할 Postgres database를 설치해준다
brew install postgresql
을 통해서 postgreSql을 다운 받아준다
pg_ctl -D /usr/local/var/postgres start
명령어 실행
brew services start postgresql
//brew 환경에서 postgresql을 시작시켜 줌
createdb 데이터베이스이름
을 통해서 Database 를 설치
.package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-rc"),
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0")
// package.swift 파일에 의존성 추가
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver")
// package.swift 파일에 target 추가
database를 등록하는 과정을 해준다
configure.swift
파일에서
app.databases.use(.postgres(hostname: "localhost", username: "leeyoungwoo", password: "", database: "test1"), as: .psql)
//데이터베이스 등록
테이블 생성을 해주기 위해서는 해당 테이블에 대한 attribute에 대한 정의, Table에 대한 정의가 필요하다
모델은 schema
라는 타입 프로퍼티를 갖습니다. 이 프로퍼티의 문자열은 데이터베이스 테이블 또는 컬렉션의 이름을 의미합니다.
final class TestModel: Model, Content {
static let schema: String = "testSchema"
// 테이블의 이름을 schema 값에 할당
@ID(key: .id)
var id: UUID?
//속성의 고유 아이디가 된다. 이렇게 하면 자동생성이 되고 탐색할 때 이 id로 탐색하게 된다
//UUID를 보면 이 고유 아이디가 되는 것을 알 수 있고 optional 타입이기 때문에 존재 하지 않을 수도 있다
@Field(key: "name")
var name: String
//@Field : 모델은 데이터를 저장하기 위한 @Field 프로퍼티를 가질 수 있다. key 값으로는 데이터베이스 키를 사용하며, 프로퍼티 이름과 같지 않아도 된다.
@Field(key: "job")
var job: String
init() { }
//모델은 반드시 빈 이니셜라이저를 가져야만 한다. Fluent가 쿼리에 의해 모델을 생성하는데 내부적으로 사용하기 때문이다. 물론 모든 프로퍼티를 포함하는 이니셜라이저도 정의할 수 있다
init(id: UUID? = nil, name: String, job: String) {
self.id = id
self.name = name
self.job = job
}
//초기화 과정이 필수이다
}
객체지향형 데이터베이스 구조이기때문에 Model이 Class로 되어있어야 함을 주의해야된다!!
JSON 타입이기 때문에 Content
프로토콜을 통해서 Codable
하게 해준다
Fluent는 데이터베이스 키에 스네이크 케이스, 프로퍼티 이름에는 카멜케이스를 사용하는 것을 권장한다
바로 위처럼 테이블을 정의해주었다고 바로 테이블이 생성되는 것은 아니다. Migration(이주) 를 통해서 데이터들을 실제 데이터베이스로 이주시켜줘야 된다. 어떻게 이주시킬 것인지에 대해서 정의를 해주자!!!
import Fluent
import FluentPostgresDriver
struct CreateTestModel: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("testSchema")
.id()
.field("name", .string, required)
.field("job", .string)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema("testSchema").delete()
}
}
prepare(on:)
메서드는 데이터베이스를 변화시키는 동작을 수행합니다. 테이블, 필드, 제약 등을 추가하고 삭제하는 것과 같이 데이터베이스 스키마를 변경합니다. 또한 모델 인스턴스 생성, 필드 값 업데이트와 같이 데이터를 수정합니다.
revert(on:)
메서드는 이러한 변화를 되돌리는 동작을 수행합니다. 마이그레이션 실행 취소는 테스트를 용이하게 하고, 백업을 제공합니다.
이제 configure.swift
파일로 와서 지금 작성한 Migration 타입을 app에 추가해준다
app.migrations.add(CreateTestModel())
//테이블 추가
그리고 터미널에서
vapor run migrate
를 해주게 되면 이제 실제 Table이 생성된 것을 확인할 수 있다
이제 데이터베이스가 구축이 되었으니 데이터 생성, 수정, 삭제 등의 동작에 대한 정의만 해주면 된다
routes.swift
파일로 이동한다
app.post("test") { req -> EventLoopFuture<TestModel> in
let exist = try req.content.decode(TestModel.self)
return exist.create(on: req.db).map { (result) -> TestModel in
return exist
}
}
app.get("testAll") { req -> EventLoopFuture<[TestModel]> in
return TestModel.query(on: req.db).all()
}
컨트롤러는 요청에 대한 응답을 반환하는 메서드를 그룹화하기 좋은 수단입니다. 프로젝트 규모가 커질수록 코드의 책임을 명확히 분리하는 것이 유지보수에 용이하다
Vapor는 라우트를 그룹화할 수 있도록 RouteCollection
프로토콜을 제공합니다. 컨트롤러가 RouteCollection
프로토콜을 채택하면 라우트를 등록하기 위한 boot(routes: )
메서드를 구현해야 합니다.
GET /tasks
POST /tasks
DELETE /tasks/:id
우리가 사용하는 엔드포인트는 /tasks
라는 같은 경로로 시작합니다. grouped(_:)
메서드를 통해 미리 지정된 경로를 사용하는 라우터를 생성할 수 있습니다.
routes.grouped("tasks")
DELETE
요청의 경우 경로가 /:id
로 끝나는데, 이는 동적 파라미터이다. :
접두사로 시작하며, URL에 입력한 값을 가져와 사용할 수 있습니다
import Fluent
import Vapor
struct TaskController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let tasks = routes.grouped("tasks")
tasks.get(use: showAll)
tasks.post(use: create)
tasks.group(":id") { task in
tasks.delete(use: delete)
}
}
func showAll(req: Request) throws -> EventLoopFuture<[Task]> {
return Task.query(on: req.db).all()
}
func create(req: Request) throws -> EventLoopFuture {
let task = try req.content.decode(Task.self)
return task.create(on: req.db).map { task }
}
func delete(req: Request) throws -> EventLoopFuture {
return Task.find(req.parameters.get("id"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { $0.delete(on: req.db) }
.transform(to: .ok)
}
}
Vapor는 데이터를 디코딩하기 전에 요청을 검증할 수 있도록 Validation API를 제공합니다. 이를테면, 이메일, 정수 범위, URL 등의 요효성을 검사할 수 있습니다.
코드를 구현하기 위해서는 앞서 잘못된 요청 데이터에 따른 오류 메시지를 살펴보겠습니다. POST/ tasks 엔드포인트의 경우로 가정하며, 아래는 요청 데이터입니다.
{
"title" : "Vapor 공부하기",
"status" : "pending"
}
Task 타입으로 디코딩하면 다음과 같은 오류 메시지를 반환합니다.
Value of type Status required for key status
Status
타입의 값으로는 toDo, doing, done 만이 유효하므로 위 메시지는 틀리지 않았습니다. 다만, 메시지 자체로는 사용 가능한 Status 타입의 값을 유추하기가 어렵습니다. Validation API를 사용하면 다음과 같은 오류 메시지를 확인할 수 있습니다.
Vapor를 통해서 앱을 구현해봤자, 이는 로컬에서만 사용이 가능하다. 그렇기 때문에 배포를 통해 URL로 접근이 가능하도록 만들어야 한다. 또한 데이터베이스를 로컬에 직접 설치하지 않고 사용해보자!
Heroku는 클라우드 서비스형 플랫폼(Platform as a Service, PaaS)으로, 여러 언어와 데이터베이스를 지원하며 서버, 하드웨어, 인프라 등을 자동으로 관리한다.
Heroku에서 대시보드를 통해서 앱을 생성, Create new app
을 선택
로컬에서 Homebrew를 사용할 수 있도록, Heroku CLI를 설치!!
brew install heroku/brew/heroku
Heroku는 Git을 사용하여 앱을 배포한다. 대시보드에서 생성한 Heroku 앱 이름을 입력하고 연결
heroku git:remote -a my-app-name
앱이 원격 데이터베이스에 접근할 수 있도록 설정해주자!!
DATABASE URL의 경우는 Heroku에 의해서 계속해서 변경될 수 있으므로, 따로 설정이 필요하다
if let databaseURL = Environment.get("DATABASE_URL"), var postgresConfig = PostgresConfiguration(url: databaseURL) {
var clientTLSConfiguration = TLSConfiguration.makeClientConfiguration()
clientTLSConfiguration.certificateVerification = .none
postgresConfig.tlsConfiguration = clientTLSConfiguration
app.databases.use(.postgres(configuration: postgresConfig), as: .psql)
}
코드를 configure.swift
폴더에 넣어주자 기존에 databaseURL
은 로컬 db니깐 삭제해준다
Heroku는 기본적으로 master
브랜치를 배포한다. master
이외의 브랜치를 배포할 경우 branch-name:master
와 같이 입력한다.
git push heroku master
데이터베이스 마이그레이션을 수행합니다
heroku run Run --migrate --env production