읽기 전

  • 불필요한 코드나 잘못 작성된 내용에 대한 지적은 언제나 환영합니다.
  • 개인적으로 사용해보면서 배운 점을 정리한 글입니다.

사이드 프로젝트에서 python 기반으로 Lambda 함수를 기능별로 나눠서 관리하던 중 새롭게 주 기능을 추가하게 됨에 따라 GraphQL로 단일 Endpoint를 구성해 관리하자는 이야기가 있어 그에 관해 공부한 내용을 정리하려 합니다.

API 구조

이전 포스팅 Serverless | AWS Lambda GraphQL API #01 - Lambda 코드에서 Python 기반 Lambda에 graphene module로 GraphQL 코드를 작성하고 psycopg2 module로 RDS와 연결했다. 이제 Client 역할을 수행할 Android App에서 GraphQL API를 호출하는 코드에 대해 정리하려 한다.

프로젝트 코드

MyGraphQLAPI example app 깃헙 링크

app 구조

AWS_GraphQL_Lambda_02_01

MainActivity에서는 점수 입력, 업데이트, 조회를 수행하고 SecondActivity에서는 특정 학생의 전체 과목 점수 리스트 조회, 특정 과목 점수 삭제를 수행한다. 각 버튼 클릭 시 GraphQL API로 요청을 전송하며 response 결과를 하단 Textview에 표시한다.

AWS_GraphQL_Lambda_02_02

request를 전송하기 전 DB Table 상태는 아래 그림과 같다.

AWS_GraphQL_Lambda_02_03

점수 입력, 업데이트 조회

MainActivity.class 코드

package com.example.mygraphqlapi;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import org.json.JSONException;
import org.json.JSONObject;

import com.example.mygraphqlapi.NetworkFunction.AsyncGraphQLRequest;

import java.util.concurrent.ExecutionException;

public class MainActivity extends AppCompatActivity {
    private Button btnInsertScore, btnUpdateScore, btnGetScore, btnMainToSecond;
    private EditText editTextGradeMain, editTextClassCodeMain, editTextSubjectTypeMain, editTextNameMain, editTextScoreMain;
    private TextView txtShowResult;
    private JSONObject mainScoreInput = new JSONObject();
    private JSONObject response;
    private int grade, classCode, score;
    private String subjectType, name;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initLayout();
    }

    private void initLayout() {
        btnInsertScore = findViewById(R.id.btnInsertScore);
        btnUpdateScore = findViewById(R.id.btnUpdateScore);
        btnGetScore = findViewById(R.id.btnGetScore);
        editTextGradeMain = findViewById(R.id.editTextGradeMain);
        editTextClassCodeMain = findViewById(R.id.editTextClassCodeMain);
        editTextSubjectTypeMain = findViewById(R.id.editTextSubjectTypeMain);
        editTextNameMain = findViewById(R.id.editTextNameMain);
        editTextScoreMain = findViewById(R.id.editTextScoreMain);
        btnMainToSecond = findViewById(R.id.btnMainToSecond);
        txtShowResult = findViewById(R.id.txtShowResult);
        btnMainToSecond.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
                finish();
            }
        });
        btnInsertScore.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try{
                    buildScoreInput("Mutation");
                    AsyncGraphQLRequest asyncGraphQLRequest = new AsyncGraphQLRequest(mainScoreInput, "Insert");
                    response = asyncGraphQLRequest.execute().get();
                    txtShowResult.setText(response.toString());
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
        });
        btnUpdateScore.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try{
                    buildScoreInput("Mutation");
                    AsyncGraphQLRequest asyncGraphQLRequest = new AsyncGraphQLRequest(mainScoreInput, "Update");
                    response = asyncGraphQLRequest.execute().get();
                    txtShowResult.setText(response.toString());
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
        });
        btnGetScore.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try{
                    buildScoreInput("Query");
                    AsyncGraphQLRequest asyncGraphQLRequest = new AsyncGraphQLRequest(mainScoreInput, "Get");
                    response = asyncGraphQLRequest.execute().get();
                    txtShowResult.setText(response.toString());
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    private void buildScoreInput(String queryType) {
        grade = Integer.parseInt(editTextGradeMain.getText().toString());
        classCode = Integer.parseInt(editTextClassCodeMain.getText().toString());
        subjectType = editTextSubjectTypeMain.getText().toString();
        name = editTextNameMain.getText().toString();
        if (queryType.equals("Mutation")) {
            score = Integer.parseInt(editTextScoreMain.getText().toString());
            try {
                mainScoreInput.put("grade", grade);
                mainScoreInput.put("classCode", classCode);
                mainScoreInput.put("subjectType", subjectType);
                mainScoreInput.put("name", name);
                mainScoreInput.put("score", score);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        } else if (queryType.equals("Query")) {
            try {
                mainScoreInput.put("grade", grade);
                mainScoreInput.put("classCode", classCode);
                mainScoreInput.put("subjectType", subjectType);
                mainScoreInput.put("name", name);
            }  catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }
}

MainActivity에서의 실행 결과 화면은 아래 그림처럼 출력된다. 각각 Create, Update, Get을 실행한 결과이다.

AWS_GraphQL_Lambda_02_04

점수 리스트 조회, 점수 삭제

SecondActivity.class 코드

package com.example.mygraphqlapi;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import org.json.JSONException;
import org.json.JSONObject;

import com.example.mygraphqlapi.NetworkFunction.AsyncGraphQLRequest;

import java.util.concurrent.ExecutionException;

public class SecondActivity extends AppCompatActivity {
    private EditText editTextGradeSecond, editTextClassCodeSecond, editTextSubjectTypeSecond, editTextNameSecond;
    private TextView textViewPostResult;
    private Button btnListUserScore, btnDeleteUserScore, btnSecondToMain;
    private JSONObject secondScoreInput = new JSONObject();
    private JSONObject response;
    private int grade, classCode;
    private String subjectType, name;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        initLayout();
    }
    private void initLayout() {
        editTextClassCodeSecond = findViewById(R.id.editTextClassCodeSecond);
        editTextGradeSecond = findViewById(R.id.editTextGradeSecond);
        editTextSubjectTypeSecond = findViewById(R.id.editTextSubjectTypeSecond);
        editTextNameSecond = findViewById(R.id.editTextNameSecond);
        btnListUserScore = findViewById(R.id.btnListUserScore);
        btnDeleteUserScore = findViewById(R.id.btnDeleteUserScore);
        textViewPostResult = findViewById(R.id.textViewPostResult);
        btnSecondToMain = findViewById(R.id.btnSecondToMain);

        btnListUserScore.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try{
                    buildSecondScoreInput("Query");
                    AsyncGraphQLRequest asyncGraphQLRequest = new AsyncGraphQLRequest(secondScoreInput, "List");
                    response = asyncGraphQLRequest.execute().get();
                    textViewPostResult.setText(response.toString());
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
        });

        btnDeleteUserScore.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try{
                    buildSecondScoreInput("Mutation");
                    AsyncGraphQLRequest asyncGraphQLRequest = new AsyncGraphQLRequest(secondScoreInput, "Delete");
                    response = asyncGraphQLRequest.execute().get();
                    textViewPostResult.setText(response.toString());
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            }
        });

        btnSecondToMain.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(SecondActivity.this, MainActivity.class);
                startActivity(intent);
                finish();
            }
        });
    }

    private void buildSecondScoreInput(String queryType) {
        grade = Integer.parseInt(editTextGradeSecond.getText().toString());
        classCode = Integer.parseInt(editTextClassCodeSecond.getText().toString());
        name = editTextNameSecond.getText().toString();
        if (queryType.equals("Query")) {
            try{
                secondScoreInput.put("grade", grade);
                secondScoreInput.put("classCode", classCode);
                secondScoreInput.put("name", name);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        } else if (queryType.equals("Mutation")) {
            try{
                subjectType = editTextSubjectTypeSecond.getText().toString();
                secondScoreInput.put("grade", grade);
                secondScoreInput.put("classCode", classCode);
                secondScoreInput.put("subjectType", subjectType);
                secondScoreInput.put("name", name);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }
}

SecondActivity에서의 실행 결과 화면은 아래 그림처럼 출력된다. 각각 List, Deletefmf 실행한 결과이다.

AWS_GraphQL_Lambda_02_05

차례대로 5개의 요청을 수행한 이후의 DB Table 상태는 아래 그림과 같다.

AWS_GraphQL_Lambda_02_06

GraphQL request 코드

NetworkFunction.class 코드

package com.example.mygraphqlapi;

import android.accounts.NetworkErrorException;
import android.os.AsyncTask;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class NetworkFunction {
    static String TEST_GRAPHQL_API = "API_URL";

    public static class AsyncGraphQLRequest extends AsyncTask<Void, Void, JSONObject> {
        JSONObject input;
        String queryType;

        public AsyncGraphQLRequest(JSONObject input, String queryType){
            this.input = input;
            this.queryType = queryType;
        }

        @Override
        protected JSONObject doInBackground(Void... params) {
            JSONObject result = new JSONObject();
            try {
                result = graphQLRequest(input, queryType);
            } catch (Exception e) {
                Log.e("error", e.toString());
            }
            return result;
        }
    }

    public static JSONObject graphQLRequest(JSONObject queryScoreInput, String queryType) {
        JSONObject queryScoreData = new JSONObject();
        JSONArray queryScoreList = new JSONArray();
        String bodyJsonString;
        try {
            URL url = new URL(TEST_GRAPHQL_API);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(3000);
            conn.setConnectTimeout(3000);
            conn.setDoInput(true);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setUseCaches(false);
            conn.setDoOutput(true);
            JSONObject body = new JSONObject();
            if (queryType.equals("Get")) {
                body.put("query", "query GetScore " +
                        "($input: GetScoreInput!)" +
                        "}");
            } else if (queryType.equals("List")) {
                body.put("query", "query ListScore " +
                        "($input: GetScoreInput!)" +
                        "}");
            } else if (queryType.equals("Insert")) {
                body.put("query", "mutation CreateScore " +
                        "($input: PostScoreInput!)" +
                        "}");
            } else if (queryType.equals("Update")) {
                body.put("query", "mutation UpdateScore " +
                        "($input: PostScoreInput!)" +
                        "}");
            } else if (queryType.equals("Delete")) {
                body.put("query", "mutation DeleteScore " +
                        "($input: PostScoreInput!)" +
                        "}");
            }

            body.put("input", queryScoreInput);

            bodyJsonString = body.toString();
            DataOutputStream wr = new DataOutputStream(conn.getOutputStream());
            wr.write(bodyJsonString.getBytes(StandardCharsets.UTF_8));
            wr.flush();
            wr.close();
            conn.connect();

            InputStream responseInputStream;
            if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                conn.connect();
                responseInputStream = conn.getInputStream();
            } else {
                throw new NetworkErrorException("Can't access to the server");
            }

            InputStreamReader inputStreamReader = new InputStreamReader(responseInputStream, "EUC-KR");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            StringBuilder sb = new StringBuilder();
            String line;

            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }

            bufferedReader.close();
            conn.disconnect();

            String result = sb.toString().trim();
            JSONObject jsonObject = new JSONObject(result);
            if (queryType.equals("Get")) {
                queryScoreData = jsonObject.getJSONObject("getScore");
            } else if (queryType.equals("List")) {
                queryScoreData = jsonObject;
            } else if (queryType.equals("Insert")) {
                queryScoreData = jsonObject.getJSONObject("createScore");
            } else if (queryType.equals("Update")) {
                queryScoreData = jsonObject.getJSONObject("updateScore");
            } else if (queryType.equals("Delete")) {
                queryScoreData = jsonObject.getJSONObject("deleteScore");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return new JSONObject();
        }
        return queryScoreData;
    }
}

POST 메소드를 사용하기로 했으므로 NetworkFunction.class에서 API gateway가 배포한 endpoint url로 연결을 시도한다. GraphQL 문법에 따라 쿼리를 구성하면 되는데 한줄로 작성해야 하므로 가독성을 위해 +연산으로 개행처리 하였다. 반환 타입이 다르면 별개의 메소드를 작성해야 하지만 예시로 작성된 프로젝트이므로 단순하게 반환한뒤 string으로 출력하게끔 작성했다.

+ Recent posts