날짜: 2022.03.09
[5장: 본격적으로 API 개발하기]
여기서부터 구현하는 세부적인 API 기능들은 다음 포스트에서 “데이터 베이스”를 공부할 때 사용할 예정이므로 정확하게 이해하고 넘어가야한다. 이번 장에서는 미니터(= mini twitter)를 구현하면서 API 개발의 핵심구조와 개념들을 살펴본다.
[미니터 기능들]
이 책에서는 동시 접속이나 HTTP 요청 처리 속도를 다루지 않는다. (나중에 AWS 배포를 통해서 간접적으로 설명한다) Twitter의 핵심 기능들만 간단하게 구현한 것이기 때문에 완벽한 Twitter를 바라지는 말자. 밑의 기능들만 API 시스템에 구현시킬 것이다.
- 회원가입
- 로그인
- 트윗 (tweet)
- 다른 회원 팔로우하기
- 다른 회원 언팔로우하기
- 타임라인 (트윗 기록들 불러오기)
[1. 회원가입]
일반적인 회원가입 시스템처럼 사용자의 이름, 이메일, 전화번호를 HTTP Request를 통해 받은 후, API 시스템에 저장하면 된다. 저장하는 부분은 다음 포스트에서 구체적으로 다룰 것이다. DB (Data Base)를 사용해야 되기 때문이다.
먼저 복습차원에서 우리가 현재 프로그래밍하는 환경부터 구조까지 살펴보자. 우리는 “app.py”라는 API를 현재 vscode에서 프로그래밍하고 있다. Flask 모듈을 import해서 만드는 프로젝트로 Flask의 “@app.route”와 같은 기능들을 통해서 API 개발을 하는 것이다. (나중 포스트에서 “@app.route”가 의미하는 바를 정확하게 다룰 것이다).
“app.py”에서 API 시스템 기능들을 (엔드포인트들) 구현했으면 이제 터미널에서 API를 실행해야한다. API 시스템이 실행되고 있는 상태에서 다른 터미널 탭에서 클라이언트로서 API 터미널과 HTTP Protocol을 주고 받는 형식으로 우리의 클라이언트와 서버가 설계된 것이다. 자, 그럼 이제 app.py를 만들어보자.
* 코드 라인 기준으로 밑에 설명을 추가했다.
① 이 책은 flask 라는 마이크로 웹 프레임워크를 통해서 API 시스템을 개발하고 있다. Flask에서 필요한 기능들을 import해서 가져온 것이다. Flask는 그럼 어떻게 사용할까? 우리는 Flask를 기반으로한 객체인 “app”을 만들 때 사용한다. jsonify는 python dictionary를 JSON으로 다시 변환해서 HTTP Response로 넘겨주기 위함이다. request는 클라이언트가 HTTP Request로 보내는 JSON 형식의 회원가입 데이터를 python dictionary로 읽어들이기 위함이다.
③ Flask 객체를 “app”이라는 객체에 저장한 것이다.
④ 회원가입한 사용자들을 저장하는 딕셔너리.
⑤ 회원가입한 사용자들의 “id” 번호를 지정해주는 기준점. id는 우리가 서버에서 프로그래밍한 대로 자동으로 생성될 것이다.
⑦ “sign-up” 기능의 엔드포인트의 URI인 “/sign-up”과 허용되는 HTTP method 형태를 지정.
⑨ HTTP Request를 통해서 클라이언트가 넘겨준 JSON 데이터를 파이썬으로 변환한다. Request는 HTTP Request의 모든 정보 (headers, body)를 저장하지만, “request.json”은 Request의 JSON 데이터만 저장한다.
⑩ new_user라는 dictionary의 새로운 key-value pair를 생성 함으로써 id 값을 부여해준다.
⑪ 완성된 새로운 회원의 정보를 “app.users” dictionary에 저장한다.
⑫ id 값을 +1 해서 다음 회원 아이디를 준비해준다.
⑭ 다시 “python dictionary → JSON”으로 데이터를 변환해서 클라이언트 쪽으로 HTTP Response를 날려준다.
앞의 포스트에서 구현한 ping 엔드포인트까지 포함하면 우리의 현재 app.py를 vscode에서 보면 다음과 같아야한다.
이제 다시 터미널로 돌아가서 python virtual environment를 activate한 뒤, app.py 디렉터리에서 app.py API를 실행시키자.
이전에는 flask를 통해서 app.py를 실행시킬때는 밑의 명령어를 사용했다.
여기서 “FLASK_DEBUG=1” → “FLASK_ENV=development”로 바꾸면서 디폴트로 debug mode를 실행함과 동시에 “FLASK_ENV=development”를 통해서 현재 프로젝트가 개발 스테이지라는 것을 나타낸다.
이제 터미널에 클라이언트용으로 사용될 새로운 탭을 열어서, httpie를 사용해서 HTTP Request를 보내보자. 이미 해본 ping 엔드포인트부터 불러오자.
이제 sign-up 엔드포인트를 실행시켜보자.
위의 sign-up 엔드포인트는 “POST” 타입의 HTTP Request를 사용했다. POST의 경우 JSON 데이터를 request에 담아서 넘겨주는 것이기 때문에 “field=value” 형태로 JSON 데이터를 보내면된다.
[트윗 tweet 올리기]
트윗 올리기 엔드포인트는 다음 3가지 조건을 만족시키도록 만들것이다.
① 300글자를 초과하지 않는 글만 올릴 수 있다. 초과시, 400 Bad Request 응답을 돌려줘야한다.
② 전송된 글을 엔드포인트를 통해서 트윗들을 다시 읽을 수 있어야한다.
③ 타임라인 엔드포인트를 통해서 트윗들을 다시 불러올수 있어야한다.
클라이언트가 보내는 HTTP Request의 JSON 데이터는 다음과 같다고 가정하고 엔드포인트를 설계하면 된다.
이를 기반으로 tweet 엔드포인트를 구현해보자.
“app.tweets”라는 list에 모든 tweet들이 dictionary 형태로 저장되고 있다는 것이 핵심이다. tweet 엔드포인트로 테스트해 보려면 먼저 터미널로 돌아와자. 클라이언트용 탭에서 API한테 HTTP Request를 보내자. sign-up 엔드포인트를 구현하면서 API를 실행시켰기 때문에 또 API를 실행시킬 필요는 없을거다.
sign-up 엔드포인트를 통해 회원 아이디를 만들놨다면 정상적으로 “200 OK” response가 올 것이다. 만약 API를 재실행시켰다면 기존 데이터가 모두 지워지기 때문에 에러가 날 것이다. 아직 데이터베이스에 데이터를 저장한 것이 아니라 메모리에만 저장하기 때문에 서버를 재실행시키면 메모리 데이터는 초기화된다.
[팔로우 & 언팔로우]
이제 팔로우와 언팔로우 기능을 추가할 것이다. 팔로우 하는 경우에는 아는 사용자의 tweet을 볼수 있도록 만들것이다. 이는 나중에 타임라인 기능을 구현하기 위한 밑작업이다. 먼저 클라이언트와 서버로 전송할 JSON 데이터는 다음과 같다.
마찬가지로 언팔로우 데이터는 다음과 같은 형식일 것이다.
이제 실제 API 엔드포인트를 구현하자.
*라인 ⑭, ⑮에 대해서만 부가 설명을 하도록 하겠다.
일단 app.users 는 user 정보를 담고 있는 dictionary이다. 그리고 user 정보 자체도 dictionary다 보니까 사실상 app.users는 dictionary within dictionary의 형태이다. 즉, 라인 ⑭에서 user는 dictionary이다. 라인 ⑮ 에서 setdefault 기능은 dictionary에 이미 키 (key)가 있다면 무시하고, 만약에 찾고자하는 key가 없다면 새롭게 key를 만들어주는 것이다.
이 경우 key의 이름을 (우리의 경우에는 “follow”) 디폴트 값으로 미리 지정해서 새로운 key-value pair가 자동 생성되도록 하는 것이다. setdefault로 user에 key 값이 “follow”이고, value 형식이 set인 dictionary가 있는지 확인하고, 있다면 무시, 없다면 자동으로 생성한다. 그리고 add(user_id_to_follow)를 통해서 ‘value’ 값을 업데이트 시켜주는 것이다. ‘follow’ key에 대한 value 데이터 타입을 set()으로 해줌으로써 중복 저장을 방지한다.
vscode에서 API 엔드포인트를 추가했으면 다시 클라이언트 터미널로 돌아와서 HTTP Request를 보내도록 하자. follow HTTP Request를 보내기 전에 “id=2” 사용자를 만들어줘야한다. 여기까지 단계별로 HTTP 통신을 해보자.
먼저 main user 로 “Kevin”이라는 이름의 user를 만들어 준다.
우리의 sign_up 엔드포인트로 인해 자동으로 “id=1”이 부여된 사용자가 만들어질 것이다. 그리고 이 사용자 아이디로 tweet을 올리자.
트윗을 올리면 위와 같이 “id=1”에 해당하는 사용자 명의로 트윗 내용이 올라갈 것이다. Miniter API는 id로 사용자를 관리하기 때문에 사용자 이름은 데이터 일부에 불과하고 사용자를 구분하는 중요한 키 데이터 값은 id 값이다. 이제 팔로우를 요청할 수 있도록 “id=2” 의 사용자를 만들 것이다.
“id=2” 사용자도 만들었다면 이제 팔로우 요청을 보내면된다. 이미 follow 엔드포인트를 만들어놓았으니 HTTP Request만 날려주면 된다.
위와 같이 “500 INTERNAL SERVER ERROR”가 뜨는 것을 볼 수 있을 것이다.
에러 내용을 쭉~ 따라 내려가다보면 위와 같은 TypeError 가 있을 것이다. 위 메세지를 해석하자면, 우리가 만든 엔드포인트가 HTTP Response를 다시 돌려주는 과정에서 JSON 데이터 타입으로 변환하지 못하는 데이터 내용을 담아서 보내줬다는 것이다. 그렇기에 HTTP 송신 과정 상에서 오류가 뜬 것이다. console에서는 다음과 같은 메세지를 볼 수 있다.
위의 에러를 좀 더 파고들어보자. 에러가 난 이유는 JSON 데이터 타입으로 변환하지 못하는 내용을 API가 응답했기 때문이다. 엔드포인트는 파이썬 언어로 작성되었고 그러므로 자연스럽게 파이썬 자료구조를 사용했다. API 코드를 보면 “set” 자료구조형을 사용했다. 하지만 python과 JSON 데이터가 호환되는 것은 python의 “list” 데이터 타입이다. 그래서 list 타입은 jsonify를 통해서 JSON 데이터 타입으로 클라이언트에게 보내지지만, set의 경우에는 오류가 뜨는 것이다. 이 경우 custom JSON encoder를 통해서 set을 list 데이터 타입으로 바꾸도록 우리가 코드를 미리 설정해야한다. 그렇다고 해서 우리의 API 상에서 set → list 가 되지는 않는다. 파이썬으로 이미 set 타입으로 코딩을 함으로서 아이디 중복 저장 방지 기능을 탑재하고, HTTP 송신 과정에서만 타입 변환이 이루어지도록 하는 것이다. custom JSON encoder는 말 그래도 직접 customizing을 해주면 되는 것이다.
② : flask.json 모듈에서 JSONEncoder 클래스를 임포트한다.
④ : 중요한 부분이다. “상속”이라는 객체지향 프로그래밍 개념을 다루고 있다. 받아온 JSONEncoder를 우리가 만들 CustomJSONEncoder의 부모 클래스로서 지정한다. CustomJSONEncoder 클래스에 JSONEncoder를 파라미터로 넘겨주면 자동으로 CustomJSONEncoder는 기존의 JSONEncoder의 모든 기능과 변수들을 상속받는다.
⑤ : “def default(self, obj)” 메서드는 CustomJSONEncoder의 부모 클래스인 JSONEncoder에 이미 있는 메서드이다. 이제 이 함수를 over-write (확장)할 것이다. 다른 말로 기존의 함수를 우리의 입맛대로 만들 것이다.
⑥ : isinstance(obj, set)은 obj가 set인지 아닌지 확인한다. 우리의 시야에서는 보이지 않지만 JSONEncoder 클래스의 또 다른 메서드일 것이다.
⑦ : 우리가 확인하고자 하는 객체인 obj가 set인 경우 list로 데이터 타입을 변환한다.
⑨ : 만약 obj 타입이 set이 아니라면 기존의 프로그래밍 되어있는 JSONEncoder대로 데이터 타입이 유지된다.
⑬ : CustomJSONEncoder를 Flask의 default JSON encoder로 지정해준다. 이제 jsonify 함수가 호출되면 JSONEncoder가 아닌 over-write된 CustomJSONEncoder 클래스가 호출될 것이다.
여기까지 팔로우 엔드포인트를 위한 API 설정을 마쳤다. 이제 언팔로우 엔드포인트도 구현하자.
라인 93에서 remove가 아닌 discard를 사용한 이유는 remove의 경우 삭제하려는 값이 없으면 오류를 일으키지만, discard 메서드는 삭제하려는 값이 없어도 무시하고 넘어가기 때문이다. 다음 포스트에서는 사용자와 트윗을 불러올 수 있는 timeline 엔드포인트를 구현하고 모든 엔드포인트가 작동되는지 테스트해 볼 것이다.
'Tech Development > Python Backend (Flask API)' 카테고리의 다른 글
Python Backend - Study Notes 7 (2) | 2022.09.03 |
---|---|
Python Backend - Study Notes 6 (0) | 2022.09.01 |
Python Backend - Study Notes 4 (0) | 2022.06.24 |
Python Backend - Study Notes 3 (0) | 2022.06.16 |
Python Backend - Study Notes 2 (0) | 2022.06.16 |
댓글