AWS 시작하기
iOS 애플리케이션 구축
AWS Amplify를 사용하여 단순한 iOS 애플리케이션 생성
모듈 4: GraphQL API 및 데이터베이스 추가
이 모듈에서는 Amplify CLI와 라이브러리를 사용하여 GraphQL API를 구성하고 앱에 추가합니다.
소개
앱을 생성하고 사용자 인증으로 앱을 구성했으니 이제 API와 생성, 읽기, 업데이트, 삭제(CRUD) 작업을 데이터베이스에 추가하겠습니다.
이 모듈에서는 Amplify CLI와 라이브러리를 사용하여 앱에 API를 추가해봅니다. 여기서 생성할 API는 GraphQL API로, Amazon DynamoDB(NoSQL 데이터베이스) 기반의 AWS AppSync(관리형 GraphQL 서비스)를 활용합니다. GraphQL에 대한 자세한 소개는 이 페이지를 참조하십시오.
여기서 구축할 앱은 사용자가 메모를 작성하고 삭제하고 나열할 수 있는 메모 앱입니다. 이 예제를 통해 일반적인 유형의 CRUD+L(생성, 읽기, 업데이트, 삭제 및 나열) 애플리케이션을 구축하는 방법을 살펴볼 수 있습니다.
배우게 될 내용
- GraphQL API 생성 및 배포
- API와 상호 작용하는 프런트 엔드 코드 작성
주요 개념
API – 여러 소프트웨어 중개자 간의 통신과 상호 작용을 지원하는 프로그래밍 인터페이스를 제공합니다.
GraphQL – 애플리케이션의 유형이 지정된 표현을 기반으로 쿼리 언어 및 서버 측 API 구현 방식입니다. 이 API 표현 방식은 GraphQL 유형 시스템을 기반으로 한 스키마를 사용하여 선언됩니다. GraphQL에 대해 자세히 알아보려면 이 페이지를 참조하십시오.
완료 시간
20분
사용되는 서비스
구현
-
GraphQL API 서비스 및 데이터베이스 생성
GraphQL API와 지원 데이터베이스를 생성하려면 터미널을 열고 프로젝트 디렉터리에서 다음 명령을 실행합니다.
amplify add api ? Please select from one of the below mentioned services: select GraphQL and press enter ? Provide API name: select the default, press enter ? Choose the default authorization type for the API: use the arrow key to select Amazon Cognito User Pool and press enter ? Do you want to configure advanced settings for the GraphQL API: select the default No, I am done and press enter ? Do you have an annotated GraphQL schema? keep the default N and press enter ? What best describes your project: choose any model, we are going to replace it with our own anyway. Press enter ? Do you want to edit the schema now? type Y and press enter
프로젝트를 초기화(amplify init)할 때 선택한 기본 텍스트 편집기가 열리고 미리 구축된 데이터 스키마가 표시됩니다.
스키마를 삭제하고 앱 GraphQL 스키마로 바꿉니다.
type NoteData @model @auth (rules: [ { allow: owner } ]) { id: ID! name: String! description: String image: String }
이 데이터 모델은 NoteData 클래스 1개와 4개의 속성으로 구성됩니다. ID와 이름은 필수입니다. 설명과 이미지는 선택적 문자열입니다.
@model 변환기는 이러한 데이터를 저장할 데이터베이스를 생성합니다.
@auth 변환기는 이러한 데이터에 대한 액세스를 허용하는 인증 규칙을 추가합니다. 이 프로젝트에서는 NoteData의 소유자만 데이터에 액세스할 수 있습니다.
완료되면 저장합니다.
-
클라이언트 측 코드 생성
Amplify는 방금 생성된 GraphQL 데이터 모델 정의에 따라 앱에서 데이터를 표시할 클라이언트 측 코드(Swift 코드)를 생성합니다.
코드를 생성하려면 터미널에서 다음 명령을 실행합니다.
amplify codegen models
그러면 amplify/generated/models 디렉터리에 Swift 파일이 생성됩니다. 다음 명령을 사용하여 파일을 확인할 수 있습니다.
➜ iOS Getting Started git:(master) ✗ ls -al amplify/generated/models total 24 drwxr-xr-x 5 stormacq admin 160 Jul 9 14:20 . drwxr-xr-x 3 stormacq admin 96 Jul 9 14:20 .. -rw-r--r-- 1 stormacq admin 380 Jul 9 14:20 AmplifyModels.swift -rw-r--r-- 1 stormacq admin 822 Jul 9 14:20 NoteData+Schema.swift -rw-r--r-- 1 stormacq admin 445 Jul 9 14:20 NoteData.swift
이러한 파일을 Xcode 프로젝트로 가져옵니다. 파인더에서 파일을 찾고 Xcode의 프로젝트로 끌어서 놓습니다.
-
API 서비스 및 데이터베이스 배포
방금 생성한 백엔드 API와 데이터베이스를 배포하려면 터미널로 이동하여 다음 명령을 실행합니다.
amplify push # press Y when asked to continue ? Are you sure you want to continue? accept the default Y and press enter ? Do you want to generate code for your newly created GraphQL API type N and press enter
몇 분 후에 다음과 같은 성공 메시지가 표시됩니다.
✔ All resources are updated in the cloud GraphQL endpoint: https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql
-
Xcode 프로젝트에 API 클라이언트 라이브러리 추가
코드 작업을 시작하기 전에 Amplify API 라이브러리를 프로젝트의 종속성에 추가합니다. Podfile 파일을 열고 AmplifyPlugins/AWSAPIPlugin을 사용하여 해당 줄을 추가하거나 아래의 전체 파일을 복사해 붙여 넣습니다.
# you need at least version 13.0 for this tutorial, more recent versions are valid too platform :ios, '13.0' target 'getting started' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for getting started pod 'Amplify', '~> 1.0' # required amplify dependency pod 'Amplify/Tools', '~> 1.0' # allows to call amplify CLI from within Xcode pod 'AmplifyPlugins/AWSCognitoAuthPlugin', '~> 1.0' # support for Cognito user authentication pod 'AmplifyPlugins/AWSAPIPlugin', '~> 1.0' # support for GraphQL API end
터미널에서 다음 명령을 실행합니다.
pod install
이 명령은 완료하는 데 다소 시간이 걸립니다. 다음 명령이 표시됩니다. 실제 버전 번호는 다를 수 있습니다.
Analyzing dependencies Downloading dependencies Installing AmplifyPlugins 1.0.4 Installing AppSyncRealTimeClient (1.1.6) Installing ReachabilitySwift (5.0.0) Installing Starscream (3.0.6) Generating Pods project Integrating client project Pod installation complete! There are 4 dependencies from the Podfile and 11 total pods installed.
-
런타임 시 Amplify 라이브러리 초기화
Xcode로 돌아가 Backend.swift를 열고 Amplify 초기화 시퀀스의 private init() 메서드에 줄을 추가합니다. 완성된 코드 블록은 다음과 같습니다.
// initialize amplify do { try Amplify.add(plugin: AWSCognitoAuthPlugin()) try Amplify.add(plugin: AWSAPIPlugin(modelRegistration: AmplifyModels())) try Amplify.configure() print("Initialized Amplify") } catch { print("Could not initialize Amplify: \(error)") }
-
GraphQL 데이터 모델과 앱 모델 사이에 브리징 추가
프로젝트에는 Note를 나타내는 데이터 모델이 이미 있습니다. 따라서 이 모델을 계속해서 사용하고 NoteData를 Note로 간편하게 변환하는 것이 좋습니다. ContentView.swift를 열고 Note 클래스에 이 이니셜라이저를 추가합니다.
convenience init(from data: NoteData) { self.init(id: data.id, name: data.name, description: data.description, image: data.image) // store API object for easy retrieval later self._data = data } fileprivate var _data : NoteData? // access the privately stored NoteData or build one if we don't have one. var data : NoteData { if (_data == nil) { _data = NoteData(id: self.id, name: self.name, description: self.description, image: self.imageName) } return _data! }
-
Backend 클래스에 API CRUD 메서드 추가
API를 호출하는 3개의 메서드(Note 쿼리 메서드, 새 Note 생성 메서드 및 Note 삭제 메서드)를 추가합니다. 이러한 메서드는 앱 데이터 모델(Note)에서 작동하므로 사용자 인터페이스에서 쉽게 상호 작용할 수 있습니다. 이러한 메서드는 Note를 자동으로 GraphQL의 NoteData 객체로 변환합니다.
Backend.swift 파일을 열고 Backend 클래스 끝에 다음 조각을 추가합니다.
// MARK: API Access func queryNotes() { _ = Amplify.API.query(request: .list(NoteData.self)) { event in switch event { case .success(let result): switch result { case .success(let notesData): print("Successfully retrieved list of Notes") // convert an array of NoteData to an array of Note class instances for n in notesData { let note = Note.init(from: n) DispatchQueue.main.async() { UserData.shared.notes.append(note) } } case .failure(let error): print("Can not retrieve result : error \(error.errorDescription)") } case .failure(let error): print("Can not retrieve Notes : error \(error)") } } } func createNote(note: Note) { // use note.data to access the NoteData instance _ = Amplify.API.mutate(request: .create(note.data)) { event in switch event { case .success(let result): switch result { case .success(let data): print("Successfully created note: \(data)") case .failure(let error): print("Got failed result with \(error.errorDescription)") } case .failure(let error): print("Got failed event with error \(error)") } } } func deleteNote(note: Note) { // use note.data to access the NoteData instance _ = Amplify.API.mutate(request: .delete(note.data)) { event in switch event { case .success(let result): switch result { case .success(let data): print("Successfully deleted note: \(data)") case .failure(let error): print("Got failed result with \(error.errorDescription)") } case .failure(let error): print("Got failed event with error \(error)") } } }
동일한 Backend.swift 파일에서 updateUserData(withSignInStatus:) 메서드를 다음과 같이 업데이트합니다.
// change our internal state, this triggers an UI update on the main thread func updateUserData(withSignInStatus status : Bool) { DispatchQueue.main.async() { let userData : UserData = .shared userData.isSignedIn = status // when user is signed in, query the database, otherwise empty our model if status { self.queryNotes() } else { userData.notes = [] } } }
이제, 새 Note를 생성하고 목록에서 Note를 삭제할 수 있는 사용자 인터페이스를 생성하기만 하면 됩니다.
-
Add Note에 Edit 버튼 추가
백엔드 및 데이터 모델 조각이 준비되었으니 이 섹션의 마지막 단계로 사용자가 새 Note를 생성하고 삭제할 수 있는 기능을 제공하면 됩니다.
Xcode에서 ContentView.swift 열기
a. ContentView 구조체에서 사용자 인터페이스에 바인딩된 상태 변수를 추가합니다.
// add at the begining of ContentView class @State var showCreateNote = false @State var name : String = "New Note" @State var description : String = "This is a new note" @State var image : String = "image"
b. 파일의 아무 위치에 사용자가 새 Note를 생성할 수 있게 하는 View 구조체를 추가합니다.
struct AddNoteView: View { @Binding var isPresented: Bool var userData: UserData @State var name : String = "New Note" @State var description : String = "This is a new note" @State var image : String = "image" var body: some View { Form { Section(header: Text("TEXT")) { TextField("Name", text: $name) TextField("Name", text: $description) } Section(header: Text("PICTURE")) { TextField("Name", text: $image) } Section { Button(action: { self.isPresented = false let noteData = NoteData(id : UUID().uuidString, name: self.$name.wrappedValue, description: self.$description.wrappedValue) let note = Note(from: noteData) // asynchronously store the note (and assume it will succeed) Backend.shared.createNote(note: note) // add the new note in our userdata, this will refresh UI self.userData.notes.append(note) }) { Text("Create this note") } } } } }
c. Note 생성을 위한 시트를 나타내는 + 버튼을 탐색 모음에 추가합니다.
ContentView 구조체로 돌아와서 navigationBarItems(leading SignOutButton())을 다음으로 바꿉니다.
.navigationBarItems(leading: SignOutButton(), trailing: Button(action: { self.showCreateNote.toggle() }) { Image(systemName: "plus") }) }.sheet(isPresented: $showCreateNote) { AddNoteView(isPresented: self.$showCreateNote, userData: self.userData)
-
Delete 동작에 Swipe 추가
마지막으로, ContentView에서 '밀어서 삭제' 동작을 추가합니다. .onDelete { } 메서드를 ForEach 구조체에 추가합니다.
ForEach(userData.notes) { note in ListRow(note: note) }.onDelete { indices in indices.forEach { // removing from user data will refresh UI let note = self.userData.notes.remove(at: $0) // asynchronously remove from database Backend.shared.deleteNote(note: note) } }
-
구축 및 테스트
정상적으로 작동하는지 확인하기 위해 프로젝트를 빌드하고 실행합니다. 제품(Product) 메뉴에서 실행(Run)을 선택하거나 ⌘R을 입력합니다. 오류가 발생하지 않아야 합니다.
아직 로그인되어 있다면 앱이 빈 목록에서 시작됩니다. Note를 추가할 수 있는 + 버튼이 포함되어 있습니다. + 기호를 탭하고 [이 메모 생성(Create this Note)]을 탭하면 목록에 메모가 표시됩니다.
AddNoteView를 아래로 당겨 닫을 수 있습니다. iOS 시뮬레이터에서는 +를 두 번 탭할 수 없으므로 먼저 '당겨서 새로 고침’을 통해 목록을 새로 고쳐야 합니다.
행을 왼쪽으로 밀어 메모를 삭제할 수 있습니다.
전체 흐름은 다음과 같습니다.