IT/GoLang

두번째 글 - 함수 사용하기

Aaron's papa 2021. 9. 25. 22:15
반응형

오늘은 Go 언어의 함수에 대해 살펴보겠습니다. 사실 언어를 공부하게 되면 제일 먼저 학습하게 되는 것이 변수 선언 같은 것일 텐데요, 여기서는 조금 다르게 함수를 먼저 살펴보겠습니다. 지난번에 만들었던 Hello Workd on API Gateway의 코드(https://fallwalker.tistory.com/4) 를 바탕으로 함수를 만들어서 여러 개의 Path를 처리할 수 있도록 해보겠습니다.


URI 별로 분기 하기

우리가 만든 Go 애플리케이션은 람다로 실행되고 앞단의 HTTP 요청은 API Gateway가 처리해 주기 때문에 Header와 Path와 같은 HTTP와 관련된 데이터들은 인자로 받을 수 있습니다. handler() 함수에서 인자로 받고 있는  events.APIGatewayProxyRequest 가 바로 그 주인공 입니다. 이 값을 통해 Path를 전달받고, 각각의 Path 별로 함수를 구현해서 서로 다른 응답을 할 수 있게 구현해 보겠습니다. 

func handler(event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error){
    switch event.Path {
    case "/hello":
        // Do something about hello
    case "/bye":
        // Do something about bye
    default:
        // Do something about default

    }
    return events.APIGatewayProxyResponse{
        StatusCode: http.StatusOK,
        Body: "Hello World",
    }, nil
}

handler() 함수가 기존보다 조금 더 풍성해 졌습니다. 먼저 event 라는 변수를 통해 API Gateway로부터 받은 인자를 넘겨받습니다. 그리고 event.Path를 통해 어떤 URI로 들어왔는지를 확인 합니다.

event를 통해서 확인할 수 있는 건 Path 외에도 더 많습니다. 어떤 것들을 더 확인할 수 있는지는 https://github.com/aws/aws-lambda-go/blob/v1.24.0/events/apigw.go#L6 을 통해서 확인하시면 됩니다.

 

그럼 하나씩 함수를 만들어 보겠습니다.


DoHello()와 DoBye() 함수

각각의 Path를 처리하기 위한 DoHello() 함수와 DoBye() 함수를 만들어 보겠습니다. 함수를 정의할 때는 이미 앞의 예제를 통해서 보셨겠지만 func 라는 지시자를 통해서 정의해 줍니다. 그리고 다음으로 인자와 응답 값들을 정의해 줍니다.

func 함수이름 (인자) 응답 값

함수를 만들 때는 몇 가지 규칙이 있는데 어려운 것들은 우선은 빼고 가장 중요한 두 가지 규칙을 먼저 살펴보겠습니다.

  1. 함수의 이름을 소문자로 시작하게 만들면 패키지 내부에서만 호출 가능하며, 대문자로 시작하게 만들면 패키지 외부에서도 호출이 가능합니다. 우리는 이번 글에서 대문자로 시작하도록 함수 이름을 설정했는데 다음번 글에서 이에 대해 좀 더 자세히 살펴보겠습니다.
  2. 응답 값이 한개면 괄호를 생략할 수 있습니다. 응답값이 두 개 이상이면 괄호로 묶어 줘야 합니다.
    ex) func DoHello() string {} - 가능, func DoHello() string, error - 불가능, func DoHello() (string, error) - 가능

그래서 우리가 만든 DoHello()DoBye() 함수는 아래와 같은 모습이 됩니다.

func DoHello() string {
    return "Hello"
}

func DoBye() string {
    return "Bye"
}

각각 함수로 넘어오는 인자는 없으며, string 타입의 응답 값을 가지고 있는 함수가 됩니다. 그리고 이렇게 만들어 준 함수를 handler() 함수에 추가해 주겠습니다. responseBody 라는 변수를 만들고 각 함수의 호출 결과를 저장한 후에 이를 바탕으로 API 응답을 만들어 주도록 합니다.

func handler(event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error){
    switch event.Path {
    case "/hello":
        responseBody := DoHello()
    case "/bye":
        responseBody := DoBye()
    default:
        responseBody := "Default"
    }
    return events.APIGatewayProxyResponse{
        StatusCode: http.StatusOK,
        Body: responseBody,
    }, nil
}

하지만 이대로 코드를 작성하면 GoLand에서는 아래와 같이 빨간 줄 천지가 됩니다.

GoLand에서의 코드 에러

왜 그럴까요? 바로 responseBody 라는 변수의 범위 때문에 발생하는 이슈입니다.


Go의 변수 선언

우리가 의도했던 것은 각 함수의 결과를 responseBody에 저장한 후 사용하려고 했으나 의도대로 동작하지 않았습니다. 이는 Go 언어 변수의 범위 때문에 발생한 이슈입니다. switch, for 문과 같은 곳에서 선언한 변수들은 해당 로직이 종료되면 더 이상 존재하지 않습니다. Go 언어 변수의 지역성 때문인데요, 보통 다른 언어에서는 switch, for 문과 같은 곳에서 선언한 변수를 그 이후에도 사용할 수 있는 것과 달리 Go 언어에서는 로직 외부에서는 사용할 수 없습니다. 따라서 지금과 같은 경우에는 responseBody를 switch 외부에서 정의해 줘야 합니다. Go 언어의 변수 선언은 아래와 같이 됩니다.

var 변수이름 변수타입

이것도 다른 언어들과 많이 다르죠.

다른 언어들은 타입이 먼저 나오지만 Go 언어는 변수 이름이 먼저 나옵니다.

그래서 위와 같은 responseBody를 정의하기 위해서는 아래와 같이 정의해 줘야 합니다.

var responseBody string

하지만 위의 함수 예제를 보면 responseBody를 처음 사용한 곳에서는 var 나 string 이라는 단어가 보이지 않는데 왜 그럴까요? Go 언어는 var 지시자를 통해 정의하지 않아도 변수 정의와 값 할당을 동시에 할 수 있습니다. 바로 := 연산자를 통해 가능합니다. 그래서 위에서 본 responseBody := DoHello() 구문은 아래와 같은 구문입니다.

var responseBody string
responseBody = DoHello()

우리는 switch 문 밖에서도 responseBody를 사용해야 하기 때문에 var 지시자를 통해서 선언해 주어야 합니다. 그래서 코드는 아래와 같이 됩니다.

func handler(event events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error){
    var responseBody string

    switch event.Path {
    case "/hello":
        responseBody = DoHello()
    case "/bye":
        responseBody = DoBye()
    default:
        responseBody = "Default"
    }
    return events.APIGatewayProxyResponse{
        StatusCode: http.StatusOK,
        Body: responseBody,
    }, nil
}

이제 빨간 줄도 모두 없어졌을 겁니다. 빌드하고 serverless 를 통해 배포해 보겠습니다.


serverless 배포

serverless.yaml 파일도 아래와 같이 수정해 줍니다.

service: hello-world

provider:
  name: aws
  runtime: go1.x
  memorySize: 256
  region: ap-northeast-2
  timeout: 30

package:
  exclude:
  - ./**
  include:
  - ./bin/**

functions:
  helloWorld:
    handler: bin/hello-world
    events:
    - http:
        path: /
        method: GET
    - http:
        path: /hello
        method: GET
    - http:
        path: /bye
        method: GET

첫 번째 글에서는 /에 대한 Path만 정의해 줬지만 이번에 우리는 /hello/bye도 처리하기 위한 구현을 했기 때문에 두 개의 Path도 추가해 줍니다. 그리고 sls deploy로 배포해 준 후 curl로 테스트해 보겠습니다.

~/Desktop/Project/hello-world-go 28s
❯ curl https://o2slv6y2ac.execute-api.ap-northeast-2.amazonaws.com/dev/hello
Hello

~/Desktop/Project/hello-world-go
❯ curl https://o2slv6y2ac.execute-api.ap-northeast-2.amazonaws.com/dev/bye  
Bye

우리가 의도했던 대로 잘 동작하는 것을 확인할 수 있습니다.


마치며

이번 글에서는 함수를 이용해서 우리가 처리하려는 것들을 더 늘려 봤습니다. 그리고 변수 선언 방법에 대해서도 가볍게 살펴봤습니다. 변수 선언과 함수 정의는 모든 언어에서 가장 중요한 부분이라고 생각합니다. 실질적인 로직을 만들어 내기 위해 필요한 것들이기 때문입니다. Go 언어의 더 다양한 변수 선언과 함수 정의 부분은 차차 살펴보기로 하고 오늘 배운 가장 기본적인 내용에 대해서 먼저 학습하고 익숙해 지시기 바랍니다. 다음 글에서는 패키지화를 통해서 Go 애플리케이션을 조금 더 잘 구조화해서 사용하는 방법에 대해서 살펴보겠습니다. 감사합니다.

반응형