本記事の概要
個人的にswiftという言語の仕様が気に入っていて、またGoのあるORMの仕様が気に入らないためvaporやkubernetesを使ってサーバーを組みました。その際の知見や躓いたポイントを記載します。
後半、大急ぎで書きました。ご意見・ご感想に対応して修正する予定なのでコメントお待ちしています。
そもそもしたいこと
vaporでサーバーを作ってそれを使ったスマホアプリをリリースしたい。
また、vaporの可能性やswiftのポテンシャルを広めたいと思ってます。
vaporで作るサーバーでできること・できないことをまとめる
できること
- リクエストを受け取る
- レスポンスを返す
- 認証処理を入れる
- FluentというvaporとセットのORMを使ってDBを使える(めっちゃ有能)
- コンテナ(Docker)の準備が万端
できないこと
- Firebase Admin SDKなどの便利な外部ツールを直接、使えない
できないことへのアプローチは?
別言語でSDKやAPIを使うクローズドなサーバーを作れば良くね?というおもいでKubernetesのPODを作成してみた。
やり方と一部内容
別言語のサーバーとはgRPCでやりとりする!
Firebase Authを使用するためのPODを呼び出し
import Vapor
import Protobufs
// goで作ったFirebase Admin SDKで認証処理をするサーバーを呼び出す。
protocol AuthServerRepository {
func createUser(req: Request) async throws -> AuthServerCreatedUserModel
func verifyIdToken(req: Request, jwtoken: String) async throws -> String
}
final class AuthServerRepositoryImpl {
let authServiceClient: AuthService_AuthServiceAsyncClient
init(authServiceClient: AuthService_AuthServiceAsyncClient) {
self.authServiceClient = authServiceClient
}
}
extension AuthServerRepositoryImpl: AuthServerRepository {
func createUser(req: Request) async throws -> AuthServerCreatedUserModel {
// ~~~~
}
// こっちは実際の実装あり
func verifyIdToken(req: Request, jwtoken: String) async throws -> String {
let request = AuthService_VerifyIdTokenRequest.with { request in
request.token = jwtoken
}
let idToken = try await self.authServiceClient.verifyIdToken(request)
return idToken.uid
}
}
Firebase Authを使用するためのPODを呼び出し
こっちはstreamを使用している
import Vapor
import Protobufs
fileprivate let notifyMaxCount = 500
protocol NotificationServerRepository {
func notify(req: Request, notificationsList: [NotificationNotifyModels]) async throws
}
final class NotificationServerRepositoryImpl {
let notificationServiceClient: NotificationService_NotificationServiceAsyncClientProtocol
init(notificationServiceClient: NotificationService_NotificationServiceAsyncClientProtocol) {
self.notificationServiceClient = notificationServiceClient
}
}
extension NotificationServerRepositoryImpl: NotificationServerRepository {
func notify(req: Request, notificationsList: [NotificationNotifyModels]) async throws {
req.logger.debug("NotificationServerRepo.notify(): makeNotifyCall()")
let notifier = notificationServiceClient.makeNotifyCall(callOptions: .none)
for notifications in notificationsList {
// 通知用に500件ごとに分割する。
var fcmTokensList: [[String]] = []
var lastIndex = 0;
for i in 0 ..< (notifications.fcmTokens.count / notifyMaxCount) {
lastIndex = i + notifyMaxCount
req.logger.info("\(notifications.title): \(i)..<\(lastIndex)")
fcmTokensList.append(notifications.fcmTokens[i..<lastIndex].map { $0 })
}
fcmTokensList.append(notifications.fcmTokens[lastIndex..<notifications.fcmTokens.endIndex].map { $0 })
// 通知サーバーに飛ばす。
for fcmTokens in fcmTokensList {
let requestBody = NotificationService_NotificationRequest.with { requestBody in
requestBody.fcmTokens = fcmTokens
requestBody.title = notifications.title
requestBody.message = notifications.message
}
try await notifier.requestStream.send(requestBody)
}
}
req.logger.debug("NotificationServerRepo.notify(): finish()")
notifier.requestStream.finish()
}
}
感想・謝罪
gRPCのstreamの処理などは資料が少なかったため実装方法に悩みました。ただ、streamを使用した箇所はちゃんと動いた所を見たことがないため実際の動きを見てからの報告になります。すみません。確認出来次第、ここを修正します。
ただ、全体を通してswiftのサーバーの可能性や言語としてのポテンシャルの高さは発見できたかなと思っています。なので、みなさん、一回swiftでサーバーを作るのはいかがでしょうか?