스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
서버측 개발을 위한 Xcode 사용
동일한 작업 공간 내에 기존의 Xcode 프로젝트와 함께 Swift 서버 앱을 만들고 빌드 및 배포하는 방법을 확인하세요. Xcode를 사용하여 나만의 로컬 앱을 만들고 엔드포인트를 테스트하는 방법을 보여드리며, 서버와 클라이언트 앱 간 코드를 구조화 및 공유하여 개발 프로세스를 용이하게 하는 방법을 살펴보겠습니다.
리소스
관련 비디오
WWDC23
WWDC22
WWDC21
WWDC20
WWDC19
-
다운로드
♪ ♪
안녕하세요 제 이름은 Tom입니다 Apple의 Swift 팀 소속이죠 iOS 애플리케이션을 클라우드로 확장할 때 필요한 걸 말씀드릴게요 대부분의 응용 프로그램은 단일 기기에 초점을 둡니다 일반적으로 iPhone이죠 사용량이 증가함에 따라 추가 기기로 확장하게 됩니다 Mac, Watch 또는 기타 Apple 플랫폼 및 기기와 같이요 이런 플랫폼의 응용 프로그램을 구성하는 데 Xcode가 도움이 되죠 패키지를 사용하여 코드를 공유할 수 있습니다 플랫폼별 응용 프로그램 코드에서 각 기기의 고유한 측면을 수용하면서요 시스템이 계속 성장하고 발전함에 따라 응용 프로그램은 서버 컴포넌트로 클라이언트 응용 프로그램을 보완해야 할 수 있습니다 이러한 서버 컴포넌트를 사용하면 클라이언트 응용 프로그램 기능을 클라우드로 확장할 수 있습니다 예를 들면 백그라운드에서 수행할 수 있는 오프로드 작업 및 계산량이 많은 오프로드 작업 또는 기기에서 사용할 수 없어서 데이터 액세스가 필요한 작업이죠 종종 서버 컴포넌트는 그들의 클라이언트 측과 다른 도구와 방법론을 사용하여 구축해야 하는 경우가 있습니다 이는 중복되는 작업과 통합 문제를 유발하게 되죠 Swift를 사용하여 서버 컴포넌트를 구축하면 이러한 기술 격차를 해소하는 데 도움이 되며 스택 전반에 친숙한 환경을 제공할 수 있습니다 그럼 Swift에서 서버 애플리케이션 구축 방법을 살펴보겠습니다 서버 응용프로그램은 Swift 패키지로 모델링됩니다 이 패키지는 응용 프로그램의 엔트리 포인트에 매핑되는 실행 가능한 대상을 정의합니다 응용 프로그램을 웹 애플리케이션으로 만들기 위해 웹 프레임워크에 종속성을 추가합니다 코드 구성과 라우팅 같은 기본 유틸리티 제공에 도움이 되죠 이 예시에서는 Vapor 웹 프레임워크를 사용합니다 웹 서비스 구축에 널리 사용되는 오픈 소스 커뮤니티 프로젝트죠
다른 Swift 기반 실행 파일과 마찬가지로 엔트리 포인트의 모델링은 @main 어노테이션이 가장 좋습니다 웹 프레임워크 통합을 위해서 main 함수에 관련 부트스트랩 코드를 추가합니다 여기 이 애플리케이션 타입은 Vapor 웹 프레임워크에서 제공됐죠 기본 부트스트랩이 준비되었으면 이 애플리케이션이 유용한 작업을 수행하도록 만들 수 있습니다 서버에 요청 보내는 사용자를 맞는 인사말 코드를 추가해 보겠습니다 웹 프레임워크를 정의하여 HTTP 엔드포인트를 정의하고 인사말을 제공하는 메서드를 가리키도록 해줍니다 한 걸음 더 나아가 두 번째 HTTP 엔드포인트를 추가하겠습니다 이 HTTP는 포스트 요청을 처리하고 요청 본문의 내용을 호출자에 에코합니다 그럼 실제로 작동하는 것을 봅시다 지금 Xcode로 된 서버 애플리케이션이 있습니다 우리는 이제 막 시작했기 때문에 시스템에서 로컬로 서버를 실행해 테스트해 볼 수 있습니다 로컬에서 실행하려면 Xcode에서 생성한 MyServer Scheme을 선택하고 My Mac을 목적지로 사용합니다 그리고 실행을 클릭합니다
응용 프로그램이 시작되면 Xcode 콘솔로 서버에서 내보내는 로그 메시지를 살펴볼 수 있습니다 지금과 같은 경우 서버가 시작된 것을 볼 수 있고 로컬호스트 (127.0.0.1:8080)에서 수신 대기 중임을 확인할 수 있죠 이 정보를 사용하여 서버를 테스트할 수 있습니다 터미널로 이동해서 광고된 서버 주소로 요청을 보내겠습니다 curl이라는 유틸리티를 사용하여 요청을 보냅니다 첫 번째 엔드포인트를 사용하고
두 번째 것도 사용합니다 그리고 에코에 데이터도 전달합니다
좋습니다 터미널을 사용하는 것은 꽤 흥미로웠습니다 하지만 정말로 알고 싶은 것은 iOS 앱에서 서버 호출 방법인데요 그 내용을 파헤쳐 보겠습니다 다음은 서버와의 상호작용을 추상화하는 데 사용할 수 있는 Swift 데이터 구조의 예입니다 추상화 작업에서 서버 API는 비동기 메서드로 모델링합니다 네트워킹은 본질적으로 비동기이기 때문이죠 URLSession을 사용하여 비동기식 요청을 생성하고 그다음 서버 응답을 파싱하고 최종적으로 호출자에게 반환합니다 지금의 경우 서버 응답이 일반 문자열인데요 현실에서는 더 정교할 가능성이 높습니다 예를 들면 응답이 JSON으로 인코딩되어 있을 수 있고 이 경우 Swift의 Codable 시스템을 사용하여 디코딩할 수 있습니다 이 모든 것을 Xcode에 통합해 보죠 Xcode의 workspace를 사용하여 iOS와 서버 애플리케이션을 나란히 놓고 구축하고 테스트하겠습니다 iOS 응용 프로그램 서버 추상화는 이미 준비되어 있습니다 우리가 합친 코드를 사용해 인사말 서버를 가져오도록 기본 SwiftUI의 ContentView를 변경해 보겠습니다 먼저 serverGreeting이라는 상태 변수를 생성합니다
그다음 serverGreeting을 Text()에 바인딩합니다
마지막으로 .task{}를 추가하여 서버 API를 호출하고 상태를 설정합니다
코드가 준비되면 시뮬레이터에서 애플리케이션을 실행할 수 있죠 MyApp Scheme을 선택하고 시뮬레이터를 선택 후 실행을 클릭합니다
이런! 오류가 발생했습니다 일종의 연결 오류인 것 같네요 주소는 맞는 것 같은데요 로컬 서버 시작을 잊은 것 같습니다 Xcode로 돌아가서 서버의 Scheme을 선택해준 뒤 서버를 실행해 보겠습니다
애플리케이션을 다시 시작합니다 행운을 빌어봅니다
드디어 작동하네요! 데모의 완성을 위해 애플리케이션을 클라우드에 배포해 보겠습니다 선택할 만한 클라우드 제공 업체가 많이 있는데요, AWS를 포함하여 Google Cloud, Azure Heroku 등 많습니다 이 예시에서는 Heroku를 사용하겠습니다 Heroku에는 편리한 git push 배포 시스템이 있습니다 이 데모 애플리케이션과 같은 소규모 프로젝트를 위한 것이죠 배포를 시작하러 터미널로 이동하겠습니다 계정을 설정하고 Heroku 서비스로 애플리케이션을 구성한 뒤 Heroku Remote로 코드를 git push 할 수 있습니다
.
그리고 이동합니다 Heroku는 buildpacks 기술을 써서 원격 애플리케이션 컴파일을 하고 바이너리 아티팩트를 임시 호스트에 배포합니다 Swift를 위한 Heroku buildpack은 Swift 오픈 소스 커뮤니티의 구성원에 의해 구축되었으며 Swift 일반 사용자들 모두가 사용할 수 있습니다 애플리케이션이 배포되었으면 curl을 사용하여 로컬 서버에서 했던 것처럼 테스트할 수 있습니다 첫 번째 엔드포인트를 테스트해 보겠습니다
이 주소를 복사할게요
두 번째 것도 해주겠습니다
이번에는 다른 페이로드를 보내겠습니다
좋네요, 애플리케이션이 성공적으로 배포되었습니다 계속하기 전에 잠깐 멈춰서 이 부분의 주요 요점을 짚고 넘어가겠습니다 이미 Swift를 사용해 iOS나 macOS 애플리케이션을 구축하고 있다면 시스템 서버 측 개발에도 Swift를 사용할 수 있습니다 Xcode는 시스템의 다양한 컴포넌트 개발 및 디버그에 도움이 됩니다 클라이언트와 서버 양측을 하나의 작업 공간에서 말이죠 마지막으로 클라우드 제공 업체를 선택할 수 있습니다 Swift 기반 서버 애플리케이션 배포를 위해서요 이런 클라우드 플랫폼에 배포하는 방법에 대한 추가 정보는 swift.org의 Swift Server 문서에서 찾을 수 있습니다 이제 기본 설정을 살펴보았으니 보다 실질적인 예시를 보겠습니다 푸드 트럭입니다 이 애플리케이션이 쓰이는 걸 아마 많은 세션에서 보셨을 겁니다 내부를 들여다보고 데이터가 어떻게 관리되는지 살펴보겠습니다 도넛 목록이 하드 코딩 된 것 같은데요 그렇다면 애플리케이션 사용자가 실제로 제공될 수 있는 것과 다른 도넛 메뉴를 볼 수 있음을 의미합니다 어떤 종류의 도넛이든 즉석에서 만들 수 있는 소규모 푸드 트럭 운영에는 유용할 수 있지만 우리는 도넛 제국을 만들고 싶습니다 메뉴가 중앙 집중화 되고 고객 서비스가 전부인 푸드 트럭요 그럼 중앙 집중화 된 푸드 트럭의 시스템 디자인 설계를 보겠습니다
메모리 내 저장소가 있는 iOS 앱에서 시작합니다 메뉴를 중앙 집중화 하기 위해 iOS 앱으로부터 저장소를 추출해서 서버로 이동시킬 수 있습니다 이렇게 하면 앱의 모든 사용자가 동일한 저장소를 공유하게 되고 따라서 동일한 도넛 메뉴를 공유할 수 있습니다 강연의 첫 부분 예시와 유사하게 서버가 HTTP 기반 API를 노출합니다 iOS 앱은 이런 API로 작업하려 추상화를 사용할 것이고 이것들을 프레젠테이션 계층으로 다시 묶습니다 이 예시의 경우 SwiftUI가 됩니다 그럼 디자인은 완성되었습니다 이제 Swift 코드를 작성할 차례입니다 개발자 리소스 키트의 푸드 트럭 샘플 앱을 다운로드하여 따라 해볼 수 있습니다 애플리케이션 뼈대로 서버 구축을 시작합니다 그 다음 'donuts' 웹 API에 대한 HTTP 엔드포인트를 정의합니다 그리고 서버 추상화 'listDonuts' 메서드를 가리키도록 합니다 API가 Donuts 타입의 응답을 반환한다는 것을 눈치채셨겠죠 그리고 Response.Donuts는 Content라는 프로토콜을 따릅니다 Content 프로토콜은 웹 프레임워크에 의해 정의되며 응답을 JOSN 유선으로 인코딩하는 데 도움을 줍니다 그리고 API에 아직 정의되지 않은 베일 속의 배열 이 포함되어 있는 걸 눈치채셨을 수 있습니다 이것이 우리 영광의 데이터 모델입니다 Donut, Dough, Glaze Topping이 있습니다 여기서 한 가지 흥미로운 점은 이 모델의 정의를 푸드 트럭 iOS 앱에서 복사했다는 점입니다 서버와 클라이언트 데이터 모델이 대략 맞춰져야 하기 때문이죠 또 다른 흥미로운 점은 Encodable 프로토콜을 준수한다는 점입니다 이것은 서버가 모델 객체를 JSON 유선으로 인코딩할 수 있도록 하기 위해 필요합니다 데이터 모델과 기본 API가 준비되었으면 로직이 Storage 추상화를 포함하도록 확장할 수 있습니다 Storage는 가능한 도넛 목록을 애플리케이션에 제공합니다 이 시점에서는 완전히 작동하는 서버가 있어야 합니다 그런데 말이죠 도넛 메뉴가 비어 있습니다 중앙 집중화 된 도넛 메뉴는 어디에서 가져와야 할까요 Storage는 서버 측 애플리케이션 설계 시 항상 흥미로운 주제입니다 용례에 따라 선택할 수 있는 몇 가지 전략이 있습니다 애플리케이션 데이터가 정적이거나 변화가 느리고 수동 변경인 경우 디스크의 파일이 충분한 솔루션이 될 수 있습니다 사용자 중심 데이터 또는 전역 데이터셋의 경우 iOS 애플리케이션에 직접 사용하는 API 세트를 iCloud가 제공합니다 전용 서버를 배포 않고도 말이죠 동적 데이터 또는 트랜잭션 데이터를 처리할 때는 데이터베이스가 탁월한 솔루션이 됩니다 서버 측 응용 프로그램에 사용할 수 있는 다양한 데이터베이스 기술들이 있습니다 각 기술은 특정 성능 데이터 일관성 및 데이터 모델링 요구 사항에 맞게 설계되어 있습니다 수년간 Swift 오픈 소스 커뮤니티에서는 대부분의 데이터베이스 기술과 자생적으로 상호 작용 하도록 돕는 데이터베이스 드라이버를 개발했습니다 일부 목록으로 Postgres, MySQL MongoDB, Redis, DynamoDB 및 기타 항목이 있습니다 데모를 단순화하기 위해서 정적 파일 저장 전략만 시연하도록 하겠습니다 그렇지만 swift.org의 Swift Server 문서에서 데이터베이스 사용에 대해 더 자세히 알아볼 수 있습니다 정적 파일 저장 전략을 사용하므로 먼저 도넛 메뉴를 확보하는 JSON 파일을 생성합니다 이 파일을 생성한 다음 SwiftPM의 리소스 지원을 사용하여 애플리케이션에 액세스할 수 있도록 만듭니다 준비되었다면 Storage 추상화를 더욱 정교하게 만들어줄 때입니다 다시 말해 'load' 메서드를 추가해 줄 때죠 load 메서드는 생성된 SwiftPM의 리소스 접근자를 사용하여 리소스 파일 경로를 찾고 FileManager API를 사용하여 파일 내용을 메모리에 로드합니다 마지막으로 JSONDecoder를 사용해 JSON 콘텐츠를 서버 애플리케이션 데이터 모델로 디코딩합니다 흥미로운 점은 이제 Storage가 actor로 정의된다는 것입니다 이것은 Storage가 가변 변수 'donuts'을 갖기 때문입니다 'load'와 'listDonuts' 메서드가 동시에 액세스할 수도 있습니다 Swift 5.5에서 처음 도입된 Actor는 데이터 경쟁을 피하고 공유되는 가변 상태 처리에 도움이 됩니다 안전하고 쉬운 방법으로 말이죠 Actor의 도입 이전에는 잠금이나 대기열 같은 API를 사용해서 가변 상태 액세스 시 동기화 블록을 기억하고 추가해야 했습니다 Storage 업데이트 이후에는 모든 걸 하나로 묶을 수 있습니다 'bootstrap' 메서드를 서버 추상화에 추가하고 거기서 storage.load()를 합니다 그다음 이 bootstrap을 가능한 엔트리 포인트에 연결합니다 Storage가 이제 Actor이니 비동기 컨텍스트에서 액세스합니다 이제 서버는 준비됐습니다 클라이언트 측으로 넘어가죠 먼저 서버 추상화를 추가하는 것으로 시작합니다 서버 API를 캡슐화하는데 도움이 됩니다 URLSession을 사용하여 HTTP 요청을 생성하고 JSONDecoder를 사용하여 서버 응답을 디코딩하고 JSON에서 iOS 애플리케이션 모델로 변환합니다 이 시점에서 하드 코딩 되었던 메뉴를 제거하고 서버로부터의 비동기 요청으로 대체할 수 있습니다 마지막으로 ContentView 로드 작업에서 서버를 호출합니다 테스트해 볼 시간입니다 이번엔 서버 시작을 잊지 맙시다 여기 'FoodTruckServer' Scheme을 선택해 주고 실행을 클릭합니다
애플리케이션이 실행되는 동안 터미널로 이동해서 API 액세스가 되는지 확인하겠습니다
주소를 다시 복사하고
이번엔 jq라는 유틸리티를 사용해 JSON 출력을 좀 더 보기 좋게 해주겠습니다 괜찮아 보이네요
좋습니다, 그럼 이제 앱으로 테스트할 차례입니다
Xcode로 이동하겠습니다 Food Truck Scheme을 선택하고 시뮬레이터를 선택한 후 실행합니다
그럼 이제 여기에 나옵니다 중앙 집중화 메뉴에 있는 세 가지 도넛입니다 이것을 서버에서 보이는 것과 상호 참조 해볼 수 있습니다 다시 터미널로 돌아갑시다 비교를 쉽게 하기 위해 jq를 써서 도넛의 이름만 쿼리하겠습니다
딥 스페이스, 초콜릿 II 커피 캐러멜 정확히 우리가 기대했던 것들입니다 대단하죠 하지만 우리는 더 잘할 수 있어요 그대로 우리 서버 및 클라이언트 응용 프로그램은 모두 데이터 모델 코드의 동일한 복사본을 가지고 있습니다 이러한 모델을 iOS 및 서버 애플리케이션 모델에 공유하여 반복을 방지하고 직렬화를 더 안전하게 만들 수 있죠 이를 높은 수준에서 설정하는 방법을 재검토해 봅시다 먼저 'Shared'라는 라이브러리에 또 다른 패키지를 생성합니다 그리고 이것을 Xcode의 workspace에 추가해 줍니다 그다음 데이터 모델 코드를 Shared 패키지로 이동할 수 있습니다 Shared를 서버 애플리케이션의 종속성으로 추가해 주고 iOS 응용 프로그램의 종속성으로도 추가해 줍니다 대상 프레임워크와 라이브러리 설정을 사용해서요 이 시점에서 클라이언트 코드를 리팩토링할 수 있습니다 공유 모델을 사용하도록 말이죠 그리고 동일한 작업을 서버 코드에 대해서도 수행합니다
이제 훨씬 더 나아 보이네요 이 애플리케이션을 발전시킬 만한 아이디어를 좀 말씀드리겠습니다 중앙 집중식 서버가 있다는 이점을 최대한 활용하여 메뉴에서 추가, 편집, 삭제가 되는 API 정의를 원할 수 있습니다 그러려면 Storage를 정적 파일에서 데이터베이스로 이동해야 합니다 데이터베이스가 준비되면 구매 및 주문 API를 구현할 수도 있습니다 이러한 API는 도넛 사업을 수익화하는 데 도움을 줄 수 있죠 또한 신호를 제공합니다 동적 가격 책정을 구현하는 데 사용할 수 있도록 말이죠 인기가 덜한 도넛에 대한 판매 및 할인과 같이요 기회는 무궁무진합니다 마무리하자면 이번 세션에서는 Swift가 범용 언어임을 확인했습니다 클라이언트와 서버 애플리케이션 모두에 유용하며 서버와 클라이언트 애플리케이션 상호간 코드를 공유하면 상용구를 줄이고 시스템 직렬화를 더 안전하게 만들 수 있습니다 URLSession은 서버와 비동기적으로 상호작용 하기 위한 핵심 도구이며 마지막으로 Xcode는 전체 시스템을 위한 강력한 개발 환경입니다 시청해 주셔서 감사드리며 남은 컨퍼런스도 즐기시길 바랍니다
-
-
1:36 - Simple, server package manifest
// swift-tools-version: 5.7 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"]), ] )
-
2:00 - Simple, server code
import Vapor @main public struct MyServer { public static func main() async throws { let webapp = Application() webapp.get("greet", use: Self.greet) 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 "" } }
-
3:42 - Using curl to test the local server
curl http://127.0.0.1:8080/greet; echo curl http://127.0.0.1:8080/echo --data "Hello from WWDC 2022"; echo
-
4:10 - Simple, iOS app server abstraction
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.invalidResponseEncoding } return responseBody } enum Errors: Error { case invalidResponseEncoding } }
-
5:00 - Simple, iOS app server call SwiftUI integration
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() } }
-
9:51 - Food truck, basic server
import Foundation import Vapor @main struct FoodTruckServerBootstrap { public static func main() async throws { // initialize the server let foodTruckServer = FoodTruckServer() // initialize the web framework and configure the http routes let webapp = Application() webapp.get("donuts", use: foodTruckServer.listDonuts) try webapp.run() } } struct FoodTruckServer { private let storage = Storage() func listDonuts(request: Request) async -> Response.Donuts { let donuts = self.storage.listDonuts() return Response.Donuts(donuts: donuts) } enum Response { struct Donuts: Content { var donuts: [Model.Donut] } } } struct Storage { var donuts = [Model.Donut]() func listDonuts() -> [Model.Donut] { return self.donuts } } enum Model { struct Donut: Codable { var id: Int var name: String var date: Date var dough: Dough var glaze: Glaze? var topping: Topping? } struct Dough: Codable { var name: String var description: String var flavors: FlavorProfile } struct Glaze: Codable { var name: String var description: String var flavors: FlavorProfile } struct Topping: Codable { var name: String var description: String var flavors: FlavorProfile } public struct FlavorProfile: Codable { var salty: Int? var sweet: Int? var bitter: Int? var sour: Int? var savory: Int? var spicy: Int? } }
-
12:18 - Food truck, server donuts menu
[ { "id": 0, "name": "Deep Space", "date": "2022-04-20T00:00:00Z", "dough": { "name": "Space Strawberry", "description": "The Space Strawberry plant grows its fruit as ready-to-pick donut dough.", "flavors": { "sweet": 3, "savory": 2 } }, "glaze": { "name": "Delta Quadrant Slice", "description": "Locally sourced, wormhole-to-table slice of the delta quadrant of the galaxy. Now with less hydrogen!", "flavors": { "salty": 1, "sour": 3, "spicy": 1 } }, "topping": { "name": "Rainbow Sprinkles", "description": "Cultivated from the many naturally occurring rainbows on various ocean planets.", "flavors": { "salty": 2, "sweet": 2, "sour": 1 } } }, { "id": 1, "name": "Chocolate II", "date": "2022-04-20T00:00:00Z", "dough": { "name": "Chocolate II", "description": "When Harold Chocolate II discovered this substance in 3028, it finally unlocked the ability of interstellar travel.", "flavors": { "salty": 1, "sweet": 3, "bitter": 1, "sour": -1, "savory": 1 } }, "glaze": { "name": "Chocolate II", "description": "A thin layer of melted Chocolate II, flash frozen to fit the standard Space Donut shape. Also useful for cleaning starship engines.", "flavors": { "salty": 1, "sweet": 2, "bitter": 1, "sour": -1, "savory": 2 } }, "topping": { "name": "Chocolate II", "description": "Particles of Chocolate II moulded into a sprinkle fashion. Do not feed to space whales.", "flavors": { "salty": 1, "sweet": 2, "bitter": 1, "sour": -1, "savory": 2 } } }, { "id": 2, "name": "Coffee Caramel", "date": "2022-04-20T00:00:00Z", "dough": { "name": "Hardened Coffee", "description": "Unlike other donut sellers, our coffee dough is simply a lot of coffee compressed into an ultra dense torus.", "flavors": { "sweet": -2, "bitter": 4, "sour": 2, "spicy": 1 } }, "glaze": { "name": "Caramel", "description": "Some good old fashioned Earth caramel.", "flavors": { "salty": 2, "sweet": 3, "sour": -1, "savory": 1 } }, "topping": { "name": "Nebula Bits", "description": "Scooped up by starships traveling through a sugar nebula.", "flavors": { "sweet": 4, "spicy": 1 } } } ]
-
12:23 - Food truck, server package manifest
// swift-tools-version: 5.7 import PackageDescription let package = Package( name: "FoodTruckServer", platforms: [.macOS("12.0")], products: [ .executable( name: "FoodTruckServer", targets: ["FoodTruckServer"]), ], dependencies: [ .package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.0.0")), ], targets: [ .executableTarget( name: "FoodTruckServer", dependencies: [ .product(name: "Vapor", package: "vapor") ], resources: [ .copy("menu.json") ] ), .testTarget( name: "FoodTruckServerTests", dependencies: ["FoodTruckServer"]), ] )
-
12:30 - Food truck, server with integrated storage
import Foundation import Vapor @main struct FoodTruckServerBootstrap { public static func main() async throws { // initialize the server let foodTruckServer = FoodTruckServer() try await foodTruckServer.bootstrap() // initialize the web framework and configure the http routes let webapp = Application() webapp.get("donuts", use: foodTruckServer.listDonuts) try webapp.run() } } struct FoodTruckServer { private let storage = Storage() func bootstrap() async throws { try await self.storage.load() } func listDonuts(request: Request) async -> Response.Donuts { let donuts = await self.storage.listDonuts() return Response.Donuts(donuts: donuts) } enum Response { struct Donuts: Content { var donuts: [Model.Donut] } } } actor Storage { let jsonDecoder: JSONDecoder var donuts = [Model.Donut]() init() { self.jsonDecoder = JSONDecoder() self.jsonDecoder.dateDecodingStrategy = .iso8601 } func load() throws { guard let path = Bundle.module.path(forResource: "menu", ofType: "json") else { throw Errors.menuFileNotFound } guard let data = FileManager.default.contents(atPath: path) else { throw Errors.failedLoadingMenu } self.donuts = try self.jsonDecoder.decode([Model.Donut].self, from: data) } func listDonuts() -> [Model.Donut] { return self.donuts } enum Errors: Error { case menuFileNotFound case failedLoadingMenu } } enum Model { struct Donut: Codable { var id: Int var name: String var date: Date var dough: Dough var glaze: Glaze? var topping: Topping? } struct Dough: Codable { var name: String var description: String var flavors: FlavorProfile } struct Glaze: Codable { var name: String var description: String var flavors: FlavorProfile } struct Topping: Codable { var name: String var description: String var flavors: FlavorProfile } public struct FlavorProfile: Codable { var salty: Int? var sweet: Int? var bitter: Int? var sour: Int? var savory: Int? var spicy: Int? } }
-
14:42 - Using curl and jq to test the local server
curl http://127.0.0.1:8080/donuts | jq . curl http://127.0.0.1:8080/donuts | jq '.donuts[] .name'
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.