은행 API 작성

완료됨

이제 온라인 은행의 핵심 논리를 빌드했으므로 브라우저(또는 명령줄)에서 테스트하기 위한 웹 API를 빌드해 보겠습니다. 지금은 데이터베이스를 사용하여 데이터를 유지하지 않으므로 모든 계좌를 메모리에 저장하는 전역 변수를 만들어야 합니다.

또한 이 가이드가 너무 길어지지 않도록 테스트 부분은 건너뛰겠습니다. 원칙적으로는 핵심 패키지를 빌드할 때처럼 코드보다 먼저 테스트를 작성하는 방식에 따라야 합니다.

메모리에 계좌 설정

데이터를 유지하기 위해 데이터베이스를 사용하는 대신 프로그램을 시작할 때 만들 계좌에 메모리 맵을 사용하겠습니다. 또한 맵을 통해 계좌 번호를 사용하여 계좌 정보에 액세스합니다.

$GOPATH/src/bankapi/main.go 파일로 이동하여 전역 accounts 변수를 만들고 계좌로 초기화하는 다음 코드를 추가합니다. (이 코드는 이전에 테스트를 만들 때 작성한 코드와 유사합니다.)

package main

import (
    "github.com/msft/bank"
)

var accounts = map[float64]*bank.Account{}

func main() {
    accounts[1001] = &bank.Account{
        Customer: bank.Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number: 1001,
    }
}

$GOPATH/src/bankapi/ 위치에 있는지 확인합니다. go run main.go로 프로그램을 실행하여 오류가 없는지 확인합니다. 지금은 프로그램에서 다른 작업을 수행하지 않으므로 웹 API를 만드는 논리를 추가해 보겠습니다.

명세서 메서드 공개

이전 모듈에서 살펴본 것처럼 Go로 웹 API를 만들기는 쉽습니다. net/http 패키지를 계속 사용하겠습니다. 또한 HandleFuncListenAndServe 함수를 사용하여 엔드포인트를 공개하고 서버를 시작합니다. HandleFunc 함수에는 공개하려는 URL 경로 이름과 해당 엔드포인트에 대한 논리를 포함하는 함수의 이름이 필요합니다.

먼저 계좌 명세서를 인쇄하는 기능을 공개해 보겠습니다. 다음 함수를 복사하여 main.go에 붙여넣습니다.

func statement(w http.ResponseWriter, req *http.Request) {
    numberqs := req.URL.Query().Get("number")

    if numberqs == "" {
        fmt.Fprintf(w, "Account number is missing!")
        return
    }

    if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
        fmt.Fprintf(w, "Invalid account number!")
    } else {
        account, ok := accounts[number]
        if !ok {
            fmt.Fprintf(w, "Account with number %v can't be found!", number)
        } else {
            fmt.Fprintf(w, account.Statement())
        }
    }
}

statement 함수에서 첫 번째 강조 표시는 브라우저에 응답을 다시 쓰기 위해 개체를 수신하고 있음을 나타냅니다(w http.ResponseWriter). 또한 HTTP 요청(req *http.Request)의 정보에 액세스하기 위해 요청 개체를 수신하고 있습니다.

그런 다음 req.URL.Query().Get() 함수를 사용하여 쿼리 문자열에서 매개 변수를 읽습니다. 이 매개 변수는 HTTP 호출을 통해 전송할 계정 번호입니다. 이 값을 사용하여 계좌 맵에 액세스하고 해당 정보를 가져옵니다.

사용자로부터 데이터를 가져오기 때문에 충돌을 방지하기 위해 몇 가지 유효성 검사를 포함해야 합니다. 유효한 계좌 번호가 있다면 Statement() 메서드를 호출하고 브라우저에 반환되는 문자열을 인쇄할 수 있습니다(fmt.Fprintf(w, account.Statement())).

이제 다음과 같이 main() 함수를 수정합니다.

func main() {
    accounts[1001] = &bank.Account{
        Customer: bank.Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number: 1001,
    }

    http.HandleFunc("/statement", statement)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

프로그램(go run main.go)을 실행할 때 오류나 출력이 표시되지 않으면 제대로 작동하는 것입니다. 웹 브라우저를 열고 URL http://localhost:8000/statement?number=1001을 입력하거나 프로그램이 실행되는 동안 다른 셸에서 다음 명령을 실행합니다.

curl http://localhost:8000/statement?number=1001

다음 출력이 표시됩니다.

1001 - John - 0

입금 메서드 공개

동일한 방법을 계속 사용하여 입금 메서드를 공개해 보겠습니다. 이 경우에는 메모리에 있는 계좌에 돈을 추가하려고 합니다. Deposit() 메서드를 호출할 때마다 잔액이 늘어나야 합니다.

주 프로그램에서 다음과 같은 deposit() 함수를 추가합니다. 이 함수는 쿼리 문자열에서 계좌 번호를 가져오고, 계좌가 accounts 맵에 있는지 확인하고, 입금 금액이 유효한 숫자인지 확인한 다음 Deposit() 메서드를 호출합니다.

func deposit(w http.ResponseWriter, req *http.Request) {
    numberqs := req.URL.Query().Get("number")
    amountqs := req.URL.Query().Get("amount")

    if numberqs == "" {
        fmt.Fprintf(w, "Account number is missing!")
        return
    }

    if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
        fmt.Fprintf(w, "Invalid account number!")
    } else if amount, err := strconv.ParseFloat(amountqs, 64); err != nil {
        fmt.Fprintf(w, "Invalid amount number!")
    } else {
        account, ok := accounts[number]
        if !ok {
            fmt.Fprintf(w, "Account with number %v can't be found!", number)
        } else {
            err := account.Deposit(amount)
            if err != nil {
                fmt.Fprintf(w, "%v", err)
            } else {
                fmt.Fprintf(w, account.Statement())
            }
        }
    }
}

이 함수는 사용자로부터 수신하는 데이터를 가져오고 유효성을 검사하는 비슷한 방법을 따릅니다. 또한 if 문에서 직접 변수를 선언하고 사용합니다. 마지막으로 계좌에 자금을 추가한 후 명세서를 인쇄하여 새 계좌 잔액을 확인합니다.

이제 deposit 함수를 호출하는 /deposit 엔드포인트를 공개해야 합니다. main() 함수를 다음과 같이 수정합니다.

func main() {
    accounts[1001] = &bank.Account{
        Customer: bank.Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number: 1001,
    }

    http.HandleFunc("/statement", statement)
    http.HandleFunc("/deposit", deposit)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

프로그램(go run main.go)을 실행할 때 오류나 출력이 표시되지 않으면 제대로 작동하는 것입니다. 웹 브라우저를 열고 URL http://localhost:8000/deposit?number=1001&amount=100을 입력하거나 프로그램이 실행되는 동안 다른 셸에서 다음 명령을 실행합니다.

curl "http://localhost:8000/deposit?number=1001&amount=100"

다음 출력이 표시됩니다.

1001 - John - 100

동일한 호출을 여러 번 수행하면 계좌 잔액이 계속 증가합니다. 메모리의 accounts 맵이 런타임에 업데이트되는지 확인해 보세요. 프로그램을 중지하면 모든 입금이 사라지지만 이 초기 버전에서는 예상된 것입니다.

출금 메서드 공개

마지막으로 계좌에서 출금하는 메서드를 공개해 보겠습니다. 이번에도 먼저 주 프로그램에서 withdraw 함수를 만들어 보겠습니다. 이 함수는 계좌 번호 정보의 유효성을 검사하고, 출금하고, 핵심 패키지에서 수신되는 오류를 인쇄합니다. 다음 함수를 주 프로그램에 추가합니다.

func withdraw(w http.ResponseWriter, req *http.Request) {
    numberqs := req.URL.Query().Get("number")
    amountqs := req.URL.Query().Get("amount")

    if numberqs == "" {
        fmt.Fprintf(w, "Account number is missing!")
        return
    }

    if number, err := strconv.ParseFloat(numberqs, 64); err != nil {
        fmt.Fprintf(w, "Invalid account number!")
    } else if amount, err := strconv.ParseFloat(amountqs, 64); err != nil {
        fmt.Fprintf(w, "Invalid amount number!")
    } else {
        account, ok := accounts[number]
        if !ok {
            fmt.Fprintf(w, "Account with number %v can't be found!", number)
        } else {
            err := account.Withdraw(amount)
            if err != nil {
                fmt.Fprintf(w, "%v", err)
            } else {
                fmt.Fprintf(w, account.Statement())
            }
        }
    }
}

이제 main() 함수에 /withdraw 엔드포인트를 추가하여 withdraw() 함수에 있는 논리를 공개합니다. main() 함수를 다음과 같이 수정합니다.

func main() {
    accounts[1001] = &bank.Account{
        Customer: bank.Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number: 1001,
    }

    http.HandleFunc("/statement", statement)
    http.HandleFunc("/deposit", deposit)
    http.HandleFunc("/withdraw", withdraw)
    log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

프로그램(go run main.go)을 실행할 때 오류나 출력이 표시되지 않으면 제대로 작동하는 것입니다. 웹 브라우저를 열고 URL http://localhost:8000/withdraw?number=1001&amount=100을 입력하거나 프로그램이 실행되는 동안 다른 셸에서 다음 명령을 실행합니다.

curl "http://localhost:8000/withdraw?number=1001&amount=100"

다음 출력이 표시됩니다.

the amount to withdraw should be greater than the account's balance

오류는 핵심 패키지에서 발생합니다. 프로그램이 시작되면 계좌 잔액이 0이 됩니다. 따라서 출금을 할 수 없습니다. /deposit 엔드포인트를 여러 번 호출하여 자금을 추가하고 /withdraw 엔드포인트를 다시 호출하여 작동하는지 확인합니다.

curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/deposit?number=1001&amount=100"
curl "http://localhost:8000/withdraw?number=1001&amount=100"

다음 출력이 표시됩니다.

1001 - John - 200

정말 간단하죠. 처음부터 빌드한 패키지로 시작하여 기능을 공개하는 웹 API를 만들었습니다. 다음 섹션으로 이동하여 연습을 계속하세요. 이번에는 직접 솔루션을 작성하는 과제가 제공됩니다.