Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Xcode for server-side development #9

Open
Taehyeon-Kim opened this issue Jun 14, 2022 · 1 comment
Open

Use Xcode for server-side development #9

Taehyeon-Kim opened this issue Jun 14, 2022 · 1 comment
Assignees
Labels
Swift WWDC Topic: swift wwdc22 wwdc2022

Comments

@Taehyeon-Kim
Copy link

Taehyeon-Kim commented Jun 14, 2022

Discover how you can create, build, and deploy a Swift server app alongside your pre-existing Xcode projects within the same workspace. We'll show you how to create your own local app and test endpoints using Xcode, and explore how you can structure and share code between server and client apps to ease your development process

@Taehyeon-Kim Taehyeon-Kim added Swift WWDC Topic: swift wwdc22 wwdc2022 labels Jun 14, 2022
@Taehyeon-Kim Taehyeon-Kim self-assigned this Jun 14, 2022
@Taehyeon-Kim
Copy link
Author

Taehyeon-Kim commented Jun 14, 2022

서론

우리의 많은 응용 프로그램은 일반적으로 iPhone과 같은 단일 기기에 초점을 맞춥니다. 사용량이 늘어남에 따라 Mac, Watch, 기타 Apple 플랫폼 및 장치와 같은 추가 장치로 가져오고 싶어합니다. Xcode는 이러한 플랫폼을 위한 애플리케이션을 구성하고 구축하는데 도움이 됩니다. 플랫폼별 애플리케이션 코드에서 각 장치의 고유한 측면을 수용하면서 패키지를 사용하여 코드를 공유할 수 있습니다. 시스템이 성장하고 발전함에 따라서 응용 프로그램은 종종 서버 구성 요소로 클라이언트 응용 프로그램을 보완해야 합니다.

예를 들어, 백그라운드에서 수행할 수 있는 작업을 오프로드하거나 계산량이 많은 작업을 오프로드하거나 장치에서 사용할 수 없는 데이터에 액세스해야 하는 작업이 있습니다. 서버 구성 요소는 클라이언트 상대와 다른 도구와 방법론을 사용하여 구축해야 하므로 비용과 노력이 듭니다. 서버를 구축하기 위해 Swift를 사용하면 이러한 기술 격차를 해소하는 데 도움이 됩니다.

Swift를 사용하여 서버 애플리케이션을 빌드하는 방법을 살펴봅시다.

Swift 패키지로 서버 애플리케이션 구축

서버 애플리케이션은 Swift 패키지로 모델링됩니다. 패키지는 애플리케이션 진입점을 매핑하는 실행가능한 대상을 정의합니다.
세션의 예시에서는 Vapor Web Framework를 사용합니다.

// Package.swift
import PackageDescription

let package = Package(
    name: "MyServer",
    platforms: [.macOS("12.0")],
    products: [
        .executable(
            name: "MyServer",
            targets: ["MyServer"]),
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.0.0")),
    ],
    targets: [
        .executableTarget(
            name: "MyServer",
            dependencies: [
                .product(name: "Vapor", package: "vapor")
            ]),
        .testTarget(
            name: "MyServerTests",
            dependencies: ["MyServer"]),
    ]
)
  • executable target을 설정합니다.
// Main : 진입점
import Vapor

@main
public struct MyServer {
    public static func main() async throws {
        let webapp = Application()
        webapp.get("greet", use: Self.greet)        // HTTP Endpoint
        webapp.post("echo", use: Self.echo)
        try webapp.run()
    }
    
    static func greet(request: Request) async throws -> String {
        return "Hello from Swift Server"
    }
    
    static func echo(request: Request) async throws -> String {
        if let body = request.body.string {
            return body
        }
        return ""
    }
}
  • 다른 Swift 기반 실행 파일과 같이 프로그램의 진입점은 @main을 annotation을 사용하여 잘 모델링됩니다.
  • Application은 Vapor 프레임워크에서 제공합니다.
// 실행 후에 확인해봅시다.
curl http://127.0.0.1:8080/greet; echo // Get 요청
curl http://127.0.0.1:8080/echo --data "Hello, world"; echo // Post 요청
  • 실행은 Scheme을 My Mac으로 설정하고 수행하도록 합니다.

iOS 앱에서 서버 호출하기

SwiftUI 프로젝트를 만들어보고 구축해놓은 서버와 통신해보겠습니다.

// MyServerClient.swift
import Foundation

struct MyServerClient {
    let baseURL = URL(string: "http://127.0.0.1:8080")!
    
    func greet() async throws -> String {
        let url = baseURL.appendingPathComponent("greet")
        let (data, _) = try await URLSession.shared.data(for: URLRequest(url: url))
        guard let responseBody = String(data: data, encoding: .utf8) else {
            throw Errors.invaildResponseEncoding
        }
        return responseBody
    }
    
    enum Errors: Error {
        case invaildResponseEncoding
    }
}
// ContentView.swift
import SwiftUI

struct ContentView: View {
    @State var serverGreeting = ""
    var body: some View {
        Text(serverGreeting)
            .padding()
            .task {
                do {
                    let myServerClient = MyServerClient()
                    self.serverGreeting = try await myServerClient.greet()
                } catch {
                    self.serverGreeting = String(describing: error)
                }
            }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

클라우드에 배포하기

AWS, Google Cloud, Azure, Heroku 및 기타 여러 클라우드 제공업체가 있지만 예시에서는 Heroku를 사용합니다.
소규모 애플리케이션을 위한 배포 시스템에 편리합니다.
git push를 이용해 손쉽게 배포할 수 있습니다.

Heroku 를 이용해서 서버를 배포해보세요

Food Truck App

Xcode버전과 iOS버전이 맞지 않아 실행이 안 되는 것 같습니다.

  • Swift is a general purpose language
  • Xcode is a powerful development environment
  • Choice of cloud providers

iOS 또는 macOS 애플리케이션을 빌드하기 위해 이미 Swift를 사용하고 있다면 시스템의 서버 측 개발에도 사용할 수 있습니다. Xcode는 하나의 작업 공간에서 클라이언트와 서버 모두 시스템의 다양한 구성 요소를 개발하고 디버그하는데 도움이 됩니다. Swift 기반 서버 애플리케이션을 배포하기 위한 클라우드 공급자를 선택할 수도 있습니다.

스크린샷 2022-06-14 오후 7 48 38

스크린샷 2022-06-14 오후 7 49 19

우리가 푸드 트럭을 운용하기 위해 앱을 사용한다고 가정해본채로 이해해보도록 할게요.

예시 앱을 들여다보면 데이터가 하드코딩된 채로 관리되는 것을 볼 수 있습니다. 그렇다면 유저는 실제로 다른 데이터를 볼 수도 있겠네요. 소규모 푸드 트럭 운용에는 괜찮을 수 있겠지만 여러 개의 푸드 트럭에서 하나의 서비스를 이용해서 운용을 한다고 생각을 해보면 비효율적인 방식이 될 수 있을 것 같습니다.

우리는 지금 메뉴가 중앙 집중화되고 모든 고객 서비스를 관리하는 도넛 제국을 만들고 싶은 것입니다. 중앙 집중 시스템을 어떻게 만들 수 있을지 생각해보죠.

스크린샷 2022-06-14 오후 7 54 46

메모리 내 저장소가 있는 앱에서 시작해볼까요?

스크린샷 2022-06-14 오후 7 56 10

메뉴를 중앙 집중화하기 위해서 iOS 앱에서 스토리지를 추출하여 서버로 이동할 수 있습니다. 이렇게 하면 앱의 모든 사용자가 동일한 저장소를 공유할 수 있기 때문에 동일한 도넛 메뉴를 사용할 수 있습니다.

스크린샷 2022-06-14 오후 7 57 52

첫번째 예제와 마찬가지로 HTTP 기반 API에 노출되어 있습니다. iOS 앱은 이러한 API 작업을 위해 추상화를 사용한 다음 Presentation Layer(이 예에서는 SwiftUI)에 연결합니다.

코드

@main
struct FoodTruckServerBootstrap {
    public static func main() async throws {
        let foodTruckServer = FoodTruckServer()
        let webapp = Application()
        webapp.get("donuts", use: foodTruckServer.listDonuts) // Set Endpoints
        try webapp.run()
    }
}

struct FoodTruckServer {
    func listDonuts(request: Request) -> Response.Donuts{
        return Reponse.Donuts(donuts: [])
    }

    enum Response {
        struct Donuts: Content {
            var donuts: [Model.Donut]
        }
    }
}
  • Contents 프로토콜은 웹 프레임워크(Vapor)에 의해 정의되며 JSON으로 응답을 인코딩하도록 도와줍니다.
enum Model {
    struct Donut: Encodable {
        public var id: UUID
        public var name: String
        public var date: String
        public var dough: Dough
        public var glaze: Glaze?
        public var topping: Topping?
    }
    
    struct Dough: Encodable {
        
    }
    
    struct Glaze: Encodable {
        
    }
    
    struct Topping: Encodable {
        
    }
}
  • 서버와 클라이언트의 데이터 모델이 대략적으로 일치해야 하므로 Client앱에서도 동일한 데이터 모델을 사용하고 있습니다.
  • 각 구조체는 Encodable 프로토콜을 준수하고 있습니다. JSON으로 인코딩을 할 수 있도록 도와주기 위함입니다.
struct FoodTruckServer {
    private let storage = Storage()
    
    func listDonuts(request: Request) -> Response.Donuts {
        let donuts = self.storage.listDonuts()
        return Response.Donuts(donuts: [])
    }
    
    enum Response {
        struct Donuts: Content {
            var donuts: [Model.Donut]
        }
    }
}

struct Storage {
    let donuts = [Model.Donut]()
    
    func listDonuts() -> [Model.Donut] {
        return self.donuts
    }
}
  • 데이터 모델과 기본 API가 있으면 스토리지 추상화를 포함하도록 로직을 확장할 수 있습니다.
  • 스토리지는 사용 가능한 도넛 목록을 애플리케이션에 제공합니다.

스토리지(Storage)

서버 측 애플리케이션을 설계할 때 항상 흥미로운 주제입니다.
사용 사례(Use case)에 따라 선택할 수 있는 몇 가지 전략이 있습니다.

  1. Files for static data
  2. iCloud for user-centric data
  3. Database for transactional data
  • 데이터가 정적이거나 매우 천천히 수동으로 변경되는 경우 디스크의 파일이 충분한 솔루션을 제공할 수 있습니다.
  • 사용자 중심 데이터 또는 전역 데이터 세트의 경우 iCloud는 전용 서버를 배포하지 않고도 iOS 애플리케이션에서 직접 사용할 수 있는 API 세트를 제공합니다.
  • 동적 또는 트랜잭션 데이터를 처리할 때 데이터베이스는 탁월한 솔루션을 제공합니다. 서버측 응용 프로그램에 사용할 수 있는 다양한 데이터베이스 기술이 있습니다. (Postgres, MySQL, MongoDB, Redis, DynamoDB 등)

데모에서는 정적 파일 저장 전략(JSON 이용)만 다룹니다.

정적 파일 저장 전략

스크린샷 2022-06-14 오후 8 19 55

스크린샷 2022-06-14 오후 8 20 10

스크린샷 2022-06-14 오후 8 20 48

Storage에 변경 가능한 donuts 변수가 있고 load 및 listDonuts 메서드가 동시에 액세스할 수 있기 때문에 Actor를 사용합니다. Swift 5.5에 처음 도입된 Actor는 데이터 경쟁을 피하고 공유된 자원을 안전하지만 쉬운 방법으로 처리하도록 도와줍니다. Actor가 도입되기 전에는 Lock 또는 큐(대기열)을 이용해서 처리를 했습니다.

스크린샷 2022-06-14 오후 8 24 34

서버 추상화(abstraction)에 bootstrap메서드를 추가하고 거기에서 스토리지를 로드합니다. 그리고 bootstrap을 실행 가능한 진입점에 연결합니다. 스토리지는 이제 액터이므로 비동기 컨텍스트에서 접근하도록 합니다. 서버 API를 캡슐화하는 데 도움이 됩니다.

스크린샷 2022-06-14 오후 8 26 23

기존 방식과 마찬가지로 URLSession을 이용해서 HTTP 요청을 만들고 JSONDecoder를 사용하여 서버 응답을 디코딩하고 JSON에서 iOS 애플리케이션 모델로 변환합니다.

상위 수준에서 사용하기

스크린샷 2022-06-14 오후 8 31 05

iOS와 서버 애플리케이션에서 모델을 공유함으로써 반복을 피하고 직렬화를 더 안전하게 만들 수 있습니다. 다음은 데이터 모델 코드를 Shared 프레임워크로 옮겨서 사용하고 있습니다.

@Taehyeon-Kim Taehyeon-Kim changed the title Meet Swift Package plugins Use Xcode for server-side development Jun 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Swift WWDC Topic: swift wwdc22 wwdc2022
Projects
None yet
Development

No branches or pull requests

1 participant