스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Swift on Server 생태계 살펴보기
Swift는 서버 애플리케이션을 작성하는 데 탁월한 언어로, Apple의 여러 클라우드 제품이 제공하는 중요 서비스의 바탕이 됩니다. 도구와 Swift 서버 패키지 생태계에 대해 자세히 알아보고, 데이터베이스와 상호작용하는 방법과 애플리케이션에 옵저버빌리티를 더하는 방법을 살펴봅니다.
챕터
- 0:00 - Introduction
- 0:13 - Agenda
- 0:27 - Meet Swift on Server
- 2:30 - Build a service
- 3:46 - Swift OpenAPI generator
- 5:42 - Database drivers
- 10:53 - Observability
- 15:19 - Explore the ecosystem
- 16:12 - Wrap up
리소스
관련 비디오
WWDC23
-
다운로드
안녕하세요, 저는 Apple Swift on Server 팀의 Franz입니다 오늘은 Swift on Server 생태계를 살펴보겠습니다 먼저 Swift가 서버 애플리케이션을 개발하는 데 그토록 훌륭한 언어인 이유에 대해 이야기 해보겠습니다 그 다음에는 그 생태계에서 인기 있는 몇 가지 패키지로 서비스를 구축하고
마지막으로 그 생태계의 운영 방식과 자세한 내용을 확인 가능한 위치를 살펴보겠습니다
먼저 Swift가 서버 앱용으로 좋은 이유부터 알아보겠습니다
Swift를 사용하면 가비지 컬렉션 대신 자동 참조 카운팅을 통해 적은 메모리 사용량으로 C언어와 같은 성능을 구현할 수 있습니다 따라서 예측 가능한 리소스 소비와 빠른 시작 시간이 필요한 모던 클라우드 서비스에 완벽하게 적합합니다
Swift는 표현력이 풍부하고 안전한 언어이며 컴파일 시 다양한 버그를 제거하여 개발자가 강력하고 안정적인 분산 시스템을 작성할 수 있도록 지원합니다 강력한 타이핑 옵션 및 메모리 안전과 같은 기능을 통해 Swift 서비스는 충돌과 보안 취약성을 줄일 수 있습니다
때로는 클라우드 서비스가 아주 높은 동시 작업 부하를 처리해야 합니다 개발자는 Swift의 최고 수준의 동시성 기능을 통해 확장 가능하고 반응성이 뛰어난 서버 애플리케이션을 작성하는 동시에 데이터 경합으로 인한 일반적인 버그의 원인을 제거할 수 있습니다
이러한 모든 속성 덕분에 Swift는 서버 애플리케이션을 작성하는 데 탁월한 선택이 됩니다 사실 Swift는 Apple 클라우드 서비스 전반에서 여러 가지 중요한 기능을 지원합니다 iCloud 키체인, 사진, 메모 등에서 말이죠 다른 사용 사례로는 App Store 프로세싱 파이프 라인과 SharePlay 파일 공유가 있습니다 마지막으로 새로운 프라이빗 클라우드 컴퓨팅 서비스는 Swift on Server로 구축되었습니다
App Store 서비스 전체에서 Swift on Server를 사용하는 애플리케이션이 처리하는 초당 요청 수는 수백만 건에 달합니다
Apple 플랫폼 외부의 Server 생태계는 Swift 초기 사용자 중 하나였습니다 사실 Swift Server 운영 그룹은 Swift가 오픈 소스화 되고 불과 1년 후인 2016년에 설립되었습니다 가장 오래된 운영 그룹이죠
이 운영 그룹은 Swift on Server를 사용하는 기업과 생태계의 개별 기여자를 대표하는 멤버로 구성됩니다 서버 애플리케이션 개발 및 배포에 Swift 사용을 촉진하는 데 중점을 두고 있습니다 운영 그룹의 책임에 대해 설명하자면 서버 커뮤니티의 요구 사항을 해결하기 위한 노력을 정의하고 우선순위를 정합니다 중복 노력을 줄이고 호환성을 높이며 가장 좋은 방법을 찾고 증진하기 위해 패키지에 대한 인큐베이션 프로세스를 실행합니다
또한 이 운영 그룹은 서버 생태계의 피드백을 Swift 프로젝트의 다른 그룹에 전달합니다 이제 서버 생태계에서 인기 있는 몇 가지 패키지를 사용하여 서비스를 구축해 보겠습니다 저와 제 동료들은 올해 수많은 이벤트에 참석할 계획입니다 저희는 누가 어떤 이벤트에 참석하는지 추적하는 이벤트 서비스를 구축하고 싶었습니다 서비스는 두 가지 작업을 지원해야 합니다 모든 이벤트를 정리하고 목록을 만들어서 누가 어떤 이벤트에 참석할 계획인지 보여 줍니다 그리고 옥토버페스트에는 꼭 참석하고 싶기 때문에 새로운 이벤트를 만들기 위한 또 다른 작업이 필요합니다
Swift 패키지로 작업하려면 다양한 편집기, 즉 Xcode, VS Code, Neovim 또는 언어 서버 프로토콜을 지원하는 다른 편집기를 사용할 수 있습니다 오늘 데모의 경우 VS Code를 사용하여 패키지 작업을 할 것입니다 데모에서는 하단의 VS Code에 내장된 터미널을 사용하여 서비스의 출력을 확인하고 요청을 전송할 것입니다 이벤트 서비스를 시작할 수 있도록 패키지를 미리 준비했습니다 확인해 보시죠
이 패키지는 OpenAPI 생성기에 따라 Vapor를 OpenAPI용 서버 전송 수단으로 사용합니다
패키지에는 두 개의 대상이 있습니다
하나는 EventAPI 타겟으로 OpenAPIGenerator 플러그인이 구성되어 있으며
또다른 하나는 EventService executableTarget입니다 여기에는 서비스 구현이 포함됩니다
Swift OpenAPI 생성기를 사용하면 서비스를 YAML로 문서화하고 서버와 클라이언트를 위한 코드를 생성할 수 있습니다 OpenAPI 사용이 처음이거나 검토하려는 경우 작년에 제작한 ‘Swift OpenAPI 생성기 알아보기’를 한번 시청해 보시기 바랍니다 OpenAPI 문서를 검토해 보겠습니다
이벤트 경로에서 두 가지 작업을 모두 정의합니다
첫 번째 작업은 listEvents라는 get 메서드입니다
이 작업은 이벤트 배열이 포함된 성공 응답을 반환합니다
두 번째 작업은 createEvent라는 post 메서드입니다
이 작업은 이벤트의 JSON 본문을 가져와서
생성 성공의 여부에 따라 201 또는 400 상태 코드를 반환합니다
Apple 서비스에는 주요 진입점이 포함되어 있습니다
먼저 Vapor 애플리케이션을 생성합니다
그리고 그 애플리케이션을 사용해 OpenAPI VaporTransport를 생성합니다
그런 다음 서비스의 인스턴스를 생성하고 이를 전송에 등록합니다
마지막으로 HTTP 서버를 시작하고 들어오는 연결 수신을 대기하는 Vapor 애플리케이션을 실행합니다
또한 Apple의 서비스에서는 생성된 APIProtocol을 구현합니다
listEvents 메서드는 하드 코딩된 이벤트 배열을 반환합니다
createEvent 메서드가 현재 구현되지 않은 상태 코드를 반환하고 있습니다
이제 서비스를 시작해 보죠
서비스를 빌드하고 디버거를 연결할 것입니다 단말기의 하단을 보면 서버가 시작되었음을 알 수 있습니다
이제 다른 터미널에서 curl을 사용해 서비스를 쿼리하여 모든 이벤트의 목록을 만들 수 있습니다
서비스가 하드코딩된 이벤트 목록을 포함하는 JSON 배열을 반환했습니다
그러나 이제는 새로운 이벤트를 동적으로 추가하고 데이터베이스에 유지시키려고 합니다 이제 데이터베이스 드라이브를 살펴보겠습니다 오픈 소스 생태계에는 PostgreSQL, MySQL, Cassandra, MongoDB 등의 데이터베이스를 위한 다양한 데이터베이스 드라이버가 있습니다
오늘은 지속성을 위해 Postgres 데이터베이스를 사용해보겠습니다 PostgresNIO는 Vapor와 Apple에서 유지관리하는 Postgres용 오픈 소스 데이터베이스 드라이버입니다 PostgresNIO 1.21의 새로운 기능은 PostgresClient입니다 PostgresClient는 완전히 새로운 비동기 인터페이스를 제공하며 구조화된 동시성을 활용하는 연결 풀이 내장되어 있어 데이터베이스의 간헐적인 네트워킹 장애에도 탄력적으로 대응할 수 있습니다 또한 연결풀은 여러 연결에 쿼리를 분산하고 연결을 미리 준비하여 쿼리 실행 속도를 높임으로써 처리량을 향상시킵니다
이제 PostgresNIO를 사용해 이벤트 서비스를 데이터베이스에 연결해 보겠습니다
먼저 패키지에 PostgresNIO에 대한 종속성을 추가하고 서비스에서 이를 가져옵니다 그런 다음 PostgresClient를 사용하여 listEvents 메서드에서 데이터베이스를 쿼리합니다 마지막으로 데이터베이스에 새 이벤트를 삽입하기 위해 createEvent 메서드를 구현하겠습니다 패키지 매니페스트에서 PostgresNIO에 대한 종속성을 추가하는 것으로 시작하겠습니다
그런 다음 이벤트 서비스 타겟에 종속성을 추가할 수 있습니다
이제 서비스에서 PostgresNIO를 가져올 수 있습니다
다음으로 서비스에 PostgresClient 속성을 추가해 보겠습니다
클라이언트를 사용하여 listEvents 메서드에서 데이터베이스를 쿼리하겠습니다
쿼리 메서드가 행의 AsyncSequence를 반환합니다 하드 코딩된 이벤트 목록을 대체하기 위해 행을 반복하고 필드를 디코딩한 다음 각 행에 대한 이벤트를 생성합니다
쿼리 메서드에서 반환된 AsyncSequence는 데이터베이스에서 자동으로 행을 프리페치하여 성능 속도를 높입니다
서비스를 다시 실행하기 전에 먼저 PostgresClient를 생성하고 이를 서비스에 전달해야 합니다
먼저 로컬에서 이미 시작한 데이터베이스에 연결하기 위해 PostgresClient를 만듭니다
다음으로 PostgresClient를 서비스에 전달하겠습니다
클라이언트를 시작하려면 현재 작업이 완료될 때까지 현재 작업을 이어받는 실행 메서드를 호출해야 합니다 Vapor 애플리케이션과 PostgresClient를 동시에 실행하고자 하므로 작업 그룹을 사용해보겠습니다 폐기 작업 그룹을 만들고 PostgresClient를 실행하는 하위 작업을 추가하겠습니다
그런 다음 Vapor 애플리케이션 실행을 별도의 하위 작업으로 이동합니다
서비스를 다시 실행해 보겠습니다
재시동 버튼은 현재 프로세스를 중지하고 서비스를 다시 빌드한 후 다시 시작합니다
하단의 단말기가 실행되고 있음을 보여 줍니다 다시 모든 이벤트를 나열해 보겠습니다
데이터베이스가 비어 있는 것 같네요 데이터베이스에 새 이벤트를 추가하기 위해 다음으로 createEvent 메서드를 구현하겠습니다
먼저 입력을 전환하고 JSON 이벤트를 추출합니다
그런 다음 데이터베이스를 쿼리하여 새로운 이벤트를 삽입합니다
마지막으로 생성한 이벤트를 반환해야 합니다
다른 언어에서는 이 코드가 SQL 인젝션 취약점의 일반적인 벡터이기 때문에 이 코드는 일부 사람들에게 경각심을 불러일으킬 수 있습니다 이것이 문자열처럼 보이지만 실제로는 문자열이 아닙니다 Swift의 문자열 보간 기능을 사용하여 문자열 쿼리를 밸류 바인딩이 있는 매개변수화된 쿼리로 변환합니다 SQL 인젝션 공격으로부터 완벽히 안전하게 보호합니다 코드의 안전성을 보장하면서 사용 편의성을 높이는 Swift의 노력을 보여주는 훌륭한 예죠
서비스를 재시작하겠습니다
서비스가 다시 실행되면 curl을 사용하여 두 개의 이벤트를 생성합니다
이벤트 생성이 성공한 것으로 보이네요 모든 이벤트를 다시 나열하여 데이터베이스에 이벤트가 저장되었는지 확인해보겠습니다
좋아요, 모든 이벤트가 데이터베이스에 저장되었습니다 방금 Gus가 친구를 데려오고 싶다는 메시지를 보내면서 친구 이름으로 이벤트 항목을 하나 더 추가할 수 있는지 물었습니다 이제 Gus를 위한 다른 이벤트를 만들어 볼게요
문제가 발생한 것 같습니다 다른 이벤트 항목을 추가하려고 했는데 말이죠 하단의 터미널에서 긴 오류 메시지를 볼 수 있습니다 하지만 이 오류로는 정확히 무엇이 잘못되었는지 알 수 없습니다 우리가 확인할 수 있는 유일한 정보는 작업이 완료되지 않았고 발생한 오류가 PSQLError 유형이라는 것입니다 테이블 스키마와 같은 데이터베이스 정보가 실수로 유출되는 것을 방지하기 위해 PSQLError에 대한 설명에서는 세부 정보를 의도적으로 생략합니다 이와 같은 경우 여러분의 서비스에 통합 가시성을 추가하면 문제 해결에 도움이 됩니다 옵저버빌리티는 로깅, 지표, 추적 이렇게 세 가지 축으로 구성됩니다 로깅은 서비스가 정확히 무엇을 했는지 이해하는 데 도움이 되며 문제를 해결할 때 세부 사항을 파악할 수 있게 해줍니다 지표를 사용하면 서비스 상태를 높은 수준으로 한눈에 파악할 수 있습니다 로그와 지표는 단일 서비스가 무엇을 하는지 이해하는 데 도움을 주는 반면 모던 클라우드 시스템은 분산된 시스템의 집합인 경우가 많습니다 이때 추적은 단일 요청이 시스템을 통해 어떤 경로를 거쳤는지 파악하는 데 도움을 줍니다
Swift 생태계는 코드가 통합 가시성 이벤트를 방출할 수 있는 세 가지 요소 모두에 대한 API 패키지를 제공합니다
listEvents 메서드를 어떻게 계측할 수 있는지 살펴보겠습니다
먼저 swift-log를 사용하여 새 listEvents 요청을 처리하기 시작할 때 로그를 내보냅니다 문제 해결 시 추가적인 컨텍스트를 제공하는 로그 메시지에 메타데이터를 추가함으로써 swift-log는 구조화된 로깅을 지원합니다
그 다음에는 요청이 있을 때마다 증가하는 swift-metrics의 카운터를 추가하여 서비스에서 처리한 요청 수를 추적할 수 있습니다
마지막으로 swift-distributed-tracing을 추가하여 데이터베이스 쿼리 주위에 스팬을 생성하면 시스템을 통해 엔드 투 엔드 요청 문제를 해결하는 데 도움이 될 수 있습니다 Swift 분산 추적 작동 방식에 대해 자세히 알아보려면 작년에 진행된 세션 ‘구조화된 동시성의 기초를 넘어’를 확인해 보시기 바랍니다 우리는 방금 로깅, 지표, 추적을 통해 listEvents 메서드를 계측했습니다 우리가 사용한 API는 옵저버빌리티 백엔드에 구애받지 않고 서비스 작성자가 데이터를 전송할 위치를 선택할 수 있게 합니다 Swift on Server 생태계에는 로깅, 지표, 분산 추적을 위한 다양한 백엔드가 포함되어 있습니다 백엔드 선택은 세 가지 라이브러리의 부트스트랩 메서드를 호출하여 수행됩니다 부트스트랩은 실행 파일에서만 수행해야 합니다 그리고 가능한 한 빨리 수행해야 옵저버빌리티 이벤트가 손실되지 않습니다 또한 로깅 시스템을 먼저 부트스트랩하고 그리고 MetricsSystem 마지막으로 InstrumentationSystem을 부트스트랩하는 것이 좋습니다 이는 지표와 계측 시스템이 해당 상태에 대한 로그를 내보내고자 할 수 있기 때문입니다
단 몇 줄의 코드만으로 로그를 터미널로 지표를 Prometheus로 추적을 Open Telemetry로 전송할 수 있었습니다 createEvent 메서드에 로깅을 추가해 보겠습니다 Gus의 이름으로 다른 이벤트를 추가하려고 할 때 정확히 무엇이 잘못되었는지 이해하기 위해서죠
먼저 패키지 및 EventService 대상에 swift-log를 종속성으로 추가합니다
그런 다음 서비스에서 로깅 모듈을 가져올 수 있습니다
그런 다음 쿼리 메서드에서 발생하는 오류를 잡아 보겠습니다
쿼리 메서드는 쿼리를 실행할 때 문제가 발생하면 PSQLError를 발생시킵니다
Postgres 서버에서 보낸 오류 메시지가 포함된 로그 이벤트를 전송할 수 있도록 로거를 생성해보겠습니다
다음으로 오류 메시지를 추출하고 로그를 내보냅니다 PSQLError에는 serverInfo 속성에 무엇이 잘못되었는지에 대한 자세한 정보가 포함되어 있습니다
마지막으로 이벤트를 데이터베이스에 추가하는 동안 문제가 발생했음을 나타내는 badRequest 응답을 반환합니다
서비스를 다시 시작해보죠 그리고 오류에 대한 자세한 정보를 얻을 수 있는지 확인해 봅시다
기본적으로 swift-log는 터미널로 로그를 내보냅니다 이는 애플리케이션을 디버깅하기에 적합합니다 동일한 curl 명령을 실행하여 이벤트를 다시 생성해 보겠습니다
이번에는 같은 오류가 발생하지 않았습니다 badRequest 상태 코드를 반환했기 때문이죠
이제 서비스에서 로그를 확인해 보겠습니다 무엇이 잘못되었는지 살펴봅시다
하단의 터미널에서 로그 메시지를 볼 수 있습니다 오류 메시지 메타데이터 필드에서 중복 키 위반으로 인한 오류임을 알 수 있습니다 데이터베이스 테이블은 이름과 날짜 그리고 참석자의 조합에 대해 하나의 항목만 허용합니다
로깅을 추가한 덕분에 구체적인 문제를 해결할 수 있었습니다 이 버그는 나중에 제 동료가 수정할 것입니다
지금까지는 서비스를 구축하는 데 사용할 수 있는 Swift on Server 생태계의 라이브러리 중 일부를 간략하게 살펴봤습니다 다양한 사용 사례에 따라 더 많은 라이브러리가 있습니다 네트워킹 데이터베이스 드라이버 옵저버빌리티 메시지 스트리밍 등이 있죠 더 많은 라이브러리를 찾고 있다면 swift.org 패키지 섹션으로 이동하여 서버 카테고리를 살펴보세요 또한 swift 패키지 인덱스를 사용해 더 많은 서버 라이브러리를 찾을 수도 있습니다
패키지를 찾을 수 있는 또 다른 좋은 리소스는 Swift Server Workgroup의 인큐베이션 목록입니다 이 운영 그룹은 강력하고 안정적인 생태계를 만들기 위해 인큐베이션 프로세스를 실행합니다
인큐베이션 프로세스의 패키지는 성숙도 수준을 통해 샌드박스에서 인큐베이팅으로 졸업 단계로 전환됩니다
각 단계에는 패키지의 프로덕션 준비 상태와 사용 용도에 따라 다양한 요구 사항이 있습니다 인큐베이션된 패키지 목록은 swift.org에서 확인할 수 있습니다
이번 세션을 통해 Swift on Server 생태계에 대한 흥미를 갖게 되셨기를 바랍니다 지금까지 Swift가 서버 애플리케이션에 적합한 언어인 이유와 Apple의 클라우드 서비스 전반에 걸쳐 중요한 기능을 지원하는 방법에 대해 이야기해 봤습니다 그리고 몇 가지 패키지와 Swift Server 운영 그룹이 건강한 생태계를 성장시키는 데 어떻게 도움이 되는지도 살펴보았습니다 시청해 주셔서 감사합니다! 감사합니다, 옥토버페스트에서 뵙겠습니다!
-
-
3:23 - EventService Package.swift
// swift-tools-version:5.9 import PackageDescription let package = Package( name: "EventService", platforms: [.macOS(.v14)], dependencies: [ .package( url: "https://github.com/apple/swift-openapi-generator", from: "1.2.1" ), .package( url: "https://github.com/apple/swift-openapi-runtime", from: "1.4.0" ), .package( url: "https://github.com/vapor/vapor", from: "4.99.2" ), .package( url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.1" ), ], targets: [ .target( name: "EventAPI", dependencies: [ .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), ], plugins: [ .plugin( name: "OpenAPIGenerator", package: "swift-openapi-generator" ) ] ), .executableTarget( name: "EventService", dependencies: [ "EventAPI", .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), .product( name: "OpenAPIVapor", package: "swift-openapi-vapor" ), .product( name: "Vapor", package: "vapor" ), ] ), ] )
-
4:05 - EventService openapi.yaml
openapi: "3.1.0" info: title: "EventService" version: "1.0.0" servers: - url: "https://localhost:8080/api" description: "Example service deployment." paths: /events: get: operationId: "listEvents" responses: "200": description: "A success response with all events." content: application/json: schema: type: "array" items: $ref: "#/components/schemas/Event" post: operationId: "createEvent" requestBody: description: "The event to create." required: true content: application/json: schema: $ref: '#/components/schemas/Event' responses: '201': description: "A success indicating the event was created." '400': description: "A failure indicating the event wasn't created." components: schemas: Event: type: "object" description: "An event." properties: name: type: "string" description: "The event's name." date: type: "string" format: "date" description: "The day of the event." attendee: type: "string" description: "The name of the person attending the event." required: - "name" - "date" - "attendee"
-
4:35 - EventService initial implementation
import OpenAPIRuntime import OpenAPIVapor import Vapor import EventAPI @main struct Service { static func main() async throws { let application = try await Vapor.Application.make() let transport = VaporTransport(routesBuilder: application) let service = Service() try service.registerHandlers( on: transport, serverURL: URL(string: "/api")! ) try await application.execute() } } extension Service: APIProtocol { func listEvents( _ input: Operations.listEvents.Input ) async throws -> Operations.listEvents.Output { let events: [Components.Schemas.Event] = [ .init(name: "Server-Side Swift Conference", date: "26.09.2024", attendee: "Gus"), .init(name: "Oktoberfest", date: "21.09.2024", attendee: "Werner"), ] return .ok(.init(body: .json(events))) } func createEvent( _ input: Operations.createEvent.Input ) async throws -> Operations.createEvent.Output { return .undocumented(statusCode: 501, .init()) } }
-
6:56 - EventService Package.swift
// swift-tools-version:5.9 import PackageDescription let package = Package( name: "EventService", platforms: [.macOS(.v14)], dependencies: [ .package( url: "https://github.com/apple/swift-openapi-generator", from: "1.2.1" ), .package( url: "https://github.com/apple/swift-openapi-runtime", from: "1.4.0" ), .package( url: "https://github.com/vapor/vapor", from: "4.99.2" ), .package( url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.1" ), .package( url: "https://github.com/vapor/postgres-nio", from: "1.19.1" ), ], targets: [ .target( name: "EventAPI", dependencies: [ .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), ], plugins: [ .plugin( name: "OpenAPIGenerator", package: "swift-openapi-generator" ) ] ), .executableTarget( name: "EventService", dependencies: [ "EventAPI", .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), .product( name: "OpenAPIVapor", package: "swift-openapi-vapor" ), .product( name: "Vapor", package: "vapor" ), .product( name: "PostgresNIO", package: "postgres-nio" ), ] ), ] )
-
7:08 - Implementing the listEvents method
import OpenAPIRuntime import OpenAPIVapor import Vapor import EventAPI import PostgresNIO @main struct Service { let postgresClient: PostgresClient static func main() async throws { let application = try await Vapor.Application.make() let transport = VaporTransport(routesBuilder: application) let postgresClient = PostgresClient( configuration: .init( host: "localhost", username: "postgres", password: nil, database: nil, tls: .disable ) ) let service = Service(postgresClient: postgresClient) try service.registerHandlers( on: transport, serverURL: URL(string: "/api")! ) try await withThrowingDiscardingTaskGroup { group in group.addTask { await postgresClient.run() } group.addTask { try await application.execute() } } } } extension Service: APIProtocol { func listEvents( _ input: Operations.listEvents.Input ) async throws -> Operations.listEvents.Output { let rows = try await self.postgresClient.query("SELECT name, date, attendee FROM events") var events = [Components.Schemas.Event]() for try await (name, date, attendee) in rows.decode((String, String, String).self) { events.append(.init(name: name, date: date, attendee: attendee)) } return .ok(.init(body: .json(events))) } func createEvent( _ input: Operations.createEvent.Input ) async throws -> Operations.createEvent.Output { return .undocumented(statusCode: 501, .init()) } }
-
9:02 - Implementing the createEvent method
func createEvent( _ input: Operations.createEvent.Input ) async throws -> Operations.createEvent.Output { switch input.body { case .json(let event): try await self.postgresClient.query( """ INSERT INTO events (name, date, attendee) VALUES (\(event.name), \(event.date), \(event.attendee)) """ ) return .created(.init()) } }
-
11:34 - Instrumenting the listEvents method
func listEvents( _ input: Operations.listEvents.Input ) async throws -> Operations.listEvents.Output { let logger = Logger(label: "ListEvents") logger.info("Handling request", metadata: ["operation": "\(Operations.listEvents.id)"]) Counter(label: "list.events.counter").increment() return try await withSpan("database query") { span in let rows = try await postgresClient.query("SELECT name, date, attendee FROM events") return try await .ok(.init(body: .json(decodeEvents(rows)))) } }
-
13:14 - EventService Package.swift
// swift-tools-version:5.9 import PackageDescription let package = Package( name: "EventService", platforms: [.macOS(.v14)], dependencies: [ .package( url: "https://github.com/apple/swift-openapi-generator", from: "1.2.1" ), .package( url: "https://github.com/apple/swift-openapi-runtime", from: "1.4.0" ), .package( url: "https://github.com/vapor/vapor", from: "4.99.2" ), .package( url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.1" ), .package( url: "https://github.com/vapor/postgres-nio", from: "1.19.1" ), .package( url: "https://github.com/apple/swift-log", from: "1.5.4" ), ], targets: [ .target( name: "EventAPI", dependencies: [ .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), ], plugins: [ .plugin( name: "OpenAPIGenerator", package: "swift-openapi-generator" ) ] ), .executableTarget( name: "EventService", dependencies: [ "EventAPI", .product( name: "OpenAPIRuntime", package: "swift-openapi-runtime" ), .product( name: "OpenAPIVapor", package: "swift-openapi-vapor" ), .product( name: "Vapor", package: "vapor" ), .product( name: "PostgresNIO", package: "postgres-nio" ), .product( name: "Logging", package: "swift-log" ), ] ), ] )
-
13:38 - Adding logging to the createEvent method
func createEvent( _ input: Operations.createEvent.Input ) async throws -> Operations.createEvent.Output { switch input.body { case .json(let event): do { try await self.postgresClient.query( """ INSERT INTO events (name, date, attendee) VALUES (\(event.name), \(event.date), \(event.attendee)) """ ) return .created(.init()) } catch let error as PSQLError { let logger = Logger(label: "CreateEvent") if let message = error.serverInfo?[.message] { logger.info( "Failed to create event", metadata: ["error.message": "\(message)"] ) } return .badRequest(.init()) } } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.